added stable version
This commit is contained in:
parent
93184d21d1
commit
9fc8a043ba
230 changed files with 2671 additions and 13821 deletions
|
|
@ -13,9 +13,9 @@ import com.github.spotbugs.snom.Effort
|
|||
import com.github.spotbugs.snom.SpotBugsTask
|
||||
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.plugin.compose" version "2.2.20"
|
||||
id "org.jetbrains.kotlin.plugin.compose" version "2.2.0"
|
||||
id "org.jetbrains.kotlin.kapt"
|
||||
id 'com.google.devtools.ksp' version '2.2.20-2.0.3'
|
||||
id 'com.google.devtools.ksp' version '2.2.0-2.0.2'
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
|
@ -28,22 +28,22 @@ apply plugin: "org.jlleitschuh.gradle.ktlint"
|
|||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
android {
|
||||
compileSdkVersion 35
|
||||
compileSdk 35
|
||||
|
||||
namespace = 'com.nextcloud.talk'
|
||||
namespace 'com.nextcloud.talk'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 35
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
// mayor.minor.hotfix.increment (for increment: 01-50=Alpha / 51-89=RC / 90-99=stable)
|
||||
// xx .xxx .xx .xx
|
||||
versionCode 230000005
|
||||
versionName "23.0.0 Alpha 05"
|
||||
versionCode 210020090
|
||||
versionName "21.2.0"
|
||||
|
||||
flavorDimensions "default"
|
||||
renderscriptTargetApi = 19
|
||||
renderscriptTargetApi 19
|
||||
renderscriptSupportModeEnabled true
|
||||
|
||||
productFlavors {
|
||||
|
|
@ -65,7 +65,7 @@ android {
|
|||
}
|
||||
|
||||
// Enabling multidex support.
|
||||
multiDexEnabled = true
|
||||
multiDexEnabled true
|
||||
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
|
||||
|
|
@ -82,11 +82,6 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
// Adds exported schema location as test app assets.
|
||||
getByName("androidTest").assets.srcDir("$projectDir/schemas")
|
||||
}
|
||||
|
||||
testInstrumentationRunnerArgument "TEST_SERVER_URL", "${NC_TEST_SERVER_BASEURL}"
|
||||
testInstrumentationRunnerArgument "TEST_SERVER_USERNAME", "${NC_TEST_SERVER_USERNAME}"
|
||||
testInstrumentationRunnerArgument "TEST_SERVER_PASSWORD", "${NC_TEST_SERVER_PASSWORD}"
|
||||
|
|
@ -100,7 +95,6 @@ android {
|
|||
unitTests.all {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
unitTests.returnDefaultValues = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
@ -135,7 +129,7 @@ android {
|
|||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
viewBinding true
|
||||
buildConfig = true
|
||||
compose = true
|
||||
}
|
||||
|
|
@ -145,10 +139,10 @@ android {
|
|||
}
|
||||
|
||||
lint {
|
||||
abortOnError = false
|
||||
abortOnError false
|
||||
disable 'MissingTranslation','PrivateResource'
|
||||
htmlOutput = layout.buildDirectory.file("reports/lint/lint.html").get().asFile
|
||||
htmlReport = true
|
||||
htmlOutput file("$project.buildDir/reports/lint/lint.html")
|
||||
htmlReport true
|
||||
}
|
||||
}
|
||||
kapt {
|
||||
|
|
@ -156,23 +150,23 @@ kapt {
|
|||
}
|
||||
|
||||
ext {
|
||||
androidxCameraVersion = "1.5.0"
|
||||
androidxCameraVersion = "1.4.2"
|
||||
coilKtVersion = "2.7.0"
|
||||
daggerVersion = "2.57.1"
|
||||
emojiVersion = "1.6.0"
|
||||
daggerVersion = "2.56.2"
|
||||
emojiVersion = "1.5.0"
|
||||
fidoVersion = "4.1.0-patch2"
|
||||
lifecycleVersion = '2.9.4'
|
||||
lifecycleVersion = '2.9.1'
|
||||
okhttpVersion = "4.12.0"
|
||||
markwonVersion = "4.6.2"
|
||||
materialDialogsVersion = "3.3.0"
|
||||
parcelerVersion = "1.1.13"
|
||||
prismVersion = "2.0.0"
|
||||
retrofit2Version = "3.0.0"
|
||||
roomVersion = "2.8.0"
|
||||
workVersion = "2.10.4"
|
||||
espressoVersion = "3.7.0"
|
||||
roomVersion = "2.7.2"
|
||||
workVersion = "2.10.2"
|
||||
espressoVersion = "3.6.1"
|
||||
androidxTestVersion = "1.5.0"
|
||||
media3_version = "1.8.0"
|
||||
media3_version = "1.7.1"
|
||||
coroutines_version = "1.10.2"
|
||||
mockitoKotlinVersion = "6.0.0"
|
||||
}
|
||||
|
|
@ -185,24 +179,22 @@ configurations.configureEach {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation "androidx.room:room-testing-android:${roomVersion}"
|
||||
implementation 'androidx.compose.foundation:foundation-layout:1.9.1'
|
||||
spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0'
|
||||
spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.14'
|
||||
spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.11'
|
||||
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.8")
|
||||
|
||||
implementation("androidx.compose.runtime:runtime:1.9.1")
|
||||
implementation("androidx.compose.runtime:runtime:1.8.3")
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation 'androidx.datastore:datastore-core:1.1.7'
|
||||
implementation 'androidx.datastore:datastore-preferences:1.1.7'
|
||||
implementation 'androidx.test.ext:junit-ktx:1.3.0'
|
||||
implementation 'androidx.test.ext:junit-ktx:1.2.1'
|
||||
|
||||
implementation fileTree(include: ['*'], dir: 'libs')
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0"
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.7.1'
|
||||
implementation 'com.google.android.material:material:1.13.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
||||
implementation "com.vanniktech:emoji-google:0.21.0"
|
||||
implementation "androidx.emoji2:emoji2:${emojiVersion}"
|
||||
|
|
@ -218,7 +210,6 @@ dependencies {
|
|||
exclude group: 'org.ogce', module: 'xpp3' // Android comes with its own XmlPullParser
|
||||
})
|
||||
implementation 'org.conscrypt:conscrypt-android:2.5.3'
|
||||
implementation "com.github.nextcloud-deps:qrcodescanner:0.1.2.4" // "com.github.blikoon:QRCodeScanner:0.1.2"
|
||||
|
||||
implementation "androidx.camera:camera-core:${androidxCameraVersion}"
|
||||
implementation "androidx.camera:camera-camera2:${androidxCameraVersion}"
|
||||
|
|
@ -245,7 +236,7 @@ dependencies {
|
|||
implementation "com.squareup.okhttp3:logging-interceptor:${okhttpVersion}"
|
||||
|
||||
implementation 'com.bluelinelabs:logansquare:1.3.7'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-core:2.20.0'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-core:2.19.1'
|
||||
kapt 'com.bluelinelabs:logansquare-compiler:1.3.7'
|
||||
|
||||
implementation "com.squareup.retrofit2:retrofit:${retrofit2Version}"
|
||||
|
|
@ -260,7 +251,7 @@ dependencies {
|
|||
compileOnly 'javax.annotation:javax.annotation-api:1.3.2'
|
||||
|
||||
implementation 'org.greenrobot:eventbus:3.3.1'
|
||||
implementation 'net.zetetic:sqlcipher-android:4.10.0'
|
||||
implementation 'net.zetetic:sqlcipher-android:4.9.0'
|
||||
|
||||
implementation "androidx.room:room-runtime:${roomVersion}"
|
||||
implementation "androidx.room:room-rxjava2:${roomVersion}"
|
||||
|
|
@ -270,7 +261,7 @@ dependencies {
|
|||
implementation "org.parceler:parceler-api:$parcelerVersion"
|
||||
implementation 'com.github.ddB0515.FlexibleAdapter:flexible-adapter:5.1.1'
|
||||
implementation 'com.github.ddB0515.FlexibleAdapter:flexible-adapter-ui:5.1.1'
|
||||
implementation 'org.apache.commons:commons-lang3:3.18.0'
|
||||
implementation 'org.apache.commons:commons-lang3:3.17.0'
|
||||
implementation 'com.github.wooplr:Spotlight:1.3'
|
||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
implementation 'com.github.nextcloud-deps:ChatKit:0.4.2'
|
||||
|
|
@ -289,7 +280,7 @@ dependencies {
|
|||
implementation "com.afollestad.material-dialogs:bottomsheets:${materialDialogsVersion}"
|
||||
implementation "com.afollestad.material-dialogs:lifecycle:${materialDialogsVersion}"
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.13.2'
|
||||
implementation 'com.google.code.gson:gson:2.13.1'
|
||||
|
||||
implementation "androidx.media3:media3-exoplayer:$media3_version"
|
||||
implementation "androidx.media3:media3-ui:$media3_version"
|
||||
|
|
@ -312,14 +303,14 @@ dependencies {
|
|||
|
||||
implementation 'androidx.core:core-ktx:1.16.0'
|
||||
implementation 'androidx.activity:activity-ktx:1.10.1'
|
||||
implementation 'com.github.nextcloud.android-common:ui:0.28.0'
|
||||
implementation 'com.github.nextcloud.android-common:ui:0.27.0'
|
||||
implementation 'com.github.nextcloud-deps:android-talk-webrtc:132.6834.0'
|
||||
|
||||
gplayImplementation 'com.google.android.gms:play-services-base:18.8.0'
|
||||
gplayImplementation "com.google.firebase:firebase-messaging:25.0.0"
|
||||
gplayImplementation 'com.google.android.gms:play-services-base:18.6.0'
|
||||
gplayImplementation "com.google.firebase:firebase-messaging:24.1.2"
|
||||
|
||||
//compose
|
||||
implementation(platform("androidx.compose:compose-bom:2025.09.00"))
|
||||
implementation(platform("androidx.compose:compose-bom:2025.06.01"))
|
||||
implementation("androidx.compose.ui:ui")
|
||||
implementation 'androidx.compose.material3:material3:1.3.2'
|
||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||
|
|
@ -327,19 +318,18 @@ dependencies {
|
|||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
|
||||
//tests
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.13.4'
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.9.1")
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.8.3")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.mockito:mockito-core:5.19.0'
|
||||
testImplementation 'org.mockito:mockito-core:5.18.0'
|
||||
testImplementation 'androidx.arch.core:core-testing:2.2.0'
|
||||
|
||||
androidTestImplementation "androidx.test:core:1.7.0"
|
||||
androidTestImplementation "androidx.test:core:1.6.1"
|
||||
|
||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2"
|
||||
androidTestImplementation 'androidx.test:core-ktx:1.7.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:5.19.0'
|
||||
androidTestImplementation 'androidx.test:core-ktx:1.6.1'
|
||||
androidTestImplementation 'org.mockito:mockito-android:5.18.0'
|
||||
androidTestImplementation "androidx.work:work-testing:${workVersion}"
|
||||
// Espresso core
|
||||
androidTestImplementation ("androidx.test.espresso:espresso-core:$espressoVersion", {
|
||||
|
|
@ -353,15 +343,11 @@ dependencies {
|
|||
|
||||
androidTestImplementation('com.android.support.test.espresso:espresso-intents:3.0.2')
|
||||
|
||||
androidTestImplementation(platform("androidx.compose:compose-bom:2025.09.00"))
|
||||
androidTestImplementation(platform("androidx.compose:compose-bom:2025.06.01"))
|
||||
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
|
||||
|
||||
testImplementation 'org.junit.vintage:junit-vintage-engine:5.13.4' // DO NOT REMOVE
|
||||
testImplementation "androidx.room:room-testing:${roomVersion}"
|
||||
testImplementation("com.squareup.okhttp3:mockwebserver:$okhttpVersion")
|
||||
testImplementation("com.google.dagger:hilt-android-testing:2.57.1")
|
||||
testImplementation("org.robolectric:robolectric:4.16")
|
||||
testImplementation 'org.junit.vintage:junit-vintage-engine:5.13.3'
|
||||
}
|
||||
|
||||
tasks.register('installGitHooks', Copy) {
|
||||
|
|
@ -385,14 +371,14 @@ tasks.withType(SpotBugsTask).configureEach { task ->
|
|||
dependsOn "compile${variantNameCap}Sources"
|
||||
|
||||
excludeFilter = file("${project.rootDir}/spotbugs-filter.xml")
|
||||
classes = fileTree(layout.buildDirectory.get().asFile.toString()+"/intermediates/javac/${variantName}/compile${variantNameCap}JavaWithJavac/classes/")
|
||||
classes = fileTree("$project.buildDir/intermediates/javac/${variantName}/compile${variantNameCap}JavaWithJavac/classes/")
|
||||
reports {
|
||||
xml {
|
||||
required = true
|
||||
}
|
||||
html {
|
||||
required = true
|
||||
outputLocation = layout.buildDirectory.file("reports/spotbugs/spotbugs.html")
|
||||
outputLocation = file("$project.buildDir/reports/spotbugs/spotbugs.html")
|
||||
stylesheet = 'fancy.xsl'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,146 +1,709 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 10,
|
||||
"identityHash": "1b2dab0ea495c45c9c9ee6e64ba74039",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "User",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userId` TEXT, `username` TEXT, `baseUrl` TEXT, `token` TEXT, `displayName` TEXT, `pushConfigurationState` TEXT, `capabilities` TEXT, `serverVersion` TEXT DEFAULT '', `clientCertificate` TEXT, `externalSignalingServer` TEXT, `current` INTEGER NOT NULL, `scheduledForDeletion` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "userId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "username",
|
||||
"columnName": "username",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "baseUrl",
|
||||
"columnName": "baseUrl",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "pushConfigurationState",
|
||||
"columnName": "pushConfigurationState",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "capabilities",
|
||||
"columnName": "capabilities",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "serverVersion",
|
||||
"columnName": "serverVersion",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "clientCertificate",
|
||||
"columnName": "clientCertificate",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalSignalingServer",
|
||||
"columnName": "externalSignalingServer",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "current",
|
||||
"columnName": "current",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "scheduledForDeletion",
|
||||
"columnName": "scheduledForDeletion",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "ArbitraryStorage",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountIdentifier` INTEGER NOT NULL, `key` TEXT NOT NULL, `object` TEXT, `value` TEXT, PRIMARY KEY(`accountIdentifier`, `key`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "accountIdentifier",
|
||||
"columnName": "accountIdentifier",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "key",
|
||||
"columnName": "key",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "storageObject",
|
||||
"columnName": "object",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "value",
|
||||
"columnName": "value",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"accountIdentifier",
|
||||
"key"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 10,
|
||||
"identityHash": "c07a2543aa583e08e7b3208f44fcc7ac",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "User",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userId` TEXT, `username` TEXT, `baseUrl` TEXT, `token` TEXT, `displayName` TEXT, `pushConfigurationState` TEXT, `capabilities` TEXT, `serverVersion` TEXT DEFAULT '', `clientCertificate` TEXT, `externalSignalingServer` TEXT, `current` INTEGER NOT NULL, `scheduledForDeletion` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "userId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "username",
|
||||
"columnName": "username",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "baseUrl",
|
||||
"columnName": "baseUrl",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "pushConfigurationState",
|
||||
"columnName": "pushConfigurationState",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "capabilities",
|
||||
"columnName": "capabilities",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "serverVersion",
|
||||
"columnName": "serverVersion",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false,
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "clientCertificate",
|
||||
"columnName": "clientCertificate",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalSignalingServer",
|
||||
"columnName": "externalSignalingServer",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "current",
|
||||
"columnName": "current",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "scheduledForDeletion",
|
||||
"columnName": "scheduledForDeletion",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"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')"
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "ArbitraryStorage",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountIdentifier` INTEGER NOT NULL, `key` TEXT NOT NULL, `object` TEXT, `value` TEXT, PRIMARY KEY(`accountIdentifier`, `key`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "accountIdentifier",
|
||||
"columnName": "accountIdentifier",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "key",
|
||||
"columnName": "key",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "storageObject",
|
||||
"columnName": "object",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "value",
|
||||
"columnName": "value",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"accountIdentifier",
|
||||
"key"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "Conversations",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT, `name` TEXT, `displayName` TEXT, `description` TEXT, `type` TEXT, `lastPing` INTEGER NOT NULL, `participantType` TEXT, `hasPassword` INTEGER NOT NULL, `sessionId` TEXT, `actorId` TEXT, `actorType` TEXT, `isFavorite` INTEGER NOT NULL, `lastActivity` INTEGER NOT NULL, `unreadMessages` INTEGER NOT NULL, `unreadMention` INTEGER NOT NULL, `lastMessageJson` TEXT, `objectType` TEXT, `notificationLevel` TEXT, `readOnly` TEXT, `lobbyState` TEXT, `lobbyTimer` INTEGER, `lastReadMessage` INTEGER NOT NULL, `lastCommonReadMessage` INTEGER NOT NULL, `hasCall` INTEGER NOT NULL, `callFlag` INTEGER NOT NULL, `canStartCall` INTEGER NOT NULL, `canLeaveConversation` INTEGER, `canDeleteConversation` INTEGER, `unreadMentionDirect` INTEGER, `notificationCalls` INTEGER, `permissions` INTEGER NOT NULL, `messageExpiration` INTEGER NOT NULL, `status` TEXT, `statusIcon` TEXT, `statusMessage` TEXT, `statusClearAt` INTEGER, `callRecording` INTEGER NOT NULL, `avatarVersion` TEXT, `isCustomAvatar` INTEGER, `callStartTime` INTEGER, `recordingConsent` INTEGER NOT NULL, `remoteServer` TEXT, `remoteToken` TEXT, PRIMARY KEY(`internalId`), FOREIGN KEY(`accountId`) REFERENCES `User`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "internalId",
|
||||
"columnName": "internalId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "description",
|
||||
"columnName": "description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastPing",
|
||||
"columnName": "lastPing",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "participantType",
|
||||
"columnName": "participantType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasPassword",
|
||||
"columnName": "hasPassword",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "sessionId",
|
||||
"columnName": "sessionId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorId",
|
||||
"columnName": "actorId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorType",
|
||||
"columnName": "actorType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "favorite",
|
||||
"columnName": "isFavorite",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastActivity",
|
||||
"columnName": "lastActivity",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMessages",
|
||||
"columnName": "unreadMessages",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMention",
|
||||
"columnName": "unreadMention",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastMessageJson",
|
||||
"columnName": "lastMessageJson",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "objectType",
|
||||
"columnName": "objectType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "notificationLevel",
|
||||
"columnName": "notificationLevel",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "conversationReadOnlyState",
|
||||
"columnName": "readOnly",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lobbyState",
|
||||
"columnName": "lobbyState",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lobbyTimer",
|
||||
"columnName": "lobbyTimer",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastReadMessage",
|
||||
"columnName": "lastReadMessage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastCommonReadMessage",
|
||||
"columnName": "lastCommonReadMessage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCall",
|
||||
"columnName": "hasCall",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "callFlag",
|
||||
"columnName": "callFlag",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canStartCall",
|
||||
"columnName": "canStartCall",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canLeaveConversation",
|
||||
"columnName": "canLeaveConversation",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "canDeleteConversation",
|
||||
"columnName": "canDeleteConversation",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMentionDirect",
|
||||
"columnName": "unreadMentionDirect",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "notificationCalls",
|
||||
"columnName": "notificationCalls",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "permissions",
|
||||
"columnName": "permissions",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageExpiration",
|
||||
"columnName": "messageExpiration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "status",
|
||||
"columnName": "status",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusIcon",
|
||||
"columnName": "statusIcon",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusMessage",
|
||||
"columnName": "statusMessage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusClearAt",
|
||||
"columnName": "statusClearAt",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "callRecording",
|
||||
"columnName": "callRecording",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "avatarVersion",
|
||||
"columnName": "avatarVersion",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCustomAvatar",
|
||||
"columnName": "isCustomAvatar",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "callStartTime",
|
||||
"columnName": "callStartTime",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "recordingConsentRequired",
|
||||
"columnName": "recordingConsent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "remoteServer",
|
||||
"columnName": "remoteServer",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "remoteToken",
|
||||
"columnName": "remoteToken",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_Conversations_accountId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"accountId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_Conversations_accountId` ON `${TABLE_NAME}` (`accountId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "User",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"accountId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "ChatMessages",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER, `token` TEXT, `id` INTEGER NOT NULL, `internalConversationId` TEXT, `actorType` TEXT, `actorId` TEXT, `actorDisplayName` TEXT, `timestamp` INTEGER NOT NULL, `systemMessage` TEXT, `messageType` TEXT, `isReplyable` INTEGER NOT NULL, `message` TEXT, `messageParameters` TEXT, `expirationTimestamp` INTEGER NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `markdown` INTEGER, `lastEditActorType` TEXT, `lastEditActorId` TEXT, `lastEditActorDisplayName` TEXT, `lastEditTimestamp` INTEGER, `deleted` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "internalId",
|
||||
"columnName": "internalId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "internalConversationId",
|
||||
"columnName": "internalConversationId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorType",
|
||||
"columnName": "actorType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorId",
|
||||
"columnName": "actorId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorDisplayName",
|
||||
"columnName": "actorDisplayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "timestamp",
|
||||
"columnName": "timestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "systemMessageType",
|
||||
"columnName": "systemMessage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageType",
|
||||
"columnName": "messageType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "replyable",
|
||||
"columnName": "isReplyable",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageParameters",
|
||||
"columnName": "messageParameters",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "expirationTimestamp",
|
||||
"columnName": "expirationTimestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "parentMessageId",
|
||||
"columnName": "parent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "reactions",
|
||||
"columnName": "reactions",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "reactionsSelf",
|
||||
"columnName": "reactionsSelf",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "renderMarkdown",
|
||||
"columnName": "markdown",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorType",
|
||||
"columnName": "lastEditActorType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorId",
|
||||
"columnName": "lastEditActorId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorDisplayName",
|
||||
"columnName": "lastEditActorDisplayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditTimestamp",
|
||||
"columnName": "lastEditTimestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "deleted",
|
||||
"columnName": "deleted",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_ChatMessages_internalId",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ChatMessages_internalId` ON `${TABLE_NAME}` (`internalId`)"
|
||||
},
|
||||
{
|
||||
"name": "index_ChatMessages_internalConversationId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_ChatMessages_internalConversationId` ON `${TABLE_NAME}` (`internalConversationId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "Conversations",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"internalId"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "ChatBlocks",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `internalConversationId` TEXT NOT NULL, `accountId` INTEGER, `token` TEXT, `oldestMessageId` INTEGER NOT NULL, `newestMessageId` INTEGER NOT NULL, `hasHistory` INTEGER NOT NULL, FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "internalConversationId",
|
||||
"columnName": "internalConversationId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "oldestMessageId",
|
||||
"columnName": "oldestMessageId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "newestMessageId",
|
||||
"columnName": "newestMessageId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasHistory",
|
||||
"columnName": "hasHistory",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "Conversations",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"internalId"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c07a2543aa583e08e7b3208f44fcc7ac')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,746 +0,0 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 18,
|
||||
"identityHash": "c5e3716925065d7419fb23efabf6691f",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "User",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userId` TEXT, `username` TEXT, `baseUrl` TEXT, `token` TEXT, `displayName` TEXT, `pushConfigurationState` TEXT, `capabilities` TEXT, `serverVersion` TEXT DEFAULT '', `clientCertificate` TEXT, `externalSignalingServer` TEXT, `current` INTEGER NOT NULL, `scheduledForDeletion` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "userId",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "username",
|
||||
"columnName": "username",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "baseUrl",
|
||||
"columnName": "baseUrl",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pushConfigurationState",
|
||||
"columnName": "pushConfigurationState",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "capabilities",
|
||||
"columnName": "capabilities",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "serverVersion",
|
||||
"columnName": "serverVersion",
|
||||
"affinity": "TEXT",
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "clientCertificate",
|
||||
"columnName": "clientCertificate",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalSignalingServer",
|
||||
"columnName": "externalSignalingServer",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "current",
|
||||
"columnName": "current",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "scheduledForDeletion",
|
||||
"columnName": "scheduledForDeletion",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "ArbitraryStorage",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountIdentifier` INTEGER NOT NULL, `key` TEXT NOT NULL, `object` TEXT, `value` TEXT, PRIMARY KEY(`accountIdentifier`, `key`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "accountIdentifier",
|
||||
"columnName": "accountIdentifier",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "key",
|
||||
"columnName": "key",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "storageObject",
|
||||
"columnName": "object",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "value",
|
||||
"columnName": "value",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"accountIdentifier",
|
||||
"key"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "Conversations",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `displayName` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `avatarVersion` TEXT NOT NULL, `callFlag` INTEGER NOT NULL, `callRecording` INTEGER NOT NULL, `callStartTime` INTEGER NOT NULL, `canDeleteConversation` INTEGER NOT NULL, `canLeaveConversation` INTEGER NOT NULL, `canStartCall` INTEGER NOT NULL, `description` TEXT NOT NULL, `hasCall` INTEGER NOT NULL, `hasPassword` INTEGER NOT NULL, `isCustomAvatar` INTEGER NOT NULL, `isFavorite` INTEGER NOT NULL, `lastActivity` INTEGER NOT NULL, `lastCommonReadMessage` INTEGER NOT NULL, `lastMessage` TEXT, `lastPing` INTEGER NOT NULL, `lastReadMessage` INTEGER NOT NULL, `lobbyState` TEXT NOT NULL, `lobbyTimer` INTEGER NOT NULL, `messageExpiration` INTEGER NOT NULL, `name` TEXT NOT NULL, `notificationCalls` INTEGER NOT NULL, `notificationLevel` TEXT NOT NULL, `objectType` TEXT NOT NULL, `objectId` TEXT NOT NULL, `participantType` TEXT NOT NULL, `permissions` INTEGER NOT NULL, `readOnly` TEXT NOT NULL, `recordingConsent` INTEGER NOT NULL, `remoteServer` TEXT, `remoteToken` TEXT, `sessionId` TEXT NOT NULL, `status` TEXT, `statusClearAt` INTEGER, `statusIcon` TEXT, `statusMessage` TEXT, `type` TEXT NOT NULL, `unreadMention` INTEGER NOT NULL, `unreadMentionDirect` INTEGER NOT NULL, `unreadMessages` INTEGER NOT NULL, `hasArchived` INTEGER NOT NULL, `hasSensitive` INTEGER NOT NULL, `hasImportant` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`accountId`) REFERENCES `User`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "internalId",
|
||||
"columnName": "internalId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorId",
|
||||
"columnName": "actorId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorType",
|
||||
"columnName": "actorType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "avatarVersion",
|
||||
"columnName": "avatarVersion",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "callFlag",
|
||||
"columnName": "callFlag",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "callRecording",
|
||||
"columnName": "callRecording",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "callStartTime",
|
||||
"columnName": "callStartTime",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canDeleteConversation",
|
||||
"columnName": "canDeleteConversation",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canLeaveConversation",
|
||||
"columnName": "canLeaveConversation",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canStartCall",
|
||||
"columnName": "canStartCall",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "description",
|
||||
"columnName": "description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCall",
|
||||
"columnName": "hasCall",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasPassword",
|
||||
"columnName": "hasPassword",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCustomAvatar",
|
||||
"columnName": "isCustomAvatar",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "favorite",
|
||||
"columnName": "isFavorite",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastActivity",
|
||||
"columnName": "lastActivity",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastCommonReadMessage",
|
||||
"columnName": "lastCommonReadMessage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastMessage",
|
||||
"columnName": "lastMessage",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastPing",
|
||||
"columnName": "lastPing",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastReadMessage",
|
||||
"columnName": "lastReadMessage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lobbyState",
|
||||
"columnName": "lobbyState",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lobbyTimer",
|
||||
"columnName": "lobbyTimer",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageExpiration",
|
||||
"columnName": "messageExpiration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "notificationCalls",
|
||||
"columnName": "notificationCalls",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "notificationLevel",
|
||||
"columnName": "notificationLevel",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "objectType",
|
||||
"columnName": "objectType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "objectId",
|
||||
"columnName": "objectId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "participantType",
|
||||
"columnName": "participantType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "permissions",
|
||||
"columnName": "permissions",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "conversationReadOnlyState",
|
||||
"columnName": "readOnly",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "recordingConsentRequired",
|
||||
"columnName": "recordingConsent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "remoteServer",
|
||||
"columnName": "remoteServer",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "remoteToken",
|
||||
"columnName": "remoteToken",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "sessionId",
|
||||
"columnName": "sessionId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "status",
|
||||
"columnName": "status",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusClearAt",
|
||||
"columnName": "statusClearAt",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusIcon",
|
||||
"columnName": "statusIcon",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusMessage",
|
||||
"columnName": "statusMessage",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMention",
|
||||
"columnName": "unreadMention",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMentionDirect",
|
||||
"columnName": "unreadMentionDirect",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMessages",
|
||||
"columnName": "unreadMessages",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasArchived",
|
||||
"columnName": "hasArchived",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasSensitive",
|
||||
"columnName": "hasSensitive",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasImportant",
|
||||
"columnName": "hasImportant",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_Conversations_accountId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"accountId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_Conversations_accountId` ON `${TABLE_NAME}` (`accountId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "User",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"accountId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "ChatMessages",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `id` INTEGER NOT NULL, `internalConversationId` TEXT NOT NULL, `threadId` INTEGER, `isThread` INTEGER NOT NULL, `actorDisplayName` TEXT NOT NULL, `message` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `expirationTimestamp` INTEGER NOT NULL, `isReplyable` INTEGER NOT NULL, `isTemporary` INTEGER NOT NULL, `lastEditActorDisplayName` TEXT, `lastEditActorId` TEXT, `lastEditActorType` TEXT, `lastEditTimestamp` INTEGER, `markdown` INTEGER, `messageParameters` TEXT, `messageType` TEXT NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `referenceId` TEXT, `sendStatus` TEXT, `silent` INTEGER NOT NULL, `systemMessage` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "internalId",
|
||||
"columnName": "internalId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "internalConversationId",
|
||||
"columnName": "internalConversationId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "threadId",
|
||||
"columnName": "threadId",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isThread",
|
||||
"columnName": "isThread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorDisplayName",
|
||||
"columnName": "actorDisplayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorId",
|
||||
"columnName": "actorId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorType",
|
||||
"columnName": "actorType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "deleted",
|
||||
"columnName": "deleted",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "expirationTimestamp",
|
||||
"columnName": "expirationTimestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "replyable",
|
||||
"columnName": "isReplyable",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTemporary",
|
||||
"columnName": "isTemporary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorDisplayName",
|
||||
"columnName": "lastEditActorDisplayName",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorId",
|
||||
"columnName": "lastEditActorId",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorType",
|
||||
"columnName": "lastEditActorType",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditTimestamp",
|
||||
"columnName": "lastEditTimestamp",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "renderMarkdown",
|
||||
"columnName": "markdown",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageParameters",
|
||||
"columnName": "messageParameters",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageType",
|
||||
"columnName": "messageType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "parentMessageId",
|
||||
"columnName": "parent",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "reactions",
|
||||
"columnName": "reactions",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "reactionsSelf",
|
||||
"columnName": "reactionsSelf",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "referenceId",
|
||||
"columnName": "referenceId",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "sendStatus",
|
||||
"columnName": "sendStatus",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "silent",
|
||||
"columnName": "silent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "systemMessageType",
|
||||
"columnName": "systemMessage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "timestamp",
|
||||
"columnName": "timestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_ChatMessages_internalId",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ChatMessages_internalId` ON `${TABLE_NAME}` (`internalId`)"
|
||||
},
|
||||
{
|
||||
"name": "index_ChatMessages_internalConversationId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_ChatMessages_internalConversationId` ON `${TABLE_NAME}` (`internalConversationId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "Conversations",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"internalId"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "ChatBlocks",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `internalConversationId` TEXT NOT NULL, `accountId` INTEGER, `token` TEXT, `threadId` INTEGER, `oldestMessageId` INTEGER NOT NULL, `newestMessageId` INTEGER NOT NULL, `hasHistory` INTEGER NOT NULL, FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "internalConversationId",
|
||||
"columnName": "internalConversationId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "threadId",
|
||||
"columnName": "threadId",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "oldestMessageId",
|
||||
"columnName": "oldestMessageId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "newestMessageId",
|
||||
"columnName": "newestMessageId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasHistory",
|
||||
"columnName": "hasHistory",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_ChatBlocks_internalConversationId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_ChatBlocks_internalConversationId` ON `${TABLE_NAME}` (`internalConversationId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "Conversations",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"internalId"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"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, 'c5e3716925065d7419fb23efabf6691f')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,746 +0,0 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 19,
|
||||
"identityHash": "c5e3716925065d7419fb23efabf6691f",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "User",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userId` TEXT, `username` TEXT, `baseUrl` TEXT, `token` TEXT, `displayName` TEXT, `pushConfigurationState` TEXT, `capabilities` TEXT, `serverVersion` TEXT DEFAULT '', `clientCertificate` TEXT, `externalSignalingServer` TEXT, `current` INTEGER NOT NULL, `scheduledForDeletion` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "userId",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "username",
|
||||
"columnName": "username",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "baseUrl",
|
||||
"columnName": "baseUrl",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pushConfigurationState",
|
||||
"columnName": "pushConfigurationState",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "capabilities",
|
||||
"columnName": "capabilities",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "serverVersion",
|
||||
"columnName": "serverVersion",
|
||||
"affinity": "TEXT",
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "clientCertificate",
|
||||
"columnName": "clientCertificate",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalSignalingServer",
|
||||
"columnName": "externalSignalingServer",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "current",
|
||||
"columnName": "current",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "scheduledForDeletion",
|
||||
"columnName": "scheduledForDeletion",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "ArbitraryStorage",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountIdentifier` INTEGER NOT NULL, `key` TEXT NOT NULL, `object` TEXT, `value` TEXT, PRIMARY KEY(`accountIdentifier`, `key`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "accountIdentifier",
|
||||
"columnName": "accountIdentifier",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "key",
|
||||
"columnName": "key",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "storageObject",
|
||||
"columnName": "object",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "value",
|
||||
"columnName": "value",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"accountIdentifier",
|
||||
"key"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "Conversations",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `displayName` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `avatarVersion` TEXT NOT NULL, `callFlag` INTEGER NOT NULL, `callRecording` INTEGER NOT NULL, `callStartTime` INTEGER NOT NULL, `canDeleteConversation` INTEGER NOT NULL, `canLeaveConversation` INTEGER NOT NULL, `canStartCall` INTEGER NOT NULL, `description` TEXT NOT NULL, `hasCall` INTEGER NOT NULL, `hasPassword` INTEGER NOT NULL, `isCustomAvatar` INTEGER NOT NULL, `isFavorite` INTEGER NOT NULL, `lastActivity` INTEGER NOT NULL, `lastCommonReadMessage` INTEGER NOT NULL, `lastMessage` TEXT, `lastPing` INTEGER NOT NULL, `lastReadMessage` INTEGER NOT NULL, `lobbyState` TEXT NOT NULL, `lobbyTimer` INTEGER NOT NULL, `messageExpiration` INTEGER NOT NULL, `name` TEXT NOT NULL, `notificationCalls` INTEGER NOT NULL, `notificationLevel` TEXT NOT NULL, `objectType` TEXT NOT NULL, `objectId` TEXT NOT NULL, `participantType` TEXT NOT NULL, `permissions` INTEGER NOT NULL, `readOnly` TEXT NOT NULL, `recordingConsent` INTEGER NOT NULL, `remoteServer` TEXT, `remoteToken` TEXT, `sessionId` TEXT NOT NULL, `status` TEXT, `statusClearAt` INTEGER, `statusIcon` TEXT, `statusMessage` TEXT, `type` TEXT NOT NULL, `unreadMention` INTEGER NOT NULL, `unreadMentionDirect` INTEGER NOT NULL, `unreadMessages` INTEGER NOT NULL, `hasArchived` INTEGER NOT NULL, `hasSensitive` INTEGER NOT NULL, `hasImportant` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`accountId`) REFERENCES `User`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "internalId",
|
||||
"columnName": "internalId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorId",
|
||||
"columnName": "actorId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorType",
|
||||
"columnName": "actorType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "avatarVersion",
|
||||
"columnName": "avatarVersion",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "callFlag",
|
||||
"columnName": "callFlag",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "callRecording",
|
||||
"columnName": "callRecording",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "callStartTime",
|
||||
"columnName": "callStartTime",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canDeleteConversation",
|
||||
"columnName": "canDeleteConversation",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canLeaveConversation",
|
||||
"columnName": "canLeaveConversation",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canStartCall",
|
||||
"columnName": "canStartCall",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "description",
|
||||
"columnName": "description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCall",
|
||||
"columnName": "hasCall",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasPassword",
|
||||
"columnName": "hasPassword",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCustomAvatar",
|
||||
"columnName": "isCustomAvatar",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "favorite",
|
||||
"columnName": "isFavorite",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastActivity",
|
||||
"columnName": "lastActivity",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastCommonReadMessage",
|
||||
"columnName": "lastCommonReadMessage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastMessage",
|
||||
"columnName": "lastMessage",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastPing",
|
||||
"columnName": "lastPing",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastReadMessage",
|
||||
"columnName": "lastReadMessage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lobbyState",
|
||||
"columnName": "lobbyState",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lobbyTimer",
|
||||
"columnName": "lobbyTimer",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageExpiration",
|
||||
"columnName": "messageExpiration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "notificationCalls",
|
||||
"columnName": "notificationCalls",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "notificationLevel",
|
||||
"columnName": "notificationLevel",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "objectType",
|
||||
"columnName": "objectType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "objectId",
|
||||
"columnName": "objectId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "participantType",
|
||||
"columnName": "participantType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "permissions",
|
||||
"columnName": "permissions",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "conversationReadOnlyState",
|
||||
"columnName": "readOnly",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "recordingConsentRequired",
|
||||
"columnName": "recordingConsent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "remoteServer",
|
||||
"columnName": "remoteServer",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "remoteToken",
|
||||
"columnName": "remoteToken",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "sessionId",
|
||||
"columnName": "sessionId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "status",
|
||||
"columnName": "status",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusClearAt",
|
||||
"columnName": "statusClearAt",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusIcon",
|
||||
"columnName": "statusIcon",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusMessage",
|
||||
"columnName": "statusMessage",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMention",
|
||||
"columnName": "unreadMention",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMentionDirect",
|
||||
"columnName": "unreadMentionDirect",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMessages",
|
||||
"columnName": "unreadMessages",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasArchived",
|
||||
"columnName": "hasArchived",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasSensitive",
|
||||
"columnName": "hasSensitive",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasImportant",
|
||||
"columnName": "hasImportant",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_Conversations_accountId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"accountId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_Conversations_accountId` ON `${TABLE_NAME}` (`accountId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "User",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"accountId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "ChatMessages",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `id` INTEGER NOT NULL, `internalConversationId` TEXT NOT NULL, `threadId` INTEGER, `isThread` INTEGER NOT NULL, `actorDisplayName` TEXT NOT NULL, `message` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `expirationTimestamp` INTEGER NOT NULL, `isReplyable` INTEGER NOT NULL, `isTemporary` INTEGER NOT NULL, `lastEditActorDisplayName` TEXT, `lastEditActorId` TEXT, `lastEditActorType` TEXT, `lastEditTimestamp` INTEGER, `markdown` INTEGER, `messageParameters` TEXT, `messageType` TEXT NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `referenceId` TEXT, `sendStatus` TEXT, `silent` INTEGER NOT NULL, `systemMessage` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "internalId",
|
||||
"columnName": "internalId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "internalConversationId",
|
||||
"columnName": "internalConversationId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "threadId",
|
||||
"columnName": "threadId",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isThread",
|
||||
"columnName": "isThread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorDisplayName",
|
||||
"columnName": "actorDisplayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorId",
|
||||
"columnName": "actorId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorType",
|
||||
"columnName": "actorType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "deleted",
|
||||
"columnName": "deleted",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "expirationTimestamp",
|
||||
"columnName": "expirationTimestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "replyable",
|
||||
"columnName": "isReplyable",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTemporary",
|
||||
"columnName": "isTemporary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorDisplayName",
|
||||
"columnName": "lastEditActorDisplayName",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorId",
|
||||
"columnName": "lastEditActorId",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorType",
|
||||
"columnName": "lastEditActorType",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditTimestamp",
|
||||
"columnName": "lastEditTimestamp",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "renderMarkdown",
|
||||
"columnName": "markdown",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageParameters",
|
||||
"columnName": "messageParameters",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageType",
|
||||
"columnName": "messageType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "parentMessageId",
|
||||
"columnName": "parent",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "reactions",
|
||||
"columnName": "reactions",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "reactionsSelf",
|
||||
"columnName": "reactionsSelf",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "referenceId",
|
||||
"columnName": "referenceId",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "sendStatus",
|
||||
"columnName": "sendStatus",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "silent",
|
||||
"columnName": "silent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "systemMessageType",
|
||||
"columnName": "systemMessage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "timestamp",
|
||||
"columnName": "timestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_ChatMessages_internalId",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ChatMessages_internalId` ON `${TABLE_NAME}` (`internalId`)"
|
||||
},
|
||||
{
|
||||
"name": "index_ChatMessages_internalConversationId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_ChatMessages_internalConversationId` ON `${TABLE_NAME}` (`internalConversationId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "Conversations",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"internalId"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "ChatBlocks",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `internalConversationId` TEXT NOT NULL, `accountId` INTEGER, `token` TEXT, `threadId` INTEGER, `oldestMessageId` INTEGER NOT NULL, `newestMessageId` INTEGER NOT NULL, `hasHistory` INTEGER NOT NULL, FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "internalConversationId",
|
||||
"columnName": "internalConversationId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "threadId",
|
||||
"columnName": "threadId",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "oldestMessageId",
|
||||
"columnName": "oldestMessageId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "newestMessageId",
|
||||
"columnName": "newestMessageId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasHistory",
|
||||
"columnName": "hasHistory",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_ChatBlocks_internalConversationId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_ChatBlocks_internalConversationId` ON `${TABLE_NAME}` (`internalConversationId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "Conversations",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"internalId"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"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, 'c5e3716925065d7419fb23efabf6691f')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,751 +0,0 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 20,
|
||||
"identityHash": "7330dad871a0b42e36931ffe8c7d4bcf",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "User",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userId` TEXT, `username` TEXT, `baseUrl` TEXT, `token` TEXT, `displayName` TEXT, `pushConfigurationState` TEXT, `capabilities` TEXT, `serverVersion` TEXT DEFAULT '', `clientCertificate` TEXT, `externalSignalingServer` TEXT, `current` INTEGER NOT NULL, `scheduledForDeletion` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "userId",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "username",
|
||||
"columnName": "username",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "baseUrl",
|
||||
"columnName": "baseUrl",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pushConfigurationState",
|
||||
"columnName": "pushConfigurationState",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "capabilities",
|
||||
"columnName": "capabilities",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "serverVersion",
|
||||
"columnName": "serverVersion",
|
||||
"affinity": "TEXT",
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "clientCertificate",
|
||||
"columnName": "clientCertificate",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalSignalingServer",
|
||||
"columnName": "externalSignalingServer",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "current",
|
||||
"columnName": "current",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "scheduledForDeletion",
|
||||
"columnName": "scheduledForDeletion",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "ArbitraryStorage",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountIdentifier` INTEGER NOT NULL, `key` TEXT NOT NULL, `object` TEXT, `value` TEXT, PRIMARY KEY(`accountIdentifier`, `key`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "accountIdentifier",
|
||||
"columnName": "accountIdentifier",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "key",
|
||||
"columnName": "key",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "storageObject",
|
||||
"columnName": "object",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "value",
|
||||
"columnName": "value",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"accountIdentifier",
|
||||
"key"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "Conversations",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `displayName` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `avatarVersion` TEXT NOT NULL, `callFlag` INTEGER NOT NULL, `callRecording` INTEGER NOT NULL, `callStartTime` INTEGER NOT NULL, `canDeleteConversation` INTEGER NOT NULL, `canLeaveConversation` INTEGER NOT NULL, `canStartCall` INTEGER NOT NULL, `description` TEXT NOT NULL, `hasCall` INTEGER NOT NULL, `hasPassword` INTEGER NOT NULL, `isCustomAvatar` INTEGER NOT NULL, `isFavorite` INTEGER NOT NULL, `lastActivity` INTEGER NOT NULL, `lastCommonReadMessage` INTEGER NOT NULL, `lastMessage` TEXT, `lastPing` INTEGER NOT NULL, `lastReadMessage` INTEGER NOT NULL, `lobbyState` TEXT NOT NULL, `lobbyTimer` INTEGER NOT NULL, `messageExpiration` INTEGER NOT NULL, `name` TEXT NOT NULL, `notificationCalls` INTEGER NOT NULL, `notificationLevel` TEXT NOT NULL, `objectType` TEXT NOT NULL, `objectId` TEXT NOT NULL, `participantType` TEXT NOT NULL, `permissions` INTEGER NOT NULL, `readOnly` TEXT NOT NULL, `recordingConsent` INTEGER NOT NULL, `remoteServer` TEXT, `remoteToken` TEXT, `sessionId` TEXT NOT NULL, `status` TEXT, `statusClearAt` INTEGER, `statusIcon` TEXT, `statusMessage` TEXT, `type` TEXT NOT NULL, `unreadMention` INTEGER NOT NULL, `unreadMentionDirect` INTEGER NOT NULL, `unreadMessages` INTEGER NOT NULL, `hasArchived` INTEGER NOT NULL, `hasSensitive` INTEGER NOT NULL, `hasImportant` INTEGER NOT NULL, `messageDraft` TEXT, PRIMARY KEY(`internalId`), FOREIGN KEY(`accountId`) REFERENCES `User`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "internalId",
|
||||
"columnName": "internalId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorId",
|
||||
"columnName": "actorId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorType",
|
||||
"columnName": "actorType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "avatarVersion",
|
||||
"columnName": "avatarVersion",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "callFlag",
|
||||
"columnName": "callFlag",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "callRecording",
|
||||
"columnName": "callRecording",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "callStartTime",
|
||||
"columnName": "callStartTime",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canDeleteConversation",
|
||||
"columnName": "canDeleteConversation",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canLeaveConversation",
|
||||
"columnName": "canLeaveConversation",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canStartCall",
|
||||
"columnName": "canStartCall",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "description",
|
||||
"columnName": "description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCall",
|
||||
"columnName": "hasCall",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasPassword",
|
||||
"columnName": "hasPassword",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCustomAvatar",
|
||||
"columnName": "isCustomAvatar",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "favorite",
|
||||
"columnName": "isFavorite",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastActivity",
|
||||
"columnName": "lastActivity",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastCommonReadMessage",
|
||||
"columnName": "lastCommonReadMessage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastMessage",
|
||||
"columnName": "lastMessage",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastPing",
|
||||
"columnName": "lastPing",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastReadMessage",
|
||||
"columnName": "lastReadMessage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lobbyState",
|
||||
"columnName": "lobbyState",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lobbyTimer",
|
||||
"columnName": "lobbyTimer",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageExpiration",
|
||||
"columnName": "messageExpiration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "notificationCalls",
|
||||
"columnName": "notificationCalls",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "notificationLevel",
|
||||
"columnName": "notificationLevel",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "objectType",
|
||||
"columnName": "objectType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "objectId",
|
||||
"columnName": "objectId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "participantType",
|
||||
"columnName": "participantType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "permissions",
|
||||
"columnName": "permissions",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "conversationReadOnlyState",
|
||||
"columnName": "readOnly",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "recordingConsentRequired",
|
||||
"columnName": "recordingConsent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "remoteServer",
|
||||
"columnName": "remoteServer",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "remoteToken",
|
||||
"columnName": "remoteToken",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "sessionId",
|
||||
"columnName": "sessionId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "status",
|
||||
"columnName": "status",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusClearAt",
|
||||
"columnName": "statusClearAt",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusIcon",
|
||||
"columnName": "statusIcon",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusMessage",
|
||||
"columnName": "statusMessage",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMention",
|
||||
"columnName": "unreadMention",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMentionDirect",
|
||||
"columnName": "unreadMentionDirect",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMessages",
|
||||
"columnName": "unreadMessages",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasArchived",
|
||||
"columnName": "hasArchived",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasSensitive",
|
||||
"columnName": "hasSensitive",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasImportant",
|
||||
"columnName": "hasImportant",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageDraft",
|
||||
"columnName": "messageDraft",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_Conversations_accountId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"accountId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_Conversations_accountId` ON `${TABLE_NAME}` (`accountId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "User",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"accountId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "ChatMessages",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `id` INTEGER NOT NULL, `internalConversationId` TEXT NOT NULL, `threadId` INTEGER, `isThread` INTEGER NOT NULL, `actorDisplayName` TEXT NOT NULL, `message` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `expirationTimestamp` INTEGER NOT NULL, `isReplyable` INTEGER NOT NULL, `isTemporary` INTEGER NOT NULL, `lastEditActorDisplayName` TEXT, `lastEditActorId` TEXT, `lastEditActorType` TEXT, `lastEditTimestamp` INTEGER, `markdown` INTEGER, `messageParameters` TEXT, `messageType` TEXT NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `referenceId` TEXT, `sendStatus` TEXT, `silent` INTEGER NOT NULL, `systemMessage` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "internalId",
|
||||
"columnName": "internalId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "internalConversationId",
|
||||
"columnName": "internalConversationId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "threadId",
|
||||
"columnName": "threadId",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isThread",
|
||||
"columnName": "isThread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorDisplayName",
|
||||
"columnName": "actorDisplayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorId",
|
||||
"columnName": "actorId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorType",
|
||||
"columnName": "actorType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "deleted",
|
||||
"columnName": "deleted",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "expirationTimestamp",
|
||||
"columnName": "expirationTimestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "replyable",
|
||||
"columnName": "isReplyable",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTemporary",
|
||||
"columnName": "isTemporary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorDisplayName",
|
||||
"columnName": "lastEditActorDisplayName",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorId",
|
||||
"columnName": "lastEditActorId",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorType",
|
||||
"columnName": "lastEditActorType",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditTimestamp",
|
||||
"columnName": "lastEditTimestamp",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "renderMarkdown",
|
||||
"columnName": "markdown",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageParameters",
|
||||
"columnName": "messageParameters",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageType",
|
||||
"columnName": "messageType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "parentMessageId",
|
||||
"columnName": "parent",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "reactions",
|
||||
"columnName": "reactions",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "reactionsSelf",
|
||||
"columnName": "reactionsSelf",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "referenceId",
|
||||
"columnName": "referenceId",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "sendStatus",
|
||||
"columnName": "sendStatus",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "silent",
|
||||
"columnName": "silent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "systemMessageType",
|
||||
"columnName": "systemMessage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "timestamp",
|
||||
"columnName": "timestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_ChatMessages_internalId",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ChatMessages_internalId` ON `${TABLE_NAME}` (`internalId`)"
|
||||
},
|
||||
{
|
||||
"name": "index_ChatMessages_internalConversationId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_ChatMessages_internalConversationId` ON `${TABLE_NAME}` (`internalConversationId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "Conversations",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"internalId"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "ChatBlocks",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `internalConversationId` TEXT NOT NULL, `accountId` INTEGER, `token` TEXT, `threadId` INTEGER, `oldestMessageId` INTEGER NOT NULL, `newestMessageId` INTEGER NOT NULL, `hasHistory` INTEGER NOT NULL, FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "internalConversationId",
|
||||
"columnName": "internalConversationId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "threadId",
|
||||
"columnName": "threadId",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "oldestMessageId",
|
||||
"columnName": "oldestMessageId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "newestMessageId",
|
||||
"columnName": "newestMessageId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasHistory",
|
||||
"columnName": "hasHistory",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_ChatBlocks_internalConversationId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_ChatBlocks_internalConversationId` ON `${TABLE_NAME}` (`internalConversationId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "Conversations",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"internalId"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"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, '7330dad871a0b42e36931ffe8c7d4bcf')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,761 +0,0 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 21,
|
||||
"identityHash": "8077a29304b3d28882e4b37fb10d0081",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "User",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userId` TEXT, `username` TEXT, `baseUrl` TEXT, `token` TEXT, `displayName` TEXT, `pushConfigurationState` TEXT, `capabilities` TEXT, `serverVersion` TEXT DEFAULT '', `clientCertificate` TEXT, `externalSignalingServer` TEXT, `current` INTEGER NOT NULL, `scheduledForDeletion` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "userId",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "username",
|
||||
"columnName": "username",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "baseUrl",
|
||||
"columnName": "baseUrl",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "pushConfigurationState",
|
||||
"columnName": "pushConfigurationState",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "capabilities",
|
||||
"columnName": "capabilities",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "serverVersion",
|
||||
"columnName": "serverVersion",
|
||||
"affinity": "TEXT",
|
||||
"defaultValue": "''"
|
||||
},
|
||||
{
|
||||
"fieldPath": "clientCertificate",
|
||||
"columnName": "clientCertificate",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "externalSignalingServer",
|
||||
"columnName": "externalSignalingServer",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "current",
|
||||
"columnName": "current",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "scheduledForDeletion",
|
||||
"columnName": "scheduledForDeletion",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "ArbitraryStorage",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountIdentifier` INTEGER NOT NULL, `key` TEXT NOT NULL, `object` TEXT, `value` TEXT, PRIMARY KEY(`accountIdentifier`, `key`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "accountIdentifier",
|
||||
"columnName": "accountIdentifier",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "key",
|
||||
"columnName": "key",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "storageObject",
|
||||
"columnName": "object",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "value",
|
||||
"columnName": "value",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"accountIdentifier",
|
||||
"key"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"tableName": "Conversations",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `displayName` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `avatarVersion` TEXT NOT NULL, `callFlag` INTEGER NOT NULL, `callRecording` INTEGER NOT NULL, `callStartTime` INTEGER NOT NULL, `canDeleteConversation` INTEGER NOT NULL, `canLeaveConversation` INTEGER NOT NULL, `canStartCall` INTEGER NOT NULL, `description` TEXT NOT NULL, `hasCall` INTEGER NOT NULL, `hasPassword` INTEGER NOT NULL, `isCustomAvatar` INTEGER NOT NULL, `isFavorite` INTEGER NOT NULL, `lastActivity` INTEGER NOT NULL, `lastCommonReadMessage` INTEGER NOT NULL, `lastMessage` TEXT, `lastPing` INTEGER NOT NULL, `lastReadMessage` INTEGER NOT NULL, `lobbyState` TEXT NOT NULL, `lobbyTimer` INTEGER NOT NULL, `messageExpiration` INTEGER NOT NULL, `name` TEXT NOT NULL, `notificationCalls` INTEGER NOT NULL, `notificationLevel` TEXT NOT NULL, `objectType` TEXT NOT NULL, `objectId` TEXT NOT NULL, `participantType` TEXT NOT NULL, `permissions` INTEGER NOT NULL, `readOnly` TEXT NOT NULL, `recordingConsent` INTEGER NOT NULL, `remoteServer` TEXT, `remoteToken` TEXT, `sessionId` TEXT NOT NULL, `status` TEXT, `statusClearAt` INTEGER, `statusIcon` TEXT, `statusMessage` TEXT, `type` TEXT NOT NULL, `unreadMention` INTEGER NOT NULL, `unreadMentionDirect` INTEGER NOT NULL, `unreadMessages` INTEGER NOT NULL, `hasArchived` INTEGER NOT NULL, `hasSensitive` INTEGER NOT NULL, `hasImportant` INTEGER NOT NULL, `messageDraft` TEXT, PRIMARY KEY(`internalId`), FOREIGN KEY(`accountId`) REFERENCES `User`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "internalId",
|
||||
"columnName": "internalId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "displayName",
|
||||
"columnName": "displayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorId",
|
||||
"columnName": "actorId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorType",
|
||||
"columnName": "actorType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "avatarVersion",
|
||||
"columnName": "avatarVersion",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "callFlag",
|
||||
"columnName": "callFlag",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "callRecording",
|
||||
"columnName": "callRecording",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "callStartTime",
|
||||
"columnName": "callStartTime",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canDeleteConversation",
|
||||
"columnName": "canDeleteConversation",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canLeaveConversation",
|
||||
"columnName": "canLeaveConversation",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "canStartCall",
|
||||
"columnName": "canStartCall",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "description",
|
||||
"columnName": "description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCall",
|
||||
"columnName": "hasCall",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasPassword",
|
||||
"columnName": "hasPassword",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasCustomAvatar",
|
||||
"columnName": "isCustomAvatar",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "favorite",
|
||||
"columnName": "isFavorite",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastActivity",
|
||||
"columnName": "lastActivity",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastCommonReadMessage",
|
||||
"columnName": "lastCommonReadMessage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastMessage",
|
||||
"columnName": "lastMessage",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastPing",
|
||||
"columnName": "lastPing",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastReadMessage",
|
||||
"columnName": "lastReadMessage",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lobbyState",
|
||||
"columnName": "lobbyState",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lobbyTimer",
|
||||
"columnName": "lobbyTimer",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageExpiration",
|
||||
"columnName": "messageExpiration",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "notificationCalls",
|
||||
"columnName": "notificationCalls",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "notificationLevel",
|
||||
"columnName": "notificationLevel",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "objectType",
|
||||
"columnName": "objectType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "objectId",
|
||||
"columnName": "objectId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "participantType",
|
||||
"columnName": "participantType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "permissions",
|
||||
"columnName": "permissions",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "conversationReadOnlyState",
|
||||
"columnName": "readOnly",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "recordingConsentRequired",
|
||||
"columnName": "recordingConsent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "remoteServer",
|
||||
"columnName": "remoteServer",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "remoteToken",
|
||||
"columnName": "remoteToken",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "sessionId",
|
||||
"columnName": "sessionId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "status",
|
||||
"columnName": "status",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusClearAt",
|
||||
"columnName": "statusClearAt",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusIcon",
|
||||
"columnName": "statusIcon",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "statusMessage",
|
||||
"columnName": "statusMessage",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "type",
|
||||
"columnName": "type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMention",
|
||||
"columnName": "unreadMention",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMentionDirect",
|
||||
"columnName": "unreadMentionDirect",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "unreadMessages",
|
||||
"columnName": "unreadMessages",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasArchived",
|
||||
"columnName": "hasArchived",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasSensitive",
|
||||
"columnName": "hasSensitive",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasImportant",
|
||||
"columnName": "hasImportant",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageDraft",
|
||||
"columnName": "messageDraft",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_Conversations_accountId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"accountId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_Conversations_accountId` ON `${TABLE_NAME}` (`accountId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "User",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"accountId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "ChatMessages",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `id` INTEGER NOT NULL, `internalConversationId` TEXT NOT NULL, `threadId` INTEGER, `isThread` INTEGER NOT NULL, `actorDisplayName` TEXT NOT NULL, `message` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `expirationTimestamp` INTEGER NOT NULL, `isReplyable` INTEGER NOT NULL, `isTemporary` INTEGER NOT NULL, `lastEditActorDisplayName` TEXT, `lastEditActorId` TEXT, `lastEditActorType` TEXT, `lastEditTimestamp` INTEGER, `markdown` INTEGER, `messageParameters` TEXT, `messageType` TEXT NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `referenceId` TEXT, `sendStatus` TEXT, `silent` INTEGER NOT NULL, `systemMessage` TEXT NOT NULL, `threadTitle` TEXT, `threadReplies` INTEGER, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "internalId",
|
||||
"columnName": "internalId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "internalConversationId",
|
||||
"columnName": "internalConversationId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "threadId",
|
||||
"columnName": "threadId",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "isThread",
|
||||
"columnName": "isThread",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorDisplayName",
|
||||
"columnName": "actorDisplayName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "message",
|
||||
"columnName": "message",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorId",
|
||||
"columnName": "actorId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "actorType",
|
||||
"columnName": "actorType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "deleted",
|
||||
"columnName": "deleted",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "expirationTimestamp",
|
||||
"columnName": "expirationTimestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "replyable",
|
||||
"columnName": "isReplyable",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "isTemporary",
|
||||
"columnName": "isTemporary",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorDisplayName",
|
||||
"columnName": "lastEditActorDisplayName",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorId",
|
||||
"columnName": "lastEditActorId",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditActorType",
|
||||
"columnName": "lastEditActorType",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastEditTimestamp",
|
||||
"columnName": "lastEditTimestamp",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "renderMarkdown",
|
||||
"columnName": "markdown",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageParameters",
|
||||
"columnName": "messageParameters",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "messageType",
|
||||
"columnName": "messageType",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "parentMessageId",
|
||||
"columnName": "parent",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "reactions",
|
||||
"columnName": "reactions",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "reactionsSelf",
|
||||
"columnName": "reactionsSelf",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "referenceId",
|
||||
"columnName": "referenceId",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "sendStatus",
|
||||
"columnName": "sendStatus",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "silent",
|
||||
"columnName": "silent",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "systemMessageType",
|
||||
"columnName": "systemMessage",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "threadTitle",
|
||||
"columnName": "threadTitle",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "threadReplies",
|
||||
"columnName": "threadReplies",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "timestamp",
|
||||
"columnName": "timestamp",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_ChatMessages_internalId",
|
||||
"unique": true,
|
||||
"columnNames": [
|
||||
"internalId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ChatMessages_internalId` ON `${TABLE_NAME}` (`internalId`)"
|
||||
},
|
||||
{
|
||||
"name": "index_ChatMessages_internalConversationId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_ChatMessages_internalConversationId` ON `${TABLE_NAME}` (`internalConversationId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "Conversations",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"internalId"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"tableName": "ChatBlocks",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `internalConversationId` TEXT NOT NULL, `accountId` INTEGER, `token` TEXT, `threadId` INTEGER, `oldestMessageId` INTEGER NOT NULL, `newestMessageId` INTEGER NOT NULL, `hasHistory` INTEGER NOT NULL, FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "internalConversationId",
|
||||
"columnName": "internalConversationId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "accountId",
|
||||
"columnName": "accountId",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "token",
|
||||
"columnName": "token",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "threadId",
|
||||
"columnName": "threadId",
|
||||
"affinity": "INTEGER"
|
||||
},
|
||||
{
|
||||
"fieldPath": "oldestMessageId",
|
||||
"columnName": "oldestMessageId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "newestMessageId",
|
||||
"columnName": "newestMessageId",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "hasHistory",
|
||||
"columnName": "hasHistory",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_ChatBlocks_internalConversationId",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_ChatBlocks_internalConversationId` ON `${TABLE_NAME}` (`internalConversationId`)"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"table": "Conversations",
|
||||
"onDelete": "CASCADE",
|
||||
"onUpdate": "CASCADE",
|
||||
"columns": [
|
||||
"internalConversationId"
|
||||
],
|
||||
"referencedColumns": [
|
||||
"internalId"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"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, '8077a29304b3d28882e4b37fb10d0081')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -25,9 +25,6 @@ import org.junit.Assert.assertEquals
|
|||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.lang.Boolean
|
||||
import kotlin.Long
|
||||
import kotlin.String
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ChatBlocksDaoTest {
|
||||
|
|
@ -52,83 +49,13 @@ class ChatBlocksDaoTest {
|
|||
@After
|
||||
fun closeDb() = db.close()
|
||||
|
||||
@Test
|
||||
fun testGetChatBlocksContainingMessageId() =
|
||||
runTest {
|
||||
val user = createUserEntity("account1", "Account 1")
|
||||
usersDao.saveUser(user)
|
||||
val account1 = usersDao.getUserWithUserId("account1").blockingGet()
|
||||
|
||||
conversationsDao.upsertConversations(
|
||||
accountId = user.id,
|
||||
listOf(
|
||||
createConversationEntity(
|
||||
accountId = account1.id,
|
||||
"abc",
|
||||
roomName = "Conversation One"
|
||||
),
|
||||
createConversationEntity(
|
||||
accountId = account1.id,
|
||||
"def",
|
||||
roomName = "Conversation Two"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val conversation1 = conversationsDao.getConversationsForUser(account1.id).first()[0]
|
||||
|
||||
val chatBlock1 = ChatBlockEntity(
|
||||
internalConversationId = conversation1.internalId,
|
||||
accountId = conversation1.accountId,
|
||||
token = conversation1.token,
|
||||
threadId = 123,
|
||||
oldestMessageId = 50,
|
||||
newestMessageId = 60,
|
||||
hasHistory = true
|
||||
)
|
||||
|
||||
val chatBlock2 = ChatBlockEntity(
|
||||
internalConversationId = conversation1.internalId,
|
||||
accountId = conversation1.accountId,
|
||||
token = conversation1.token,
|
||||
threadId = 123,
|
||||
oldestMessageId = 10,
|
||||
newestMessageId = 20,
|
||||
hasHistory = true
|
||||
)
|
||||
|
||||
val chatBlock3 = ChatBlockEntity(
|
||||
internalConversationId = conversation1.internalId,
|
||||
accountId = conversation1.accountId,
|
||||
token = conversation1.token,
|
||||
threadId = null,
|
||||
oldestMessageId = 50,
|
||||
newestMessageId = 60,
|
||||
hasHistory = true
|
||||
)
|
||||
|
||||
chatBlocksDao.upsertChatBlock(chatBlock1)
|
||||
chatBlocksDao.upsertChatBlock(chatBlock2)
|
||||
chatBlocksDao.upsertChatBlock(chatBlock3)
|
||||
|
||||
val chatBlocksOfThread = chatBlocksDao.getChatBlocksContainingMessageId(
|
||||
internalConversationId = conversation1.internalId,
|
||||
threadId = 123,
|
||||
messageId = 55
|
||||
)
|
||||
|
||||
assertEquals(1, chatBlocksOfThread.first().size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetConnectedChatBlocks() =
|
||||
runTest {
|
||||
val user = createUserEntity("account1", "Account 1")
|
||||
usersDao.saveUser(user)
|
||||
usersDao.saveUser(createUserEntity("account1", "Account 1"))
|
||||
val account1 = usersDao.getUserWithUserId("account1").blockingGet()
|
||||
|
||||
conversationsDao.upsertConversations(
|
||||
account1.id,
|
||||
listOf(
|
||||
createConversationEntity(
|
||||
accountId = account1.id,
|
||||
|
|
@ -150,7 +77,6 @@ class ChatBlocksDaoTest {
|
|||
internalConversationId = conversation1.internalId,
|
||||
accountId = conversation1.accountId,
|
||||
token = conversation1.token,
|
||||
threadId = null,
|
||||
oldestMessageId = 50,
|
||||
newestMessageId = 60,
|
||||
hasHistory = true
|
||||
|
|
@ -160,7 +86,6 @@ class ChatBlocksDaoTest {
|
|||
internalConversationId = conversation1.internalId,
|
||||
accountId = conversation1.accountId,
|
||||
token = conversation1.token,
|
||||
threadId = null,
|
||||
oldestMessageId = 10,
|
||||
newestMessageId = 20,
|
||||
hasHistory = true
|
||||
|
|
@ -170,7 +95,6 @@ class ChatBlocksDaoTest {
|
|||
internalConversationId = conversation1.internalId,
|
||||
accountId = conversation1.accountId,
|
||||
token = conversation1.token,
|
||||
threadId = null,
|
||||
oldestMessageId = 45,
|
||||
newestMessageId = 55,
|
||||
hasHistory = true
|
||||
|
|
@ -180,7 +104,6 @@ class ChatBlocksDaoTest {
|
|||
internalConversationId = conversation1.internalId,
|
||||
accountId = conversation1.accountId,
|
||||
token = conversation1.token,
|
||||
threadId = null,
|
||||
oldestMessageId = 52,
|
||||
newestMessageId = 58,
|
||||
hasHistory = true
|
||||
|
|
@ -190,7 +113,6 @@ class ChatBlocksDaoTest {
|
|||
internalConversationId = conversation1.internalId,
|
||||
accountId = conversation1.accountId,
|
||||
token = conversation1.token,
|
||||
threadId = null,
|
||||
oldestMessageId = 1,
|
||||
newestMessageId = 99,
|
||||
hasHistory = true
|
||||
|
|
@ -200,7 +122,6 @@ class ChatBlocksDaoTest {
|
|||
internalConversationId = conversation1.internalId,
|
||||
accountId = conversation1.accountId,
|
||||
token = conversation1.token,
|
||||
threadId = null,
|
||||
oldestMessageId = 59,
|
||||
newestMessageId = 70,
|
||||
hasHistory = true
|
||||
|
|
@ -210,7 +131,6 @@ class ChatBlocksDaoTest {
|
|||
internalConversationId = conversation1.internalId,
|
||||
accountId = conversation1.accountId,
|
||||
token = conversation1.token,
|
||||
threadId = null,
|
||||
oldestMessageId = 80,
|
||||
newestMessageId = 90,
|
||||
hasHistory = true
|
||||
|
|
@ -220,7 +140,6 @@ class ChatBlocksDaoTest {
|
|||
internalConversationId = conversation2.internalId,
|
||||
accountId = conversation2.accountId,
|
||||
token = conversation2.token,
|
||||
threadId = null,
|
||||
oldestMessageId = 53,
|
||||
newestMessageId = 57,
|
||||
hasHistory = true
|
||||
|
|
@ -237,94 +156,14 @@ class ChatBlocksDaoTest {
|
|||
chatBlocksDao.upsertChatBlock(chatBlockWithinButOtherConversation)
|
||||
|
||||
val results = chatBlocksDao.getConnectedChatBlocks(
|
||||
internalConversationId = conversation1.internalId,
|
||||
threadId = null,
|
||||
oldestMessageId = searchedChatBlock.oldestMessageId,
|
||||
newestMessageId = searchedChatBlock.newestMessageId
|
||||
conversation1.internalId,
|
||||
searchedChatBlock.oldestMessageId,
|
||||
searchedChatBlock.newestMessageId
|
||||
)
|
||||
|
||||
assertEquals(5, results.first().size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetConnectedChatBlocksWithThreadsScenario() =
|
||||
runTest {
|
||||
val user = createUserEntity("account1", "Account 1")
|
||||
usersDao.saveUser(user)
|
||||
val account1 = usersDao.getUserWithUserId("account1").blockingGet()
|
||||
|
||||
conversationsDao.upsertConversations(
|
||||
account1.id,
|
||||
listOf(
|
||||
createConversationEntity(
|
||||
accountId = account1.id,
|
||||
"abc",
|
||||
roomName = "Conversation One"
|
||||
),
|
||||
createConversationEntity(
|
||||
accountId = account1.id,
|
||||
"def",
|
||||
roomName = "Conversation Two"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val conversation1 = conversationsDao.getConversationsForUser(account1.id).first()[0]
|
||||
|
||||
val searchedChatBlock = ChatBlockEntity(
|
||||
internalConversationId = conversation1.internalId,
|
||||
accountId = conversation1.accountId,
|
||||
token = conversation1.token,
|
||||
threadId = 123,
|
||||
oldestMessageId = 50,
|
||||
newestMessageId = 60,
|
||||
hasHistory = true
|
||||
)
|
||||
|
||||
val chatBlockOverlap1 = ChatBlockEntity(
|
||||
internalConversationId = conversation1.internalId,
|
||||
accountId = conversation1.accountId,
|
||||
token = conversation1.token,
|
||||
threadId = null,
|
||||
oldestMessageId = 45,
|
||||
newestMessageId = 55,
|
||||
hasHistory = true
|
||||
)
|
||||
|
||||
val chatBlockOverlap2 = ChatBlockEntity(
|
||||
internalConversationId = conversation1.internalId,
|
||||
accountId = conversation1.accountId,
|
||||
token = conversation1.token,
|
||||
threadId = 123,
|
||||
oldestMessageId = 59,
|
||||
newestMessageId = 70,
|
||||
hasHistory = true
|
||||
)
|
||||
|
||||
chatBlocksDao.upsertChatBlock(searchedChatBlock)
|
||||
|
||||
chatBlocksDao.upsertChatBlock(chatBlockOverlap1)
|
||||
chatBlocksDao.upsertChatBlock(chatBlockOverlap2)
|
||||
|
||||
val resultsForThreadIdNull = chatBlocksDao.getConnectedChatBlocks(
|
||||
internalConversationId = conversation1.internalId,
|
||||
threadId = null,
|
||||
oldestMessageId = searchedChatBlock.oldestMessageId,
|
||||
newestMessageId = searchedChatBlock.newestMessageId
|
||||
)
|
||||
|
||||
assertEquals(1, resultsForThreadIdNull.first().size)
|
||||
|
||||
val resultsForThreadId123 = chatBlocksDao.getConnectedChatBlocks(
|
||||
internalConversationId = conversation1.internalId,
|
||||
threadId = 123,
|
||||
oldestMessageId = searchedChatBlock.oldestMessageId,
|
||||
newestMessageId = searchedChatBlock.newestMessageId
|
||||
)
|
||||
|
||||
assertEquals(2, resultsForThreadId123.first().size)
|
||||
}
|
||||
|
||||
private fun createUserEntity(userId: String, userName: String) =
|
||||
UserEntity(
|
||||
userId = userId,
|
||||
|
|
@ -337,8 +176,8 @@ class ChatBlocksDaoTest {
|
|||
serverVersion = null,
|
||||
clientCertificate = null,
|
||||
externalSignalingServer = null,
|
||||
current = Boolean.FALSE,
|
||||
scheduledForDeletion = Boolean.FALSE
|
||||
current = java.lang.Boolean.FALSE,
|
||||
scheduledForDeletion = java.lang.Boolean.FALSE
|
||||
)
|
||||
|
||||
private fun createConversationEntity(accountId: Long, token: String, roomName: String) =
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ class ChatMessagesDaoTest {
|
|||
// 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(
|
||||
account1.id,
|
||||
listOf(
|
||||
createConversationEntity(
|
||||
accountId = account1.id,
|
||||
|
|
@ -141,11 +140,7 @@ class ChatMessagesDaoTest {
|
|||
assertEquals("are", conv1chatMessage3.message)
|
||||
|
||||
val chatMessagesConv1Since =
|
||||
chatMessagesDao.getMessagesForConversationSince(
|
||||
conversation1.internalId,
|
||||
conv1chatMessage3.id,
|
||||
null
|
||||
)
|
||||
chatMessagesDao.getMessagesForConversationSince(conversation1.internalId, conv1chatMessage3.id)
|
||||
assertEquals(3, chatMessagesConv1Since.first().size)
|
||||
assertEquals("are", chatMessagesConv1Since.first()[0].message)
|
||||
assertEquals("some", chatMessagesConv1Since.first()[1].message)
|
||||
|
|
@ -155,8 +150,7 @@ class ChatMessagesDaoTest {
|
|||
chatMessagesDao.getMessagesForConversationBeforeAndEqual(
|
||||
conversation1.internalId,
|
||||
conv1chatMessage3.id,
|
||||
3,
|
||||
null
|
||||
3
|
||||
)
|
||||
assertEquals(3, chatMessagesConv1To.first().size)
|
||||
assertEquals("hello", chatMessagesConv1To.first()[2].message)
|
||||
|
|
|
|||
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@email.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.data.database.migrations
|
||||
|
||||
import androidx.room.Room
|
||||
import androidx.room.testing.MigrationTestHelper
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.nextcloud.talk.data.source.local.Migrations
|
||||
import com.nextcloud.talk.data.source.local.TalkDatabase
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.IOException
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MigrationsTest {
|
||||
companion object {
|
||||
private const val TEST_DB = "migration-test"
|
||||
private const val INIT_VERSION = 10 // last version before update to offline first
|
||||
private val TAG = MigrationsTest::class.java.simpleName
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
val helper: MigrationTestHelper = MigrationTestHelper(
|
||||
InstrumentationRegistry.getInstrumentation(),
|
||||
TalkDatabase::class.java
|
||||
)
|
||||
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
@Suppress("SpreadOperator")
|
||||
fun migrateAll() {
|
||||
helper.createDatabase(TEST_DB, INIT_VERSION).apply {
|
||||
close()
|
||||
}
|
||||
|
||||
Room.databaseBuilder(
|
||||
InstrumentationRegistry.getInstrumentation().targetContext,
|
||||
TalkDatabase::class.java,
|
||||
TEST_DB
|
||||
).addMigrations(*TalkDatabase.MIGRATIONS).build().apply {
|
||||
openHelper.writableDatabase.close()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun migrate10To11() {
|
||||
helper.createDatabase(TEST_DB, 10).apply {
|
||||
close()
|
||||
}
|
||||
helper.runMigrationsAndValidate(TEST_DB, 11, true, Migrations.MIGRATION_10_11)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun migrate11To12() {
|
||||
helper.createDatabase(TEST_DB, 11).apply {
|
||||
close()
|
||||
}
|
||||
helper.runMigrationsAndValidate(TEST_DB, 12, true, Migrations.MIGRATION_11_12)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun migrate12To13() {
|
||||
helper.createDatabase(TEST_DB, 12).apply {
|
||||
close()
|
||||
}
|
||||
helper.runMigrationsAndValidate(TEST_DB, 13, true, Migrations.MIGRATION_12_13)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun migrate13To14() {
|
||||
helper.createDatabase(TEST_DB, 13).apply {
|
||||
close()
|
||||
}
|
||||
helper.runMigrationsAndValidate(TEST_DB, 14, true, Migrations.MIGRATION_13_14)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun migrate14To15() {
|
||||
helper.createDatabase(TEST_DB, 14).apply {
|
||||
close()
|
||||
}
|
||||
helper.runMigrationsAndValidate(TEST_DB, 15, true, Migrations.MIGRATION_14_15)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun migrate15To16() {
|
||||
helper.createDatabase(TEST_DB, 15).apply {
|
||||
close()
|
||||
}
|
||||
helper.runMigrationsAndValidate(TEST_DB, 16, true, Migrations.MIGRATION_15_16)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun migrate17To19() {
|
||||
helper.createDatabase(TEST_DB, 17).apply {
|
||||
close()
|
||||
}
|
||||
helper.runMigrationsAndValidate(TEST_DB, 19, true, Migrations.MIGRATION_17_19)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.utils
|
||||
|
||||
import android.graphics.Color
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
class ColorGeneratorTest {
|
||||
|
||||
@Test
|
||||
fun testUsernameToColor() {
|
||||
usernameToColorHexHelper("", "#0082c9")
|
||||
usernameToColorHexHelper(",", "#1e78c1")
|
||||
usernameToColorHexHelper(".", "#c98879")
|
||||
usernameToColorHexHelper("admin", "#d09e6d")
|
||||
usernameToColorHexHelper("123e4567-e89b-12d3-a456-426614174000", "#bc5c91")
|
||||
usernameToColorHexHelper("Akeel Robertson", "#9750a4")
|
||||
usernameToColorHexHelper("Brayden Truong", "#d09e6d")
|
||||
usernameToColorHexHelper("Daphne Roy", "#9750a4")
|
||||
usernameToColorHexHelper("Ellena Wright Frederic Conway", "#c37285")
|
||||
usernameToColorHexHelper("Gianluca Hills", "#d6b461")
|
||||
usernameToColorHexHelper("Haseeb Stephens", "#d6b461")
|
||||
usernameToColorHexHelper("Idris Mac", "#9750a4")
|
||||
usernameToColorHexHelper("Kristi Fisher", "#0082c9")
|
||||
usernameToColorHexHelper("Lillian Wall", "#bc5c91")
|
||||
usernameToColorHexHelper("Lorelai Taylor", "#ddcb55")
|
||||
usernameToColorHexHelper("Madina Knight", "#9750a4")
|
||||
usernameToColorHexHelper("Meeting", "#c98879")
|
||||
usernameToColorHexHelper("Private Circle", "#c37285")
|
||||
usernameToColorHexHelper("Rae Hope", "#795aab")
|
||||
usernameToColorHexHelper("Santiago Singleton", "#bc5c91")
|
||||
usernameToColorHexHelper("Sid Combs", "#d09e6d")
|
||||
usernameToColorHexHelper("TestCircle", "#499aa2")
|
||||
usernameToColorHexHelper("Tom Mörtel", "#248eb5")
|
||||
usernameToColorHexHelper("Vivienne Jacobs", "#1e78c1")
|
||||
usernameToColorHexHelper("Zaki Cortes", "#6ea68f")
|
||||
usernameToColorHexHelper("a user", "#5b64b3")
|
||||
usernameToColorHexHelper("admin@cloud.example.com", "#9750a4")
|
||||
usernameToColorHexHelper("another user", "#ddcb55")
|
||||
usernameToColorHexHelper("asd", "#248eb5")
|
||||
usernameToColorHexHelper("bar", "#0082c9")
|
||||
usernameToColorHexHelper("foo", "#d09e6d")
|
||||
usernameToColorHexHelper("wasd", "#b6469d")
|
||||
usernameToColorHexHelper("مرحبا بالعالم", "#c98879")
|
||||
usernameToColorHexHelper("🙈", "#b6469d")
|
||||
}
|
||||
|
||||
private fun usernameToColorHexHelper(username: String, expectedHexColor: String) {
|
||||
val userColorInt = ColorGenerator.usernameToColor(username) // returns Int
|
||||
val userHexColor = intToHex(userColorInt)
|
||||
|
||||
Assert.assertEquals(expectedHexColor.lowercase(), userHexColor.lowercase())
|
||||
}
|
||||
|
||||
private fun intToHex(colorInt: Int): String {
|
||||
val r = Color.red(colorInt)
|
||||
val g = Color.green(colorInt)
|
||||
val b = Color.blue(colorInt)
|
||||
return String.format("#%02x%02x%02x", r, g, b)
|
||||
}
|
||||
}
|
||||
|
|
@ -258,10 +258,6 @@
|
|||
android:name=".lock.LockedActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".threadsoverview.ThreadsOverviewActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<receiver
|
||||
android:name=".receivers.PackageReplacedReceiver"
|
||||
android:exported="false">
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
*/
|
||||
package com.nextcloud.talk.account
|
||||
|
||||
import android.Manifest
|
||||
import android.accounts.Account
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
|
|
@ -22,13 +21,8 @@ import android.view.View
|
|||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.bundleOf
|
||||
import autodagger.AutoInjector
|
||||
import com.blikoon.qrcodescanner.QrCodeActivity
|
||||
import com.github.dhaval2404.imagepicker.util.PermissionUtil
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
|
|
@ -53,7 +47,6 @@ import io.reactivex.schedulers.Schedulers
|
|||
import java.security.cert.CertificateException
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class ServerSelectionActivity : BaseActivity() {
|
||||
|
||||
|
|
@ -127,8 +120,6 @@ class ServerSelectionActivity : BaseActivity() {
|
|||
}
|
||||
binding.certTextView.setOnClickListener { onCertClick() }
|
||||
|
||||
binding.scanQr.setOnClickListener { onScan() }
|
||||
|
||||
if (ApplicationWideMessageHolder.getInstance().messageType != null) {
|
||||
if (ApplicationWideMessageHolder.getInstance().messageType
|
||||
== ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK
|
||||
|
|
@ -399,52 +390,6 @@ class ServerSelectionActivity : BaseActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private val requestCameraPermissionLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
|
||||
if (isGranted) {
|
||||
// Permission was granted
|
||||
startQRScanner()
|
||||
}
|
||||
}
|
||||
|
||||
fun onScan() {
|
||||
if (PermissionUtil.isPermissionGranted(this, Manifest.permission.CAMERA)) {
|
||||
startQRScanner()
|
||||
} else {
|
||||
requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startQRScanner() {
|
||||
val intent = Intent(this, QrCodeActivity::class.java)
|
||||
qrScanResultLauncher.launch(intent)
|
||||
}
|
||||
|
||||
private val qrScanResultLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
val data = result.data
|
||||
|
||||
if (data == null) {
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val resultData = data.getStringExtra(QR_URI)
|
||||
|
||||
if (resultData == null || !resultData.startsWith("nc")) {
|
||||
Snackbar.make(binding.root, getString(R.string.qr_code_error), Snackbar.LENGTH_SHORT).show()
|
||||
return@registerForActivityResult
|
||||
}
|
||||
|
||||
val intent = Intent(this, WebViewLoginActivity::class.java)
|
||||
val bundle = bundleOf().apply {
|
||||
putString(BundleKeys.KEY_FROM_QR, resultData)
|
||||
}
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
dispose()
|
||||
|
|
@ -463,6 +408,5 @@ class ServerSelectionActivity : BaseActivity() {
|
|||
companion object {
|
||||
private val TAG = ServerSelectionActivity::class.java.simpleName
|
||||
const val MIN_SERVER_MAJOR_VERSION = 13
|
||||
private const val QR_URI = "com.blikoon.qrcodescanner.got_qr_scan_relult"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
|
||||
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-FileCopyrightText: 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.account
|
||||
|
|
@ -61,7 +63,6 @@ import java.security.cert.X509Certificate
|
|||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("ReturnCount", "LongMethod")
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class WebViewLoginActivity : BaseActivity() {
|
||||
|
||||
|
|
@ -114,9 +115,10 @@ class WebViewLoginActivity : BaseActivity() {
|
|||
setContentView(binding.root)
|
||||
actionBar?.hide()
|
||||
initSystemBars()
|
||||
assembledPrefix = resources!!.getString(R.string.nc_talk_login_scheme) + PROTOCOL_SUFFIX + "login/"
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||
handleIntent()
|
||||
setupWebView()
|
||||
}
|
||||
|
||||
private fun handleIntent() {
|
||||
|
|
@ -131,18 +133,11 @@ class WebViewLoginActivity : BaseActivity() {
|
|||
if (extras.containsKey(BundleKeys.KEY_PASSWORD)) {
|
||||
password = extras.getString(BundleKeys.KEY_PASSWORD)
|
||||
}
|
||||
|
||||
if (extras.containsKey(BundleKeys.KEY_FROM_QR)) {
|
||||
extras.getString(BundleKeys.KEY_FROM_QR)?.let {
|
||||
parseAndLoginFromWebView(it)
|
||||
}
|
||||
} else {
|
||||
setupWebView()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun setupWebView() {
|
||||
assembledPrefix = resources!!.getString(R.string.nc_talk_login_scheme) + PROTOCOL_SUFFIX + "login/"
|
||||
binding.webview.settings.allowFileAccess = false
|
||||
binding.webview.settings.allowFileAccessFromFileURLs = false
|
||||
binding.webview.settings.javaScriptEnabled = true
|
||||
|
|
@ -294,18 +289,22 @@ class WebViewLoginActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
@SuppressLint("DiscouragedPrivateApi")
|
||||
@Suppress("Detekt.TooGenericExceptionCaught", "WebViewClientOnReceivedSslError")
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
|
||||
try {
|
||||
val sslCertificate = error.certificate
|
||||
val f: Field = sslCertificate.javaClass.getDeclaredField("mX509Certificate")
|
||||
f.isAccessible = true
|
||||
val cert = f[sslCertificate] as X509Certificate
|
||||
try {
|
||||
trustManager.checkServerTrusted(arrayOf(cert), "generic")
|
||||
handler.proceed()
|
||||
} catch (exception: CertificateException) {
|
||||
eventBus.post(CertificateEvent(cert, trustManager, handler))
|
||||
if (cert == null) {
|
||||
handler.cancel()
|
||||
} else {
|
||||
try {
|
||||
trustManager.checkServerTrusted(arrayOf(cert), "generic")
|
||||
handler.proceed()
|
||||
} catch (exception: CertificateException) {
|
||||
eventBus.post(CertificateEvent(cert, trustManager, handler))
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
handler.cancel()
|
||||
|
|
@ -333,16 +332,12 @@ class WebViewLoginActivity : BaseActivity() {
|
|||
dispose()
|
||||
cookieManager.cookieStore.removeAll()
|
||||
|
||||
if (userManager.checkIfUserIsScheduledForDeletion(loginData.username!!, loginData.serverUrl!!)
|
||||
.blockingGet()
|
||||
) {
|
||||
if (userManager.checkIfUserIsScheduledForDeletion(loginData.username!!, baseUrl!!).blockingGet()) {
|
||||
Log.e(TAG, "Tried to add already existing user who is scheduled for deletion.")
|
||||
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
// however the user is not yet deleted, just start AccountRemovalWorker again to make sure to delete it.
|
||||
startAccountRemovalWorkerAndRestartApp()
|
||||
} else if (userManager.checkIfUserExists(loginData.username!!, loginData.serverUrl!!)
|
||||
.blockingGet()
|
||||
) {
|
||||
} else if (userManager.checkIfUserExists(loginData.username!!, baseUrl!!).blockingGet()) {
|
||||
if (reauthorizeAccount) {
|
||||
updateUserAndRestartApp(loginData)
|
||||
} else {
|
||||
|
|
@ -352,9 +347,6 @@ class WebViewLoginActivity : BaseActivity() {
|
|||
} else {
|
||||
startAccountVerification(loginData)
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Login Data was null")
|
||||
restartApp()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -364,9 +356,9 @@ class WebViewLoginActivity : BaseActivity() {
|
|||
bundle.putString(KEY_TOKEN, loginData.token)
|
||||
bundle.putString(KEY_BASE_URL, loginData.serverUrl)
|
||||
var protocol = ""
|
||||
if (loginData.serverUrl!!.startsWith("http://")) {
|
||||
if (baseUrl!!.startsWith("http://")) {
|
||||
protocol = "http://"
|
||||
} else if (loginData.serverUrl!!.startsWith("https://")) {
|
||||
} else if (baseUrl!!.startsWith("https://")) {
|
||||
protocol = "https://"
|
||||
}
|
||||
if (!TextUtils.isEmpty(protocol)) {
|
||||
|
|
@ -424,17 +416,17 @@ class WebViewLoginActivity : BaseActivity() {
|
|||
return null
|
||||
}
|
||||
for (value in values) {
|
||||
if (value.startsWith("user$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR")) {
|
||||
if (value.startsWith("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
|
||||
loginData.username = URLDecoder.decode(
|
||||
value.substring(("user$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR").length)
|
||||
value.substring(("user" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length)
|
||||
)
|
||||
} else if (value.startsWith("password$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR")) {
|
||||
} else if (value.startsWith("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
|
||||
loginData.token = URLDecoder.decode(
|
||||
value.substring(("password$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR").length)
|
||||
value.substring(("password" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length)
|
||||
)
|
||||
} else if (value.startsWith("server$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR")) {
|
||||
} else if (value.startsWith("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR)) {
|
||||
loginData.serverUrl = URLDecoder.decode(
|
||||
value.substring(("server$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR").length)
|
||||
value.substring(("server" + LOGIN_URL_DATA_KEY_VALUE_SEPARATOR).length)
|
||||
)
|
||||
} else {
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -1,161 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.account.data
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import com.nextcloud.talk.account.data.io.LocalLoginDataSource
|
||||
import com.nextcloud.talk.account.data.model.LoginCompletion
|
||||
import com.nextcloud.talk.account.data.model.LoginResponse
|
||||
import com.nextcloud.talk.account.data.network.NetworkLoginDataSource
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_BASE_URL
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ORIGINAL_PROTOCOL
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USERNAME
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.net.URLDecoder
|
||||
|
||||
@Suppress("TooManyFunctions", "ReturnCount")
|
||||
class LoginRepository(val network: NetworkLoginDataSource, val local: LocalLoginDataSource) {
|
||||
|
||||
companion object {
|
||||
val TAG: String = LoginRepository::class.java.simpleName
|
||||
private const val INTERVAL = 250L
|
||||
private const val HTTP_OK = 200
|
||||
private const val USER_KEY = "user:"
|
||||
private const val SERVER_KEY = "server:"
|
||||
private const val PASS_KEY = "password:"
|
||||
private const val PREFIX = "nc://login/"
|
||||
private const val MAX_ARGS = 3
|
||||
}
|
||||
|
||||
private var shouldReauthorizeUser = false
|
||||
private var shouldLoop = true
|
||||
|
||||
suspend fun pollLogin(response: LoginResponse): LoginCompletion? =
|
||||
withContext(Dispatchers.IO) {
|
||||
while (shouldLoop) {
|
||||
val loginData = network.performLoginFlowV2(response)
|
||||
if (loginData == null) {
|
||||
break
|
||||
}
|
||||
|
||||
if (loginData.status == HTTP_OK) {
|
||||
return@withContext loginData
|
||||
}
|
||||
|
||||
delay(INTERVAL) // No response yet, retry
|
||||
}
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point for QR scanner
|
||||
*
|
||||
*/
|
||||
fun startLoginFlowFromQR(dataString: String, reAuth: Boolean = false): LoginCompletion? {
|
||||
shouldReauthorizeUser = reAuth
|
||||
|
||||
if (!dataString.startsWith(PREFIX)) {
|
||||
Log.e(TAG, "Invalid login URL detected")
|
||||
return null
|
||||
}
|
||||
|
||||
val data = dataString.removePrefix(PREFIX)
|
||||
val values = data.split('&')
|
||||
|
||||
if (values.size !in 1..MAX_ARGS) {
|
||||
Log.e(TAG, "Illegal number of login URL elements detected: ${values.size}")
|
||||
return null
|
||||
}
|
||||
|
||||
var server = ""
|
||||
var loginName = ""
|
||||
var appPassword = ""
|
||||
values.forEach { value ->
|
||||
when {
|
||||
value.startsWith(USER_KEY) -> {
|
||||
loginName = URLDecoder.decode(value.removePrefix(USER_KEY), "UTF-8")
|
||||
}
|
||||
|
||||
value.startsWith(PASS_KEY) -> {
|
||||
appPassword = URLDecoder.decode(value.removePrefix(PASS_KEY), "UTF-8")
|
||||
}
|
||||
|
||||
value.startsWith(SERVER_KEY) -> {
|
||||
server = URLDecoder.decode(value.removePrefix(SERVER_KEY), "UTF-8")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if (server.isNotEmpty() && loginName.isNotEmpty() && appPassword.isNotEmpty()) {
|
||||
LoginCompletion(HTTP_OK, server, loginName, appPassword)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point to the login process
|
||||
*/
|
||||
suspend fun startLoginFlow(baseUrl: String, reAuth: Boolean = false): LoginResponse? =
|
||||
withContext(Dispatchers.IO) {
|
||||
shouldReauthorizeUser = reAuth
|
||||
val response = network.anonymouslyPostLoginRequest(baseUrl)
|
||||
return@withContext response
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends normal login process by canceling the polling
|
||||
*/
|
||||
fun cancelLoginFlow() {
|
||||
shouldLoop = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bundle if user is not scheduled for deletion or doesn't already exist, null otherwise
|
||||
*/
|
||||
fun parseAndLogin(loginData: LoginCompletion): Bundle? {
|
||||
if (local.checkIfUserIsScheduledForDeletion(loginData)) {
|
||||
// however the user is not yet deleted, just start AccountRemovalWorker again to make sure to delete it.
|
||||
local.startAccountRemovalWorker()
|
||||
return null
|
||||
} else if (local.checkIfUserExists(loginData)) {
|
||||
if (shouldReauthorizeUser) {
|
||||
local.updateUser(loginData)
|
||||
} else {
|
||||
Log.w(TAG, "Tried to add an account that account already exists. Skipped user creation.")
|
||||
}
|
||||
|
||||
return null
|
||||
} else {
|
||||
return startAccountVerification(loginData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAccountVerification(loginData: LoginCompletion): Bundle {
|
||||
val bundle = Bundle()
|
||||
bundle.putString(KEY_USERNAME, loginData.loginName)
|
||||
bundle.putString(KEY_TOKEN, loginData.appPassword)
|
||||
bundle.putString(KEY_BASE_URL, loginData.server)
|
||||
var protocol = ""
|
||||
if (loginData.server.startsWith("http://")) {
|
||||
protocol = "http://"
|
||||
} else if (loginData.server.startsWith("https://")) {
|
||||
protocol = "https://"
|
||||
}
|
||||
if (!TextUtils.isEmpty(protocol)) {
|
||||
bundle.putString(KEY_ORIGINAL_PROTOCOL, protocol)
|
||||
}
|
||||
|
||||
return bundle
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.account.data.io
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import com.nextcloud.talk.account.data.model.LoginCompletion
|
||||
import com.nextcloud.talk.jobs.AccountRemovalWorker
|
||||
import com.nextcloud.talk.users.UserManager
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
|
||||
// local datasource for communicating with room through account manager
|
||||
// crucial for making sure the login process interacts with the db as expected.
|
||||
class LocalLoginDataSource(val userManager: UserManager, val appPreferences: AppPreferences, val context: Context) {
|
||||
|
||||
fun updateUser(loginData: LoginCompletion) {
|
||||
val currentUser = userManager.currentUser.blockingGet()
|
||||
if (currentUser != null) {
|
||||
currentUser.clientCertificate = appPreferences.temporaryClientCertAlias
|
||||
currentUser.token = loginData.appPassword
|
||||
userManager.updateOrCreateUser(currentUser)
|
||||
}
|
||||
}
|
||||
|
||||
fun startAccountRemovalWorker(): LiveData<WorkInfo?> {
|
||||
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
|
||||
WorkManager.getInstance(context).enqueue(accountRemovalWork)
|
||||
|
||||
return WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
|
||||
}
|
||||
|
||||
fun checkIfUserIsScheduledForDeletion(data: LoginCompletion): Boolean =
|
||||
userManager.checkIfUserIsScheduledForDeletion(data.loginName, data.server).blockingGet()
|
||||
|
||||
fun checkIfUserExists(data: LoginCompletion): Boolean =
|
||||
userManager.checkIfUserExists(data.loginName, data.server).blockingGet()
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.account.data.model
|
||||
|
||||
data class LoginResponse(val token: String, val pollUrl: String, val loginUrl: String)
|
||||
|
||||
data class LoginCompletion(val status: Int, val server: String, val loginName: String, val appPassword: String)
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.account.data.network
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import com.nextcloud.talk.account.data.model.LoginCompletion
|
||||
import com.nextcloud.talk.account.data.model.LoginResponse
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import java.io.IOException
|
||||
import javax.net.ssl.SSLHandshakeException
|
||||
|
||||
// This class handles the network and polling logic in isolation, which makes it easier to test
|
||||
// Login and Authentication is critical, thus it needs to be working properly.
|
||||
class NetworkLoginDataSource(val okHttpClient: OkHttpClient) {
|
||||
|
||||
companion object {
|
||||
val TAG: String = NetworkLoginDataSource::class.java.simpleName
|
||||
}
|
||||
|
||||
fun anonymouslyPostLoginRequest(baseUrl: String): LoginResponse? {
|
||||
val url = "$baseUrl/index.php/login/v2"
|
||||
var result: LoginResponse? = null
|
||||
runCatching {
|
||||
val response = getResponseOfAnonymouslyPostLoginRequest(url)
|
||||
val jsonObject: JsonObject = JsonParser.parseString(response).asJsonObject
|
||||
val loginUrl: String = getLoginUrl(jsonObject)
|
||||
val token = jsonObject.getAsJsonObject("poll").get("token").asString
|
||||
val pollUrl = jsonObject.getAsJsonObject("poll").get("endpoint").asString
|
||||
result = LoginResponse(token, pollUrl, loginUrl)
|
||||
}.getOrElse { e ->
|
||||
when (e) {
|
||||
is SSLHandshakeException,
|
||||
is NullPointerException,
|
||||
is IOException -> {
|
||||
Log.e(TAG, "Error caught at anonymouslyPostLoginRequest: $e")
|
||||
}
|
||||
|
||||
else -> throw e
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getResponseOfAnonymouslyPostLoginRequest(url: String): String? {
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.post(FormBody.Builder().build())
|
||||
.addHeader("Clear-Site-Data", "cookies")
|
||||
.build()
|
||||
|
||||
okHttpClient.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException("Unexpected code $response")
|
||||
}
|
||||
return response.body?.string()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLoginUrl(response: JsonObject): String {
|
||||
var result: String? = response.get("login").asString
|
||||
if (result == null) {
|
||||
result = ""
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun performLoginFlowV2(response: LoginResponse): LoginCompletion? {
|
||||
val requestBody: RequestBody = FormBody.Builder()
|
||||
.add("token", response.token)
|
||||
.build()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(response.pollUrl)
|
||||
.post(requestBody)
|
||||
.build()
|
||||
|
||||
var result: LoginCompletion? = null
|
||||
runCatching {
|
||||
okHttpClient.newCall(request).execute()
|
||||
.use { response ->
|
||||
val status: Int = response.code
|
||||
val responseBody = response.body?.string()
|
||||
|
||||
result = if (response.isSuccessful && responseBody?.isNotEmpty() == true) {
|
||||
val jsonObject = JsonParser.parseString(responseBody).asJsonObject
|
||||
val server: String = jsonObject.get("server").asString
|
||||
val loginName: String = jsonObject.get("loginName").asString
|
||||
val appPassword: String = jsonObject.get("appPassword").asString
|
||||
|
||||
LoginCompletion(status, server, loginName, appPassword)
|
||||
} else {
|
||||
LoginCompletion(status, "", "", "")
|
||||
}
|
||||
}
|
||||
}.getOrElse { e ->
|
||||
when (e) {
|
||||
is NullPointerException,
|
||||
is SSLHandshakeException,
|
||||
is IllegalStateException,
|
||||
is IOException -> {
|
||||
Log.e(TAG, "Error caught at performLoginFlowV2: $e")
|
||||
}
|
||||
|
||||
else -> throw e
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.account.viewmodels
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.nextcloud.talk.account.data.LoginRepository
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class BrowserLoginActivityViewModel @Inject constructor(val repository: LoginRepository) : ViewModel() {
|
||||
|
||||
companion object {
|
||||
private val TAG = BrowserLoginActivityViewModel::class.java.simpleName
|
||||
}
|
||||
|
||||
sealed class InitialLoginViewState {
|
||||
data object None : InitialLoginViewState()
|
||||
data class InitialLoginRequestSuccess(val loginUrl: String) : InitialLoginViewState()
|
||||
data object InitialLoginRequestError : InitialLoginViewState()
|
||||
}
|
||||
|
||||
private val _initialLoginRequestState = MutableStateFlow<InitialLoginViewState>(InitialLoginViewState.None)
|
||||
val initialLoginRequestState: StateFlow<InitialLoginViewState> = _initialLoginRequestState
|
||||
|
||||
sealed class PostLoginViewState {
|
||||
data object None : PostLoginViewState()
|
||||
data object PostLoginRestartApp : PostLoginViewState()
|
||||
data object PostLoginError : PostLoginViewState()
|
||||
data class PostLoginContinue(val data: Bundle) : PostLoginViewState()
|
||||
}
|
||||
|
||||
private val _postLoginState = MutableStateFlow<PostLoginViewState>(PostLoginViewState.None)
|
||||
val postLoginState: StateFlow<PostLoginViewState> = _postLoginState
|
||||
|
||||
fun loginNormally(baseUrl: String, reAuth: Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
val response = repository.startLoginFlow(baseUrl, reAuth)
|
||||
|
||||
if (response == null) {
|
||||
_initialLoginRequestState.value = InitialLoginViewState.InitialLoginRequestError
|
||||
return@launch
|
||||
}
|
||||
|
||||
_initialLoginRequestState.value =
|
||||
InitialLoginViewState.InitialLoginRequestSuccess(response.loginUrl)
|
||||
|
||||
val loginCompletionResponse = repository.pollLogin(response)
|
||||
|
||||
if (loginCompletionResponse == null) {
|
||||
_postLoginState.value = PostLoginViewState.PostLoginError
|
||||
return@launch
|
||||
}
|
||||
|
||||
val bundle = repository.parseAndLogin(loginCompletionResponse)
|
||||
if (bundle == null) {
|
||||
_postLoginState.value = PostLoginViewState.PostLoginRestartApp
|
||||
return@launch
|
||||
}
|
||||
|
||||
_postLoginState.value = PostLoginViewState.PostLoginContinue(bundle)
|
||||
}
|
||||
}
|
||||
|
||||
fun loginWithQR(dataString: String, reAuth: Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
val loginCompletionResponse = repository.startLoginFlowFromQR(dataString, reAuth)
|
||||
if (loginCompletionResponse == null) {
|
||||
_postLoginState.value = PostLoginViewState.PostLoginError
|
||||
return@launch
|
||||
}
|
||||
|
||||
val bundle = repository.parseAndLogin(loginCompletionResponse)
|
||||
if (bundle == null) {
|
||||
_postLoginState.value = PostLoginViewState.PostLoginRestartApp
|
||||
return@launch
|
||||
}
|
||||
|
||||
_postLoginState.value = PostLoginViewState.PostLoginContinue(bundle)
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelLogin() = repository.cancelLoginFlow()
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import android.text.TextUtils
|
|||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowInsets
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.webkit.SslErrorHandler
|
||||
|
|
@ -24,8 +25,6 @@ import android.widget.EditText
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import autodagger.AutoInjector
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.nextcloud.talk.R
|
||||
|
|
@ -120,23 +119,18 @@ open class BaseActivity : AppCompatActivity() {
|
|||
* May be aligned with android-common lib in the future: .../ui/util/extensions/AppCompatActivityExtensions.kt
|
||||
*/
|
||||
fun initSystemBars() {
|
||||
val decorView = window.decorView
|
||||
decorView.setOnApplyWindowInsetsListener { view, insets ->
|
||||
window.decorView.setOnApplyWindowInsetsListener { view, insets ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||
val systemBars = insets.getInsets(
|
||||
WindowInsetsCompat.Type.systemBars() or
|
||||
WindowInsetsCompat.Type.displayCutout()
|
||||
)
|
||||
val statusBarHeight = insets.getInsets(WindowInsets.Type.statusBars()).top
|
||||
view.setPadding(0, statusBarHeight, 0, 0)
|
||||
val color = ResourcesCompat.getColor(resources, R.color.bg_default, context.theme)
|
||||
view.setBackgroundColor(color)
|
||||
view.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
} else {
|
||||
colorizeStatusBar()
|
||||
colorizeNavigationBar()
|
||||
}
|
||||
insets
|
||||
}
|
||||
ViewCompat.requestApplyInsets(decorView)
|
||||
}
|
||||
|
||||
open fun colorizeStatusBar() {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
package com.nextcloud.talk.activities
|
||||
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.provider.ContactsContract
|
||||
|
|
@ -92,7 +93,7 @@ class MainActivity :
|
|||
}
|
||||
|
||||
fun lockScreenIfConditionsApply() {
|
||||
val keyguardManager = getSystemService(KEYGUARD_SERVICE) as KeyguardManager
|
||||
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
||||
if (keyguardManager.isKeyguardSecure && appPreferences.isScreenLocked) {
|
||||
if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)) {
|
||||
val lockIntent = Intent(context, LockedActivity::class.java)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import android.view.View
|
|||
import android.widget.RelativeLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import coil.dispose
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.adapters.items.ConversationItem.ConversationItemViewHolder
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
|
|
@ -183,9 +182,7 @@ class ConversationItem(
|
|||
}
|
||||
|
||||
private fun showAvatar(holder: ConversationItemViewHolder) {
|
||||
holder.binding.dialogAvatar.dispose()
|
||||
holder.binding.dialogAvatar.visibility = View.VISIBLE
|
||||
|
||||
var shouldLoadAvatar = shouldLoadAvatar(holder)
|
||||
if (ConversationEnums.ConversationType.ROOM_SYSTEM == model.type) {
|
||||
holder.binding.dialogAvatar.loadSystemAvatar()
|
||||
|
|
|
|||
|
|
@ -221,10 +221,6 @@ class MentionAutocompleteItem(
|
|||
if (statusMessage.isNullOrEmpty()) {
|
||||
holder.binding.conversationInfoStatusMessage.setText(R.string.dnd)
|
||||
}
|
||||
} else if (status != null && status == StatusType.BUSY.string) {
|
||||
if (statusMessage.isNullOrEmpty()) {
|
||||
holder.binding.conversationInfoStatusMessage.setText(R.string.busy)
|
||||
}
|
||||
} else if (status != null && status == StatusType.AWAY.string) {
|
||||
if (statusMessage.isNullOrEmpty()) {
|
||||
holder.binding.conversationInfoStatusMessage.setText(R.string.away)
|
||||
|
|
|
|||
|
|
@ -276,10 +276,6 @@ class ParticipantItem(
|
|||
if (model.statusMessage == null || model.statusMessage!!.isEmpty()) {
|
||||
holder.binding.conversationInfoStatusMessage.setText(R.string.dnd)
|
||||
}
|
||||
} else if (model.status != null && model.status == StatusType.BUSY.string) {
|
||||
if (model.statusMessage == null || model.statusMessage!!.isEmpty()) {
|
||||
holder.binding.conversationInfoStatusMessage.setText(R.string.busy)
|
||||
}
|
||||
} else if (model.status != null && model.status == StatusType.AWAY.string) {
|
||||
if (model.statusMessage == null || model.statusMessage!!.isEmpty()) {
|
||||
holder.binding.conversationInfoStatusMessage.setText(R.string.away)
|
||||
|
|
|
|||
|
|
@ -22,21 +22,21 @@ interface AdjustableMessageHolderInterface {
|
|||
|
||||
val binding: ViewBinding
|
||||
|
||||
fun adjustIfNoteToSelf(currentConversation: ConversationModel?) {
|
||||
fun adjustIfNoteToSelf(viewHolder: AdjustableMessageHolderInterface, currentConversation: ConversationModel?) {
|
||||
if (currentConversation?.type == ConversationType.NOTE_TO_SELF) {
|
||||
when (this.binding.javaClass) {
|
||||
when (viewHolder.binding.javaClass) {
|
||||
ItemCustomOutcomingTextMessageBinding::class.java ->
|
||||
(this.binding as ItemCustomOutcomingTextMessageBinding).bubble
|
||||
(viewHolder.binding as ItemCustomOutcomingTextMessageBinding).bubble
|
||||
ItemCustomOutcomingDeckCardMessageBinding::class.java ->
|
||||
(this.binding as ItemCustomOutcomingDeckCardMessageBinding).bubble
|
||||
(viewHolder.binding as ItemCustomOutcomingDeckCardMessageBinding).bubble
|
||||
ItemCustomOutcomingLinkPreviewMessageBinding::class.java ->
|
||||
(this.binding as ItemCustomOutcomingLinkPreviewMessageBinding).bubble
|
||||
(viewHolder.binding as ItemCustomOutcomingLinkPreviewMessageBinding).bubble
|
||||
ItemCustomOutcomingPollMessageBinding::class.java ->
|
||||
(this.binding as ItemCustomOutcomingPollMessageBinding).bubble
|
||||
(viewHolder.binding as ItemCustomOutcomingPollMessageBinding).bubble
|
||||
ItemCustomOutcomingVoiceMessageBinding::class.java ->
|
||||
(this.binding as ItemCustomOutcomingVoiceMessageBinding).bubble
|
||||
(viewHolder.binding as ItemCustomOutcomingVoiceMessageBinding).bubble
|
||||
ItemCustomOutcomingLocationMessageBinding::class.java ->
|
||||
(this.binding as ItemCustomOutcomingLocationMessageBinding).bubble
|
||||
(viewHolder.binding as ItemCustomOutcomingLocationMessageBinding).bubble
|
||||
else -> null
|
||||
}?.let {
|
||||
RelativeLayout.LayoutParams(binding.root.layoutParams).apply {
|
||||
|
|
|
|||
|
|
@ -12,5 +12,4 @@ interface CommonMessageInterface {
|
|||
fun onLongClickReactions(chatMessage: ChatMessage)
|
||||
fun onClickReaction(chatMessage: ChatMessage, emoji: String)
|
||||
fun onOpenMessageActionsDialog(chatMessage: ChatMessage)
|
||||
fun openThread(chatMessage: ChatMessage)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ class IncomingDeckCardViewHolder(incomingView: View, payload: Any) :
|
|||
viewThemeUtils.talk.themeIncomingMessageBubble(bubble, message.isGrouped, message.isDeleted)
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod")
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||
if (message.parentMessageId != null && !message.isDeleted) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
|
|
@ -229,18 +229,10 @@ class IncomingDeckCardViewHolder(incomingView: View, payload: Any) :
|
|||
viewThemeUtils.talk.themeParentMessage(
|
||||
parentChatMessage,
|
||||
message,
|
||||
binding.messageQuote.quotedChatMessageView
|
||||
binding.messageQuote.quoteColoredView
|
||||
)
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.visibility =
|
||||
if (!message.isDeleted &&
|
||||
message.parentMessageId != null &&
|
||||
message.parentMessageId != chatActivity.conversationThreadId
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Error when processing parent message in view holder", e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,19 +110,6 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) :
|
|||
|
||||
itemView.setTag(R.string.replyable_message_view_tag, message.replyable)
|
||||
|
||||
val chatActivity = commonMessageInterface as ChatActivity
|
||||
val showThreadButton = chatActivity.conversationThreadId == null && message.isThread
|
||||
if (showThreadButton) {
|
||||
binding.reactions.threadButton.visibility = View.VISIBLE
|
||||
binding.reactions.threadButton.setContent {
|
||||
ThreadButtonComposable(
|
||||
onButtonClick = { openThread(message) }
|
||||
)
|
||||
}
|
||||
} else {
|
||||
binding.reactions.threadButton.visibility = View.GONE
|
||||
}
|
||||
|
||||
Reaction().showReactions(
|
||||
message,
|
||||
::clickOnReaction,
|
||||
|
|
@ -142,10 +129,6 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) :
|
|||
commonMessageInterface.onClickReaction(chatMessage, emoji)
|
||||
}
|
||||
|
||||
private fun openThread(chatMessage: ChatMessage) {
|
||||
commonMessageInterface.openThread(chatMessage)
|
||||
}
|
||||
|
||||
private fun setAvatarAndAuthorOnMessageItem(message: ChatMessage) {
|
||||
val actorName = message.actorDisplayName
|
||||
if (!actorName.isNullOrBlank()) {
|
||||
|
|
@ -174,7 +157,7 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) :
|
|||
viewThemeUtils.talk.themeIncomingMessageBubble(bubble, message.isGrouped, message.isDeleted)
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod")
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||
if (message.parentMessageId != null && !message.isDeleted) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
|
|
@ -221,18 +204,10 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) :
|
|||
viewThemeUtils.talk.themeParentMessage(
|
||||
parentChatMessage,
|
||||
message,
|
||||
binding.messageQuote.quotedChatMessageView
|
||||
binding.messageQuote.quoteColoredView
|
||||
)
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.visibility =
|
||||
if (!message.isDeleted &&
|
||||
message.parentMessageId != null &&
|
||||
message.parentMessageId != chatActivity.conversationThreadId
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Error when processing parent message in view holder", e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) :
|
|||
viewThemeUtils.talk.themeIncomingMessageBubble(bubble, message.isGrouped, message.isDeleted)
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod")
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||
if (message.parentMessageId != null && !message.isDeleted) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
|
|
@ -189,18 +189,10 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) :
|
|||
viewThemeUtils.talk.themeParentMessage(
|
||||
parentChatMessage,
|
||||
message,
|
||||
binding.messageQuote.quotedChatMessageView
|
||||
binding.messageQuote.quoteColoredView
|
||||
)
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.visibility =
|
||||
if (!message.isDeleted &&
|
||||
message.parentMessageId != null &&
|
||||
message.parentMessageId != chatActivity.conversationThreadId
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Error when processing parent message in view holder", e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,15 +81,6 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) :
|
|||
|
||||
setPollPreview(message)
|
||||
|
||||
val chatActivity = commonMessageInterface as ChatActivity
|
||||
Thread().showThreadPreview(
|
||||
chatActivity,
|
||||
message,
|
||||
threadBinding = binding.threadTitleWrapper,
|
||||
reactionsBinding = binding.reactions,
|
||||
openThread = { openThread(message) }
|
||||
)
|
||||
|
||||
Reaction().showReactions(
|
||||
message,
|
||||
::clickOnReaction,
|
||||
|
|
@ -109,10 +100,6 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) :
|
|||
commonMessageInterface.onClickReaction(chatMessage, emoji)
|
||||
}
|
||||
|
||||
private fun openThread(chatMessage: ChatMessage) {
|
||||
commonMessageInterface.openThread(chatMessage)
|
||||
}
|
||||
|
||||
private fun setPollPreview(message: ChatMessage) {
|
||||
var pollId: String? = null
|
||||
var pollName: String? = null
|
||||
|
|
@ -177,7 +164,7 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) :
|
|||
viewThemeUtils.talk.themeIncomingMessageBubble(bubble, message.isGrouped, message.isDeleted)
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod")
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||
if (message.parentMessageId != null && !message.isDeleted) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
|
|
@ -224,17 +211,9 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) :
|
|||
viewThemeUtils.talk.themeParentMessage(
|
||||
parentChatMessage,
|
||||
message,
|
||||
binding.messageQuote.quotedChatMessageView
|
||||
binding.messageQuote.quoteColoredView
|
||||
)
|
||||
binding.messageQuote.quotedChatMessageView.visibility =
|
||||
if (!message.isDeleted &&
|
||||
message.parentMessageId != null &&
|
||||
message.parentMessageId != chatActivity.conversationThreadId
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Error when processing parent message in view holder", e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import android.widget.ProgressBar;
|
|||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.nextcloud.talk.R;
|
||||
import com.nextcloud.talk.databinding.ItemCustomIncomingPreviewMessageBinding;
|
||||
import com.nextcloud.talk.databinding.ItemThreadTitleBinding;
|
||||
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
|
||||
import com.nextcloud.talk.chat.data.model.ChatMessage;
|
||||
import com.nextcloud.talk.utils.TextMatchers;
|
||||
|
|
@ -139,7 +138,4 @@ public class IncomingPreviewMessageViewHolder extends PreviewMessageViewHolder {
|
|||
|
||||
@Override
|
||||
public ReactionsInsideMessageBinding getReactionsBinding(){ return binding.reactions; }
|
||||
|
||||
@Override
|
||||
public ItemThreadTitleBinding getThreadsBinding(){ return binding.threadTitleWrapper; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,9 +146,6 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
|
|||
}
|
||||
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
binding.messageText.text = processedMessageText
|
||||
// just for debugging:
|
||||
// binding.messageText.text =
|
||||
// SpannableStringBuilder(processedMessageText).append(" (" + message.jsonMessageId + ")")
|
||||
} else {
|
||||
binding.messageText.visibility = View.GONE
|
||||
binding.checkboxContainer.visibility = View.VISIBLE
|
||||
|
|
@ -162,35 +159,16 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
|
|||
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
|
||||
}
|
||||
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
|
||||
|
||||
// parent message handling
|
||||
val chatActivity = commonMessageInterface as ChatActivity
|
||||
binding.messageQuote.quotedChatMessageView.visibility =
|
||||
if (!message.isDeleted &&
|
||||
message.parentMessageId != null &&
|
||||
message.parentMessageId != chatActivity.conversationThreadId
|
||||
) {
|
||||
processParentMessage(message)
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.setOnLongClickListener { l: View? ->
|
||||
commonMessageInterface.onOpenMessageActionsDialog(message)
|
||||
true
|
||||
if (!message.isDeleted && message.parentMessageId != null) {
|
||||
processParentMessage(message)
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
|
||||
}
|
||||
|
||||
itemView.setTag(R.string.replyable_message_view_tag, message.replyable)
|
||||
|
||||
Thread().showThreadPreview(
|
||||
chatActivity,
|
||||
message,
|
||||
threadBinding = binding.threadTitleWrapper,
|
||||
reactionsBinding = binding.reactions,
|
||||
openThread = { openThread(message) }
|
||||
)
|
||||
|
||||
Reaction().showReactions(
|
||||
message,
|
||||
::clickOnReaction,
|
||||
|
|
@ -313,10 +291,6 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
|
|||
commonMessageInterface.onClickReaction(chatMessage, emoji)
|
||||
}
|
||||
|
||||
private fun openThread(chatMessage: ChatMessage) {
|
||||
commonMessageInterface.openThread(chatMessage)
|
||||
}
|
||||
|
||||
private fun setAvatarAndAuthorOnMessageItem(message: ChatMessage) {
|
||||
val actorName = message.actorDisplayName
|
||||
if (!actorName.isNullOrBlank()) {
|
||||
|
|
@ -395,7 +369,7 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
|
|||
viewThemeUtils.talk.themeParentMessage(
|
||||
parentChatMessage,
|
||||
message,
|
||||
binding.messageQuote.quotedChatMessageView,
|
||||
binding.messageQuote.quoteColoredView,
|
||||
R.color.high_emphasis_text
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod")
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||
if (message.parentMessageId != null && !message.isDeleted) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
|
|
@ -351,20 +351,10 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
|||
viewThemeUtils.talk.themeParentMessage(
|
||||
parentChatMessage,
|
||||
message,
|
||||
binding.messageQuote.quotedChatMessageView
|
||||
binding.messageQuote.quoteColoredView
|
||||
)
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.visibility =
|
||||
if (!message.isDeleted &&
|
||||
message.parentMessageId != null &&
|
||||
message.parentMessageId != chatActivity.conversationThreadId
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Error when processing parent message in view holder", e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,10 +10,8 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.net.toUri
|
||||
import coil.load
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.chat.data.model.ChatMessage
|
||||
import com.nextcloud.talk.databinding.ReferenceInsideMessageBinding
|
||||
|
|
@ -76,18 +74,12 @@ class LinkPreview {
|
|||
}
|
||||
|
||||
val referenceThumbUrl = reference.openGraphObject?.thumb
|
||||
var backgroundId = R.drawable.link_text_background
|
||||
if (!referenceThumbUrl.isNullOrEmpty()) {
|
||||
binding.referenceThumbImage.visibility = View.VISIBLE
|
||||
binding.referenceThumbImage.load(referenceThumbUrl)
|
||||
} else {
|
||||
backgroundId = R.drawable.link_text_no_preview_background
|
||||
binding.referenceThumbImage.visibility = View.GONE
|
||||
}
|
||||
binding.referenceMetadataContainer.background = ContextCompat.getDrawable(
|
||||
binding.referenceMetadataContainer.context,
|
||||
backgroundId
|
||||
)
|
||||
|
||||
binding.referenceWrapper.setOnClickListener {
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, referenceLink!!.toUri())
|
||||
|
|
@ -103,6 +95,7 @@ class LinkPreview {
|
|||
binding.referenceDescription.visibility = View.GONE
|
||||
binding.referenceLink.visibility = View.GONE
|
||||
binding.referenceThumbImage.visibility = View.GONE
|
||||
binding.referenceIndentedSideBar.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ class OutcomingDeckCardViewHolder(outcomingView: View) :
|
|||
commonMessageInterface.onClickReaction(chatMessage, emoji)
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod")
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||
if (message.parentMessageId != null && !message.isDeleted) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
|
|
@ -217,18 +217,10 @@ class OutcomingDeckCardViewHolder(outcomingView: View) :
|
|||
viewThemeUtils.talk.themeParentMessage(
|
||||
parentChatMessage,
|
||||
message,
|
||||
binding.messageQuote.quotedChatMessageView
|
||||
binding.messageQuote.quoteColoredView
|
||||
)
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.visibility =
|
||||
if (!message.isDeleted &&
|
||||
message.parentMessageId != null &&
|
||||
message.parentMessageId != chatActivity.conversationThreadId
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Error when processing parent message in view holder", e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,19 +127,6 @@ class OutcomingLinkPreviewMessageViewHolder(outcomingView: View, payload: Any) :
|
|||
|
||||
itemView.setTag(R.string.replyable_message_view_tag, message.replyable)
|
||||
|
||||
val chatActivity = commonMessageInterface as ChatActivity
|
||||
val showThreadButton = chatActivity.conversationThreadId == null && message.isThread
|
||||
if (showThreadButton) {
|
||||
binding.reactions.threadButton.visibility = View.VISIBLE
|
||||
binding.reactions.threadButton.setContent {
|
||||
ThreadButtonComposable(
|
||||
onButtonClick = { openThread(message) }
|
||||
)
|
||||
}
|
||||
} else {
|
||||
binding.reactions.threadButton.visibility = View.GONE
|
||||
}
|
||||
|
||||
Reaction().showReactions(
|
||||
message,
|
||||
::clickOnReaction,
|
||||
|
|
@ -159,11 +146,7 @@ class OutcomingLinkPreviewMessageViewHolder(outcomingView: View, payload: Any) :
|
|||
commonMessageInterface.onClickReaction(chatMessage, emoji)
|
||||
}
|
||||
|
||||
private fun openThread(chatMessage: ChatMessage) {
|
||||
commonMessageInterface.openThread(chatMessage)
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod")
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||
if (message.parentMessageId != null && !message.isDeleted) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
|
|
@ -205,21 +188,9 @@ class OutcomingLinkPreviewMessageViewHolder(outcomingView: View, payload: Any) :
|
|||
)
|
||||
viewThemeUtils.talk.colorOutgoingQuoteText(binding.messageQuote.quotedMessage)
|
||||
viewThemeUtils.talk.colorOutgoingQuoteAuthorText(binding.messageQuote.quotedMessageAuthor)
|
||||
viewThemeUtils.talk.themeParentMessage(
|
||||
parentChatMessage,
|
||||
message,
|
||||
binding.messageQuote.quotedChatMessageView
|
||||
)
|
||||
viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView)
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.visibility =
|
||||
if (!message.isDeleted &&
|
||||
message.parentMessageId != null &&
|
||||
message.parentMessageId != chatActivity.conversationThreadId
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Error when processing parent message in view holder", e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ class OutcomingLocationMessageViewHolder(incomingView: View) :
|
|||
})
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod")
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||
if (message.parentMessageId != null && !message.isDeleted) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
|
|
@ -238,21 +238,9 @@ class OutcomingLocationMessageViewHolder(incomingView: View) :
|
|||
)
|
||||
viewThemeUtils.talk.colorOutgoingQuoteText(binding.messageQuote.quotedMessage)
|
||||
viewThemeUtils.talk.colorOutgoingQuoteAuthorText(binding.messageQuote.quotedMessageAuthor)
|
||||
viewThemeUtils.talk.themeParentMessage(
|
||||
parentChatMessage,
|
||||
message,
|
||||
binding.messageQuote.quotedChatMessageView
|
||||
)
|
||||
viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView)
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.visibility =
|
||||
if (!message.isDeleted &&
|
||||
message.parentMessageId != null &&
|
||||
message.parentMessageId != chatActivity.conversationThreadId
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Error when processing parent message in view holder", e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,10 +39,9 @@ import javax.inject.Inject
|
|||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) :
|
||||
MessageHolders.OutcomingTextMessageViewHolder<ChatMessage>(outcomingView, payload),
|
||||
AdjustableMessageHolderInterface {
|
||||
MessageHolders.OutcomingTextMessageViewHolder<ChatMessage>(outcomingView, payload) {
|
||||
|
||||
override val binding: ItemCustomOutcomingPollMessageBinding = ItemCustomOutcomingPollMessageBinding.bind(itemView)
|
||||
private val binding: ItemCustomOutcomingPollMessageBinding = ItemCustomOutcomingPollMessageBinding.bind(itemView)
|
||||
|
||||
@Inject
|
||||
lateinit var context: Context
|
||||
|
|
@ -104,15 +103,6 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) :
|
|||
|
||||
setPollPreview(message)
|
||||
|
||||
val chatActivity = commonMessageInterface as ChatActivity
|
||||
Thread().showThreadPreview(
|
||||
chatActivity,
|
||||
message,
|
||||
threadBinding = binding.threadTitleWrapper,
|
||||
reactionsBinding = binding.reactions,
|
||||
openThread = { openThread(message) }
|
||||
)
|
||||
|
||||
Reaction().showReactions(
|
||||
message,
|
||||
::clickOnReaction,
|
||||
|
|
@ -132,10 +122,6 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) :
|
|||
commonMessageInterface.onClickReaction(chatMessage, emoji)
|
||||
}
|
||||
|
||||
private fun openThread(chatMessage: ChatMessage) {
|
||||
commonMessageInterface.openThread(chatMessage)
|
||||
}
|
||||
|
||||
private fun setPollPreview(message: ChatMessage) {
|
||||
var pollId: String? = null
|
||||
var pollName: String? = null
|
||||
|
|
@ -172,7 +158,7 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) :
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod")
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||
if (message.parentMessageId != null && !message.isDeleted) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
|
|
@ -214,21 +200,9 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) :
|
|||
)
|
||||
viewThemeUtils.talk.colorOutgoingQuoteText(binding.messageQuote.quotedMessage)
|
||||
viewThemeUtils.talk.colorOutgoingQuoteAuthorText(binding.messageQuote.quotedMessageAuthor)
|
||||
viewThemeUtils.talk.themeParentMessage(
|
||||
parentChatMessage,
|
||||
message,
|
||||
binding.messageQuote.quotedChatMessageView
|
||||
)
|
||||
viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView)
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.visibility =
|
||||
if (!message.isDeleted &&
|
||||
message.parentMessageId != null &&
|
||||
message.parentMessageId != chatActivity.conversationThreadId
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Error when processing parent message in view holder", e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import android.widget.ProgressBar;
|
|||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.nextcloud.talk.R;
|
||||
import com.nextcloud.talk.databinding.ItemCustomOutcomingPreviewMessageBinding;
|
||||
import com.nextcloud.talk.databinding.ItemThreadTitleBinding;
|
||||
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
|
||||
import com.nextcloud.talk.chat.data.model.ChatMessage;
|
||||
import com.nextcloud.talk.utils.TextMatchers;
|
||||
|
|
@ -134,9 +133,6 @@ public class OutcomingPreviewMessageViewHolder extends PreviewMessageViewHolder
|
|||
@Override
|
||||
public ReactionsInsideMessageBinding getReactionsBinding() { return binding.reactions; }
|
||||
|
||||
@Override
|
||||
public ItemThreadTitleBinding getThreadsBinding(){ return binding.threadTitleWrapper; }
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public EmojiTextView getMessageCaption() { return binding.messageCaption; }
|
||||
|
|
|
|||
|
|
@ -160,9 +160,6 @@ class OutcomingTextMessageViewHolder(itemView: View) :
|
|||
binding.messageTime.layoutParams = layoutParams
|
||||
viewThemeUtils.platform.colorTextView(binding.messageText, ColorRole.ON_SURFACE_VARIANT)
|
||||
binding.messageText.text = processedMessageText
|
||||
// just for debugging:
|
||||
// binding.messageText.text =
|
||||
// SpannableStringBuilder(processedMessageText).append(" (" + message.jsonMessageId + ")")
|
||||
} else {
|
||||
binding.messageText.visibility = View.GONE
|
||||
binding.checkboxContainer.visibility = View.VISIBLE
|
||||
|
|
@ -177,23 +174,12 @@ class OutcomingTextMessageViewHolder(itemView: View) :
|
|||
}
|
||||
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
|
||||
setBubbleOnChatMessage(message)
|
||||
|
||||
// parent message handling
|
||||
val chatActivity = commonMessageInterface as ChatActivity
|
||||
binding.messageQuote.quotedChatMessageView.visibility =
|
||||
if (!message.isDeleted &&
|
||||
message.parentMessageId != null &&
|
||||
message.parentMessageId != chatActivity.conversationThreadId
|
||||
) {
|
||||
processParentMessage(message)
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.setOnLongClickListener { l: View? ->
|
||||
commonMessageInterface.onOpenMessageActionsDialog(message)
|
||||
true
|
||||
if (!message.isDeleted && message.parentMessageId != null) {
|
||||
processParentMessage(message)
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
|
||||
}
|
||||
|
||||
binding.checkMark.visibility = View.INVISIBLE
|
||||
|
|
@ -209,6 +195,8 @@ class OutcomingTextMessageViewHolder(itemView: View) :
|
|||
updateStatus(R.drawable.ic_check, context.resources?.getString(R.string.nc_message_sent))
|
||||
}
|
||||
|
||||
val chatActivity = commonMessageInterface as ChatActivity
|
||||
|
||||
chatActivity.lifecycleScope.launch {
|
||||
if (message.isTemporary && !networkMonitor.isOnline.value) {
|
||||
updateStatus(
|
||||
|
|
@ -220,14 +208,6 @@ class OutcomingTextMessageViewHolder(itemView: View) :
|
|||
|
||||
itemView.setTag(R.string.replyable_message_view_tag, message.replyable)
|
||||
|
||||
Thread().showThreadPreview(
|
||||
chatActivity,
|
||||
message,
|
||||
threadBinding = binding.threadTitleWrapper,
|
||||
reactionsBinding = binding.reactions,
|
||||
openThread = { openThread(message) }
|
||||
)
|
||||
|
||||
Reaction().showReactions(
|
||||
message,
|
||||
::clickOnReaction,
|
||||
|
|
@ -365,10 +345,6 @@ class OutcomingTextMessageViewHolder(itemView: View) :
|
|||
commonMessageInterface.onClickReaction(chatMessage, emoji)
|
||||
}
|
||||
|
||||
private fun openThread(chatMessage: ChatMessage) {
|
||||
commonMessageInterface.openThread(chatMessage)
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private fun processParentMessage(message: ChatMessage) {
|
||||
if (message.parentMessageId != null && !message.isDeleted) {
|
||||
|
|
@ -413,11 +389,7 @@ class OutcomingTextMessageViewHolder(itemView: View) :
|
|||
|
||||
viewThemeUtils.talk.colorOutgoingQuoteText(binding.messageQuote.quotedMessage)
|
||||
viewThemeUtils.talk.colorOutgoingQuoteAuthorText(binding.messageQuote.quotedMessageAuthor)
|
||||
viewThemeUtils.talk.themeParentMessage(
|
||||
parentChatMessage,
|
||||
message,
|
||||
binding.messageQuote.quotedChatMessageView
|
||||
)
|
||||
viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView)
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.setOnClickListener {
|
||||
chatActivity.jumpToQuotedMessage(parentChatMessage)
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
|||
binding.progressBar.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught", "Detekt.LongMethod")
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
|
||||
if (message.parentMessageId != null && !message.isDeleted) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
|
|
@ -349,21 +349,9 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
|||
)
|
||||
viewThemeUtils.talk.colorOutgoingQuoteText(binding.messageQuote.quotedMessage)
|
||||
viewThemeUtils.talk.colorOutgoingQuoteAuthorText(binding.messageQuote.quotedMessageAuthor)
|
||||
viewThemeUtils.talk.themeParentMessage(
|
||||
parentChatMessage,
|
||||
message,
|
||||
binding.messageQuote.quotedChatMessageView
|
||||
)
|
||||
viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView)
|
||||
|
||||
binding.messageQuote.quotedChatMessageView.visibility =
|
||||
if (!message.isDeleted &&
|
||||
message.parentMessageId != null &&
|
||||
message.parentMessageId != chatActivity.conversationThreadId
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Error when processing parent message in view holder", e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,10 +28,8 @@ 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.data.user.model.User
|
||||
import com.nextcloud.talk.databinding.ItemThreadTitleBinding
|
||||
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding
|
||||
import com.nextcloud.talk.extensions.loadChangelogBotAvatar
|
||||
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
|
||||
|
|
@ -80,7 +78,6 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
|
|||
var okHttpClient: OkHttpClient? = null
|
||||
open var progressBar: ProgressBar? = null
|
||||
open var reactionsBinding: ReactionsInsideMessageBinding? = null
|
||||
open var threadsBinding: ItemThreadTitleBinding? = null
|
||||
var fileViewerUtils: FileViewerUtils? = null
|
||||
var clickView: View? = null
|
||||
|
||||
|
|
@ -153,16 +150,6 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
|
|||
messageText.text = ""
|
||||
}
|
||||
itemView.setTag(R.string.replyable_message_view_tag, message.replyable)
|
||||
|
||||
val chatActivity = commonMessageInterface as ChatActivity
|
||||
Thread().showThreadPreview(
|
||||
chatActivity,
|
||||
message,
|
||||
threadBinding = threadsBinding!!,
|
||||
reactionsBinding = reactionsBinding!!,
|
||||
openThread = { openThread(message) }
|
||||
)
|
||||
|
||||
val paddingSide = DisplayUtils.convertDpToPixel(HORIZONTAL_REACTION_PADDING, context!!).toInt()
|
||||
Reaction().showReactions(
|
||||
message,
|
||||
|
|
@ -216,10 +203,6 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
|
|||
commonMessageInterface.onClickReaction(chatMessage, emoji)
|
||||
}
|
||||
|
||||
private fun openThread(chatMessage: ChatMessage) {
|
||||
commonMessageInterface.openThread(chatMessage)
|
||||
}
|
||||
|
||||
override fun getPayloadForImageLoader(message: ChatMessage?): Any? {
|
||||
if (message!!.selectedIndividualHashMap!!.containsKey(KEY_CONTACT_NAME)) {
|
||||
previewContainer.visibility = View.GONE
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import android.text.SpannableString
|
|||
import android.text.TextPaint
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.text.style.ClickableSpan
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
|
|
@ -77,10 +76,6 @@ class SystemMessageViewHolder(itemView: View) :
|
|||
R.drawable.shape_grouped_incoming_message
|
||||
)
|
||||
ViewCompat.setBackground(background, bubbleDrawable)
|
||||
binding.messageText.setTextSize(
|
||||
TypedValue.COMPLEX_UNIT_PX,
|
||||
resources.getDimension(R.dimen.chat_system_message_text_size)
|
||||
)
|
||||
var messageString: Spannable = SpannableString(message.text)
|
||||
if (message.messageParameters != null && message.messageParameters!!.size > 0) {
|
||||
for (key in message.messageParameters!!.keys) {
|
||||
|
|
@ -94,13 +89,7 @@ class SystemMessageViewHolder(itemView: View) :
|
|||
} else {
|
||||
individualMap["name"]
|
||||
}
|
||||
messageString =
|
||||
DisplayUtils.searchAndColor(
|
||||
messageString,
|
||||
searchText!!,
|
||||
mentionColor,
|
||||
resources.getDimensionPixelSize(R.dimen.chat_system_message_text_size)
|
||||
)
|
||||
messageString = DisplayUtils.searchAndColor(messageString, searchText!!, mentionColor)
|
||||
if (individualMap["link"] != null) {
|
||||
val displayName = individualMap["name"] ?: ""
|
||||
val link = (user.baseUrl + individualMap["link"])
|
||||
|
|
|
|||
|
|
@ -39,19 +39,19 @@ public class TalkMessagesListAdapter<M extends IMessage> extends MessagesListAda
|
|||
holderInstance.assignCommonMessageInterface(chatActivity);
|
||||
} else if (holder instanceof OutcomingTextMessageViewHolder holderInstance) {
|
||||
holderInstance.assignCommonMessageInterface(chatActivity);
|
||||
holderInstance.adjustIfNoteToSelf(chatActivity.getCurrentConversation());
|
||||
holderInstance.adjustIfNoteToSelf(holderInstance, chatActivity.getCurrentConversation());
|
||||
|
||||
} else if (holder instanceof IncomingLocationMessageViewHolder holderInstance) {
|
||||
holderInstance.assignCommonMessageInterface(chatActivity);
|
||||
} else if (holder instanceof OutcomingLocationMessageViewHolder holderInstance) {
|
||||
holderInstance.assignCommonMessageInterface(chatActivity);
|
||||
holderInstance.adjustIfNoteToSelf(chatActivity.getCurrentConversation());
|
||||
holderInstance.adjustIfNoteToSelf(holderInstance, chatActivity.getCurrentConversation());
|
||||
|
||||
} else if (holder instanceof IncomingLinkPreviewMessageViewHolder holderInstance) {
|
||||
holderInstance.assignCommonMessageInterface(chatActivity);
|
||||
} else if (holder instanceof OutcomingLinkPreviewMessageViewHolder holderInstance) {
|
||||
holderInstance.assignCommonMessageInterface(chatActivity);
|
||||
holderInstance.adjustIfNoteToSelf(chatActivity.getCurrentConversation());
|
||||
holderInstance.adjustIfNoteToSelf(holderInstance, chatActivity.getCurrentConversation());
|
||||
|
||||
} else if (holder instanceof IncomingVoiceMessageViewHolder holderInstance) {
|
||||
holderInstance.assignVoiceMessageInterface(chatActivity);
|
||||
|
|
@ -59,7 +59,7 @@ public class TalkMessagesListAdapter<M extends IMessage> extends MessagesListAda
|
|||
} else if (holder instanceof OutcomingVoiceMessageViewHolder holderInstance) {
|
||||
holderInstance.assignVoiceMessageInterface(chatActivity);
|
||||
holderInstance.assignCommonMessageInterface(chatActivity);
|
||||
holderInstance.adjustIfNoteToSelf(chatActivity.getCurrentConversation());
|
||||
holderInstance.adjustIfNoteToSelf(holderInstance, chatActivity.getCurrentConversation());
|
||||
|
||||
} else if (holder instanceof PreviewMessageViewHolder holderInstance) {
|
||||
holderInstance.assignPreviewMessageInterface(chatActivity);
|
||||
|
|
@ -72,13 +72,7 @@ public class TalkMessagesListAdapter<M extends IMessage> extends MessagesListAda
|
|||
holderInstance.assignCommonMessageInterface(chatActivity);
|
||||
} else if (holder instanceof OutcomingDeckCardViewHolder holderInstance) {
|
||||
holderInstance.assignCommonMessageInterface(chatActivity);
|
||||
holderInstance.adjustIfNoteToSelf(chatActivity.getCurrentConversation());
|
||||
|
||||
} else if (holder instanceof IncomingPollMessageViewHolder holderInstance) {
|
||||
holderInstance.assignCommonMessageInterface(chatActivity);
|
||||
} else if (holder instanceof OutcomingPollMessageViewHolder holderInstance) {
|
||||
holderInstance.assignCommonMessageInterface(chatActivity);
|
||||
holderInstance.adjustIfNoteToSelf(chatActivity.getCurrentConversation());
|
||||
holderInstance.adjustIfNoteToSelf(holderInstance, chatActivity.getCurrentConversation());
|
||||
}
|
||||
|
||||
super.onBindViewHolder(holder, position);
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2022 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.adapters.messages
|
||||
|
||||
import android.view.View
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.chat.ChatActivity
|
||||
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding
|
||||
import com.nextcloud.talk.chat.data.model.ChatMessage
|
||||
import com.nextcloud.talk.databinding.ItemThreadTitleBinding
|
||||
|
||||
class Thread {
|
||||
|
||||
fun showThreadPreview(
|
||||
chatActivity: ChatActivity,
|
||||
message: ChatMessage,
|
||||
threadBinding: ItemThreadTitleBinding,
|
||||
reactionsBinding: ReactionsInsideMessageBinding,
|
||||
openThread: (message: ChatMessage) -> Unit
|
||||
) {
|
||||
val isFirstMessageOfThreadInNormalChat = chatActivity.conversationThreadId == null && message.isThread
|
||||
if (isFirstMessageOfThreadInNormalChat) {
|
||||
threadBinding.threadTitleLayout.visibility = View.VISIBLE
|
||||
|
||||
threadBinding.threadTitleLayout.findViewById<androidx.emoji2.widget.EmojiTextView>(R.id.threadTitle).text =
|
||||
message.threadTitle
|
||||
|
||||
reactionsBinding.threadButton.visibility = View.VISIBLE
|
||||
|
||||
reactionsBinding.threadButton.setContent {
|
||||
ThreadButtonComposable(
|
||||
message.threadReplies ?: 0,
|
||||
onButtonClick = { openThread(message) }
|
||||
)
|
||||
}
|
||||
} else {
|
||||
threadBinding.threadTitleLayout.visibility = View.GONE
|
||||
reactionsBinding.threadButton.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.adapters.messages
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.nextcloud.talk.R
|
||||
|
||||
@Composable
|
||||
fun ThreadButtonComposable(replyAmount: Int = 0, onButtonClick: () -> Unit = {}) {
|
||||
val replyAmountText = if (replyAmount == 0) {
|
||||
stringResource(R.string.thread_reply)
|
||||
} else {
|
||||
pluralStringResource(
|
||||
R.plurals.thread_replies,
|
||||
replyAmount,
|
||||
replyAmount
|
||||
)
|
||||
}
|
||||
|
||||
OutlinedButton(
|
||||
onClick = onButtonClick,
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.height(24.dp),
|
||||
shape = RoundedCornerShape(9.dp),
|
||||
border = BorderStroke(1.dp, colorResource(R.color.grey_600)),
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
containerColor = Color.Transparent,
|
||||
contentColor = colorResource(R.color.grey_600)
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_reply),
|
||||
contentDescription = stringResource(R.string.open_thread),
|
||||
modifier = Modifier
|
||||
.size(20.dp)
|
||||
.padding(start = 5.dp, end = 2.dp),
|
||||
tint = colorResource(R.color.grey_600)
|
||||
)
|
||||
Text(
|
||||
text = replyAmountText,
|
||||
modifier = Modifier
|
||||
.padding(end = 5.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ThreadButtonPreviewMultipleReplies() {
|
||||
ThreadButtonComposable(2)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ThreadButtonPreviewOneReply() {
|
||||
ThreadButtonComposable(1)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ThreadButtonPreviewZeroReplies() {
|
||||
ThreadButtonComposable(0)
|
||||
}
|
||||
|
|
@ -19,8 +19,6 @@ import com.nextcloud.talk.models.json.participants.TalkBan
|
|||
import com.nextcloud.talk.models.json.participants.TalkBanOverall
|
||||
import com.nextcloud.talk.models.json.profile.ProfileOverall
|
||||
import com.nextcloud.talk.models.json.testNotification.TestNotificationOverall
|
||||
import com.nextcloud.talk.models.json.threads.ThreadOverall
|
||||
import com.nextcloud.talk.models.json.threads.ThreadsOverall
|
||||
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody
|
||||
|
|
@ -148,8 +146,7 @@ interface NcApiCoroutines {
|
|||
@Field("actorDisplayName") actorDisplayName: String,
|
||||
@Field("replyTo") replyTo: Int,
|
||||
@Field("silent") sendWithoutNotification: Boolean,
|
||||
@Field("referenceId") referenceId: String,
|
||||
@Field("threadTitle") threadTitle: String?
|
||||
@Field("referenceId") referenceId: String
|
||||
): ChatOverallSingleMessage
|
||||
|
||||
@FormUrlEncoded
|
||||
|
|
@ -288,22 +285,4 @@ interface NcApiCoroutines {
|
|||
|
||||
@DELETE
|
||||
suspend fun unbindRoom(@Header("Authorization") authorization: String, @Url url: String): GenericOverall
|
||||
|
||||
@GET
|
||||
suspend fun getThreads(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Url url: String,
|
||||
@Query("limit") limit: Int?
|
||||
): ThreadsOverall
|
||||
|
||||
@GET
|
||||
suspend fun getThread(@Header("Authorization") authorization: String, @Url url: String): ThreadOverall
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST
|
||||
suspend fun setThreadNotificationLevel(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Url url: String,
|
||||
@Field("level") level: Int
|
||||
): ThreadOverall
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ fun ParticipantTile(
|
|||
modifier: Modifier = Modifier,
|
||||
isVoiceOnlyCall: Boolean
|
||||
) {
|
||||
val colorInt = ColorGenerator.usernameToColor(participantUiState.nick)
|
||||
val colorInt = ColorGenerator.shared.usernameToColor(participantUiState.nick)
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
|
|
|
|||
|
|
@ -24,14 +24,12 @@ import android.content.pm.PackageManager
|
|||
import android.content.res.AssetFileDescriptor
|
||||
import android.database.Cursor
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.location.LocationManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.provider.ContactsContract
|
||||
import android.provider.MediaStore
|
||||
import android.provider.Settings
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
|
|
@ -40,7 +38,6 @@ import android.view.Menu
|
|||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import android.widget.AbsListView
|
||||
import android.widget.FrameLayout
|
||||
|
|
@ -59,12 +56,7 @@ import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.content.PermissionChecker
|
||||
import androidx.core.content.PermissionChecker.PERMISSION_GRANTED
|
||||
|
|
@ -156,13 +148,11 @@ import com.nextcloud.talk.models.json.chat.ReadStatus
|
|||
import com.nextcloud.talk.models.json.conversations.ConversationEnums
|
||||
import com.nextcloud.talk.models.json.participants.Participant
|
||||
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall
|
||||
import com.nextcloud.talk.models.json.threads.ThreadInfo
|
||||
import com.nextcloud.talk.polls.ui.PollCreateDialogFragment
|
||||
import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
|
||||
import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
|
||||
import com.nextcloud.talk.signaling.SignalingMessageReceiver
|
||||
import com.nextcloud.talk.signaling.SignalingMessageSender
|
||||
import com.nextcloud.talk.threadsoverview.ThreadsOverviewActivity
|
||||
import com.nextcloud.talk.translate.ui.TranslateActivity
|
||||
import com.nextcloud.talk.ui.PlaybackSpeed
|
||||
import com.nextcloud.talk.ui.PlaybackSpeedControl
|
||||
|
|
@ -207,7 +197,6 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_RECORDING_STATE
|
|||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_START_CALL_AFTER_ROOM_SWITCH
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SWITCH_TO_ROOM
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_THREAD_ID
|
||||
import com.nextcloud.talk.utils.permissions.PlatformPermissionUtil
|
||||
import com.nextcloud.talk.utils.rx.DisposableSet
|
||||
import com.nextcloud.talk.utils.singletons.ApplicationWideCurrentRoomHolder
|
||||
|
|
@ -246,7 +235,6 @@ import java.util.Locale
|
|||
import java.util.concurrent.ExecutionException
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.roundToInt
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
|
|
@ -291,9 +279,6 @@ class ChatActivity :
|
|||
|
||||
private var chatMenu: Menu? = null
|
||||
|
||||
private var overflowMenuHostView: ComposeView? = null
|
||||
private var isThreadMenuExpanded by mutableStateOf(false)
|
||||
|
||||
private val startSelectContactForResult = registerForActivityResult(
|
||||
ActivityResultContracts
|
||||
.StartActivityForResult()
|
||||
|
|
@ -365,8 +350,6 @@ class ChatActivity :
|
|||
|
||||
var sessionIdAfterRoomJoined: String? = null
|
||||
lateinit var roomToken: String
|
||||
var conversationThreadId: Long? = null
|
||||
var conversationThreadInfo: ThreadInfo? = null
|
||||
var conversationUser: User? = null
|
||||
lateinit var spreedCapabilities: SpreedCapability
|
||||
var chatApiVersion: Int = 1
|
||||
|
|
@ -408,14 +391,8 @@ class ChatActivity :
|
|||
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (isChatThread()) {
|
||||
isEnabled = false
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
} else {
|
||||
val intent = Intent(this@ChatActivity, ConversationsListActivity::class.java)
|
||||
intent.putExtras(Bundle())
|
||||
startActivity(intent)
|
||||
}
|
||||
val intent = Intent(this@ChatActivity, ConversationsListActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -484,20 +461,18 @@ class ChatActivity :
|
|||
setContentView(binding.root)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.chatContainer) { view, insets ->
|
||||
val systemBarInsets = insets.getInsets(
|
||||
WindowInsetsCompat.Type.systemBars() or
|
||||
WindowInsetsCompat.Type.displayCutout()
|
||||
)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.chat_container)) { view, insets ->
|
||||
val statusBarInsets = insets.getInsets(WindowInsetsCompat.Type.statusBars())
|
||||
val navBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
|
||||
val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime())
|
||||
|
||||
val isKeyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
|
||||
val bottomPadding = if (isKeyboardVisible) imeInsets.bottom else systemBarInsets.bottom
|
||||
val bottomPadding = if (isKeyboardVisible) imeInsets.bottom else navBarInsets.bottom
|
||||
|
||||
view.setPadding(
|
||||
systemBarInsets.left,
|
||||
systemBarInsets.top,
|
||||
systemBarInsets.right,
|
||||
view.paddingLeft,
|
||||
statusBarInsets.top,
|
||||
view.paddingRight,
|
||||
bottomPadding
|
||||
)
|
||||
WindowInsetsCompat.CONSUMED
|
||||
|
|
@ -519,20 +494,9 @@ class ChatActivity :
|
|||
chatViewModel.initData(
|
||||
credentials!!,
|
||||
urlForChatting,
|
||||
roomToken,
|
||||
conversationThreadId
|
||||
roomToken
|
||||
)
|
||||
|
||||
conversationThreadId?.let {
|
||||
val threadUrl = ApiUtils.getUrlForThread(
|
||||
version = 1,
|
||||
baseUrl = conversationUser!!.baseUrl,
|
||||
token = roomToken,
|
||||
threadId = it.toInt()
|
||||
)
|
||||
chatViewModel.getThread(credentials, threadUrl)
|
||||
}
|
||||
|
||||
messageInputFragment = getMessageInputFragment()
|
||||
messageInputViewModel = ViewModelProvider(this, viewModelFactory)[MessageInputViewModel::class.java]
|
||||
messageInputViewModel.setData(chatViewModel.getChatRepository())
|
||||
|
|
@ -557,7 +521,6 @@ class ChatActivity :
|
|||
return MessageInputFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putString(CONVERSATION_INTERNAL_ID, internalId)
|
||||
putString(BundleKeys.KEY_SHARED_TEXT, sharedText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -586,12 +549,6 @@ class ChatActivity :
|
|||
|
||||
roomToken = extras?.getString(KEY_ROOM_TOKEN).orEmpty()
|
||||
|
||||
conversationThreadId = if (extras?.containsKey(KEY_THREAD_ID) == true) {
|
||||
extras.getLong(KEY_THREAD_ID)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
sharedText = extras?.getString(BundleKeys.KEY_SHARED_TEXT).orEmpty()
|
||||
|
||||
Log.d(TAG, " roomToken = $roomToken")
|
||||
|
|
@ -714,8 +671,7 @@ class ChatActivity :
|
|||
joinRoomWithPassword()
|
||||
|
||||
if (conversationUser?.userId != "?" &&
|
||||
hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MENTION_FLAG) &&
|
||||
!isChatThread()
|
||||
hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MENTION_FLAG)
|
||||
) {
|
||||
binding.chatToolbar.setOnClickListener { _ -> showConversationInfoScreen() }
|
||||
}
|
||||
|
|
@ -981,7 +937,6 @@ class ChatActivity :
|
|||
var chatMessageList = triple.third
|
||||
|
||||
chatMessageList = handleSystemMessages(chatMessageList)
|
||||
chatMessageList = handleThreadMessages(chatMessageList)
|
||||
if (chatMessageList.isEmpty()) {
|
||||
return@onEach
|
||||
}
|
||||
|
|
@ -1152,11 +1107,6 @@ class ChatActivity :
|
|||
|
||||
chatViewModel.getVoiceRecordingInProgress.observe(this) { voiceRecordingInProgress ->
|
||||
VibrationUtils.vibrateShort(context)
|
||||
if (voiceRecordingInProgress) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
} else {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
binding.voiceRecordingLock.visibility = if (
|
||||
voiceRecordingInProgress &&
|
||||
chatViewModel.getVoiceRecordingLocked.value != true
|
||||
|
|
@ -1183,7 +1133,6 @@ class ChatActivity :
|
|||
|
||||
chatMenu?.removeItem(R.id.conversation_event)
|
||||
}
|
||||
|
||||
is ChatViewModel.UnbindRoomUiState.Error -> {
|
||||
Snackbar.make(
|
||||
binding.root,
|
||||
|
|
@ -1191,8 +1140,7 @@ class ChatActivity :
|
|||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1286,25 +1234,6 @@ class ChatActivity :
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.lifecycleScope.launch {
|
||||
chatViewModel.threadRetrieveState.collect { uiState ->
|
||||
when (uiState) {
|
||||
ChatViewModel.ThreadRetrieveUiState.None -> {
|
||||
}
|
||||
|
||||
is ChatViewModel.ThreadRetrieveUiState.Error -> {
|
||||
Log.e(TAG, "Error when retrieving thread", uiState.exception)
|
||||
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
is ChatViewModel.ThreadRetrieveUiState.Success -> {
|
||||
conversationThreadInfo = uiState.thread
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeUnreadMessagesMarker() {
|
||||
|
|
@ -1574,7 +1503,6 @@ class ChatActivity :
|
|||
} while (true && pos >= 0)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun initMessageHolders(): MessageHolders {
|
||||
val messageHolders = MessageHolders()
|
||||
val profileBottomSheet = ProfileBottomSheet(ncApi, conversationUser!!, viewThemeUtils)
|
||||
|
|
@ -2343,26 +2271,11 @@ class ChatActivity :
|
|||
BuildConfig.APPLICATION_ID,
|
||||
File(file.absolutePath)
|
||||
)
|
||||
uploadFile(
|
||||
fileUri = shareUri.toString(),
|
||||
isVoiceMessage = false,
|
||||
caption = "",
|
||||
roomToken = roomToken,
|
||||
replyToMessageId = getReplyToMessageId(),
|
||||
displayName = currentConversation?.displayName ?: ""
|
||||
)
|
||||
uploadFile(shareUri.toString(), false)
|
||||
}
|
||||
cursor?.close()
|
||||
}
|
||||
|
||||
fun getReplyToMessageId(): Int {
|
||||
var replyMessageId = messageInputViewModel.getReplyChatMessage.value?.id?.toInt()
|
||||
if (replyMessageId == null || replyMessageId == 0) {
|
||||
replyMessageId = conversationThreadInfo?.thread?.id ?: 0
|
||||
}
|
||||
return replyMessageId
|
||||
}
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
private fun onPickCameraResult(intent: Intent?) {
|
||||
try {
|
||||
|
|
@ -2534,27 +2447,35 @@ class ChatActivity :
|
|||
private fun uploadFiles(files: MutableList<String>, caption: String = "") {
|
||||
for (i in 0 until files.size) {
|
||||
if (i == files.size - 1) {
|
||||
uploadFile(
|
||||
fileUri = files[i],
|
||||
isVoiceMessage = false,
|
||||
caption = caption,
|
||||
roomToken = roomToken,
|
||||
replyToMessageId = getReplyToMessageId(),
|
||||
displayName = currentConversation?.displayName!!
|
||||
)
|
||||
uploadFile(files[i], false, caption)
|
||||
} else {
|
||||
uploadFile(
|
||||
fileUri = files[i],
|
||||
isVoiceMessage = false,
|
||||
caption = "",
|
||||
roomToken = roomToken,
|
||||
replyToMessageId = getReplyToMessageId(),
|
||||
displayName = currentConversation?.displayName!!
|
||||
)
|
||||
uploadFile(files[i], false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun uploadFile(fileUri: String, isVoiceMessage: Boolean, caption: String = "", token: String = "") {
|
||||
var metaData = ""
|
||||
var room = ""
|
||||
|
||||
if (!participantPermissions.hasChatPermission()) {
|
||||
Log.w(TAG, "uploading file(s) is forbidden because of missing attendee permissions")
|
||||
return
|
||||
}
|
||||
|
||||
if (isVoiceMessage) {
|
||||
metaData = VOICE_MESSAGE_META_DATA
|
||||
}
|
||||
|
||||
if (caption != "") {
|
||||
metaData = "{\"caption\":\"$caption\"}"
|
||||
}
|
||||
|
||||
if (token == "") room = roomToken else room = token
|
||||
|
||||
chatViewModel.uploadFile(fileUri, room, currentConversation?.displayName!!, metaData)
|
||||
}
|
||||
|
||||
fun showGalleryPicker() {
|
||||
pickMultipleMedia.launch(PickVisualMediaRequest(PickVisualMedia.ImageAndVideo))
|
||||
}
|
||||
|
|
@ -2595,67 +2516,10 @@ class ChatActivity :
|
|||
fun showShareLocationScreen() {
|
||||
Log.d(TAG, "showShareLocationScreen")
|
||||
|
||||
val locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
|
||||
val isGpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
||||
|
||||
if (!isGpsEnabled) {
|
||||
showLocationServicesDisabledDialog()
|
||||
} else if (!permissionUtil.isLocationPermissionGranted()) {
|
||||
showLocationPermissionDeniedDialog()
|
||||
}
|
||||
|
||||
if (permissionUtil.isLocationPermissionGranted() && isGpsEnabled) {
|
||||
val intent = Intent(this, LocationPickerActivity::class.java)
|
||||
intent.putExtra(KEY_ROOM_TOKEN, roomToken)
|
||||
intent.putExtra(BundleKeys.KEY_CHAT_API_VERSION, chatApiVersion)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLocationServicesDisabledDialog() {
|
||||
val title = resources.getString(R.string.location_services_disabled)
|
||||
val explanation = resources.getString(R.string.location_services_disabled_msg)
|
||||
val positive = resources.getString(R.string.nc_permissions_settings)
|
||||
val cancel = resources.getString(R.string.nc_cancel)
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(this)
|
||||
.setTitle(title)
|
||||
.setMessage(explanation)
|
||||
.setPositiveButton(positive) { _, _ ->
|
||||
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
|
||||
startActivity(intent)
|
||||
}
|
||||
.setNegativeButton(cancel, null)
|
||||
|
||||
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, dialogBuilder)
|
||||
val dialog = dialogBuilder.show()
|
||||
viewThemeUtils.platform.colorTextButtons(
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
|
||||
)
|
||||
}
|
||||
|
||||
private fun showLocationPermissionDeniedDialog() {
|
||||
val title = resources.getString(R.string.location_permission_denied)
|
||||
val explanation = resources.getString(R.string.location_permission_denied_msg)
|
||||
val positive = resources.getString(R.string.nc_permissions_settings)
|
||||
val cancel = resources.getString(R.string.nc_cancel)
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(this)
|
||||
.setTitle(title)
|
||||
.setMessage(explanation)
|
||||
.setPositiveButton(positive) { _, _ ->
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||
data = Uri.fromParts("package", packageName, null)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
.setNegativeButton(cancel, null)
|
||||
|
||||
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, dialogBuilder)
|
||||
val dialog = dialogBuilder.show()
|
||||
viewThemeUtils.platform.colorTextButtons(
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
|
||||
)
|
||||
val intent = Intent(this, LocationPickerActivity::class.java)
|
||||
intent.putExtra(KEY_ROOM_TOKEN, roomToken)
|
||||
intent.putExtra(BundleKeys.KEY_CHAT_API_VERSION, chatApiVersion)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun showConversationInfoScreen() {
|
||||
|
|
@ -2723,7 +2587,6 @@ class ChatActivity :
|
|||
if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) {
|
||||
mentionAutocomplete?.dismissPopup()
|
||||
}
|
||||
adapter = null
|
||||
}
|
||||
|
||||
private fun isActivityNotChangingConfigurations(): Boolean = !isChangingConfigurations
|
||||
|
|
@ -2737,9 +2600,7 @@ class ChatActivity :
|
|||
viewThemeUtils.platform.colorTextView(title, ColorRole.ON_SURFACE)
|
||||
|
||||
title.text =
|
||||
if (isChatThread()) {
|
||||
conversationThreadInfo?.thread?.title
|
||||
} else if (currentConversation?.displayName != null) {
|
||||
if (currentConversation?.displayName != null) {
|
||||
try {
|
||||
EmojiCompat.get().process(currentConversation?.displayName as CharSequence).toString()
|
||||
} catch (e: java.lang.IllegalStateException) {
|
||||
|
|
@ -2750,16 +2611,7 @@ class ChatActivity :
|
|||
""
|
||||
}
|
||||
|
||||
if (isChatThread()) {
|
||||
val replyAmount = conversationThreadInfo?.thread?.numReplies ?: 0
|
||||
val repliesAmountTitle = resources.getQuantityString(
|
||||
R.plurals.thread_replies,
|
||||
replyAmount,
|
||||
replyAmount
|
||||
)
|
||||
|
||||
statusMessageViewContents(repliesAmountTitle)
|
||||
} else if (currentConversation?.type == ConversationEnums.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
|
||||
|
|
@ -3249,21 +3101,11 @@ class ChatActivity :
|
|||
}
|
||||
|
||||
val searchItem = menu.findItem(R.id.conversation_search)
|
||||
|
||||
searchItem.isVisible = CapabilitiesUtil.isUnifiedSearchAvailable(spreedCapabilities) &&
|
||||
currentConversation!!.remoteServer.isNullOrEmpty() &&
|
||||
!isChatThread()
|
||||
currentConversation!!.remoteServer.isNullOrEmpty()
|
||||
|
||||
val sharedItemsItem = menu.findItem(R.id.shared_items)
|
||||
sharedItemsItem.isVisible = !isChatThread()
|
||||
|
||||
val conversationInfoItem = menu.findItem(R.id.conversation_info)
|
||||
conversationInfoItem.isVisible = !isChatThread()
|
||||
|
||||
val showThreadsItem = menu.findItem(R.id.show_threads)
|
||||
showThreadsItem.isVisible = !isChatThread() &&
|
||||
hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.THREADS)
|
||||
|
||||
if (CapabilitiesUtil.isAbleToCall(spreedCapabilities) && !isChatThread()) {
|
||||
if (CapabilitiesUtil.isAbleToCall(spreedCapabilities)) {
|
||||
conversationVoiceCallMenuItem = menu.findItem(R.id.conversation_voice_call)
|
||||
conversationVideoMenuItem = menu.findItem(R.id.conversation_video_call)
|
||||
|
||||
|
|
@ -3294,24 +3136,10 @@ class ChatActivity :
|
|||
menu.removeItem(R.id.conversation_video_call)
|
||||
menu.removeItem(R.id.conversation_voice_call)
|
||||
}
|
||||
|
||||
handleThreadNotificationIcon(menu.findItem(R.id.thread_notifications))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun handleThreadNotificationIcon(threadNotificationItem: MenuItem) {
|
||||
threadNotificationItem.isVisible = isChatThread() &&
|
||||
hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.THREADS)
|
||||
|
||||
val threadNotificationIcon = when (conversationThreadInfo?.attendee?.notificationLevel) {
|
||||
1 -> R.drawable.outline_notifications_active_24
|
||||
3 -> R.drawable.ic_baseline_notifications_off_24
|
||||
else -> R.drawable.baseline_notifications_24
|
||||
}
|
||||
threadNotificationItem.icon = ContextCompat.getDrawable(context, threadNotificationIcon)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean =
|
||||
when (item.itemId) {
|
||||
R.id.conversation_video_call -> {
|
||||
|
|
@ -3341,105 +3169,15 @@ class ChatActivity :
|
|||
|
||||
R.id.conversation_event -> {
|
||||
val anchorView = findViewById<View>(R.id.conversation_event)
|
||||
showConversationEventMenu(anchorView)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.show_threads -> {
|
||||
openThreadsOverview()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.thread_notifications -> {
|
||||
showThreadNotificationMenu()
|
||||
showPopupWindow(anchorView)
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
@Suppress("Detekt.LongMethod")
|
||||
private fun showThreadNotificationMenu() {
|
||||
fun setThreadNotificationLevel(level: Int) {
|
||||
val threadNotificationUrl = ApiUtils.getUrlForThreadNotificationLevel(
|
||||
version = 1,
|
||||
baseUrl = conversationUser!!.baseUrl,
|
||||
token = roomToken,
|
||||
threadId = conversationThreadId!!.toInt()
|
||||
)
|
||||
chatViewModel.setThreadNotificationLevel(credentials!!, threadNotificationUrl, level)
|
||||
}
|
||||
|
||||
if (overflowMenuHostView == null) {
|
||||
val threadNotificationsAnchor: View? = findViewById(R.id.thread_notifications)
|
||||
|
||||
val colorScheme = viewThemeUtils.getColorScheme(this)
|
||||
|
||||
overflowMenuHostView = ComposeView(this).apply {
|
||||
setContent {
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme
|
||||
) {
|
||||
val items = listOf(
|
||||
MenuItemData(
|
||||
title = context.resources.getString(R.string.notifications_default),
|
||||
subtitle = context.resources.getString(
|
||||
R.string.notifications_default_description
|
||||
),
|
||||
icon = R.drawable.baseline_notifications_24,
|
||||
onClick = {
|
||||
setThreadNotificationLevel(0)
|
||||
}
|
||||
),
|
||||
MenuItemData(
|
||||
title = context.resources.getString(R.string.notification_all_messages),
|
||||
subtitle = null,
|
||||
icon = R.drawable.outline_notifications_active_24,
|
||||
onClick = {
|
||||
setThreadNotificationLevel(1)
|
||||
}
|
||||
),
|
||||
MenuItemData(
|
||||
title = context.resources.getString(R.string.notification_mention_only),
|
||||
subtitle = null,
|
||||
icon = R.drawable.baseline_notifications_24,
|
||||
onClick = {
|
||||
setThreadNotificationLevel(2)
|
||||
}
|
||||
),
|
||||
MenuItemData(
|
||||
title = context.resources.getString(R.string.notification_off),
|
||||
subtitle = null,
|
||||
icon = R.drawable.ic_baseline_notifications_off_24,
|
||||
onClick = {
|
||||
setThreadNotificationLevel(3)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
OverflowMenu(
|
||||
anchor = threadNotificationsAnchor,
|
||||
expanded = isThreadMenuExpanded,
|
||||
items = items,
|
||||
onDismiss = { isThreadMenuExpanded = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addContentView(
|
||||
overflowMenuHostView,
|
||||
CoordinatorLayout.LayoutParams(
|
||||
CoordinatorLayout.LayoutParams.MATCH_PARENT,
|
||||
CoordinatorLayout.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
)
|
||||
}
|
||||
isThreadMenuExpanded = true
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
private fun showConversationEventMenu(anchorView: View) {
|
||||
private fun showPopupWindow(anchorView: View) {
|
||||
val popupView = layoutInflater.inflate(R.layout.item_event_schedule, null)
|
||||
|
||||
val subtitleTextView = popupView.findViewById<TextView>(R.id.meetingTime)
|
||||
|
|
@ -3628,7 +3366,7 @@ class ChatActivity :
|
|||
}
|
||||
|
||||
private fun handleSystemMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> {
|
||||
val chatMessageMap = chatMessageList.associateBy { it.id }.toMutableMap()
|
||||
val chatMessageMap = chatMessageList.map { it.id to it }.toMap().toMutableMap()
|
||||
|
||||
val chatMessageIterator = chatMessageMap.iterator()
|
||||
while (chatMessageIterator.hasNext()) {
|
||||
|
|
@ -3637,8 +3375,7 @@ class ChatActivity :
|
|||
if (isInfoMessageAboutDeletion(currentMessage) ||
|
||||
isReactionsMessage(currentMessage) ||
|
||||
isPollVotedMessage(currentMessage) ||
|
||||
isEditMessage(currentMessage) ||
|
||||
isThreadCreatedMessage(currentMessage)
|
||||
isEditMessage(currentMessage)
|
||||
) {
|
||||
chatMessageIterator.remove()
|
||||
}
|
||||
|
|
@ -3647,56 +3384,29 @@ class ChatActivity :
|
|||
}
|
||||
|
||||
private fun handleExpandableSystemMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> {
|
||||
val chatMessageMap = chatMessageList.associateBy { it.id }.toMutableMap()
|
||||
val chatMessageMap = chatMessageList.map { it.id to it }.toMap().toMutableMap()
|
||||
val chatMessageIterator = chatMessageMap.iterator()
|
||||
|
||||
while (chatMessageIterator.hasNext()) {
|
||||
val currentMessage = chatMessageIterator.next()
|
||||
chatMessageMap[currentMessage.value.previousMessageId.toString()]?.let { previousMessage ->
|
||||
if (isSystemMessage(currentMessage.value) &&
|
||||
previousMessage.systemMessageType == currentMessage.value.systemMessageType &&
|
||||
isSameDayMessages(previousMessage, currentMessage.value)
|
||||
) {
|
||||
groupSystemMessages(previousMessage, currentMessage.value)
|
||||
|
||||
val previousMessage = chatMessageMap[currentMessage.value.previousMessageId.toString()]
|
||||
if (isSystemMessage(currentMessage.value) &&
|
||||
previousMessage?.systemMessageType == currentMessage.value.systemMessageType
|
||||
) {
|
||||
previousMessage?.expandableParent = true
|
||||
currentMessage.value.expandableParent = false
|
||||
|
||||
if (currentMessage.value.lastItemOfExpandableGroup == 0) {
|
||||
currentMessage.value.lastItemOfExpandableGroup = currentMessage.value.jsonMessageId
|
||||
}
|
||||
|
||||
previousMessage?.lastItemOfExpandableGroup = currentMessage.value.lastItemOfExpandableGroup
|
||||
previousMessage?.expandableChildrenAmount = currentMessage.value.expandableChildrenAmount + 1
|
||||
}
|
||||
}
|
||||
return chatMessageMap.values.toList()
|
||||
}
|
||||
|
||||
private fun groupSystemMessages(previousMessage: ChatMessage, currentMessage: ChatMessage) {
|
||||
previousMessage.expandableParent = true
|
||||
currentMessage.expandableParent = false
|
||||
|
||||
if (currentMessage.lastItemOfExpandableGroup == 0) {
|
||||
currentMessage.lastItemOfExpandableGroup = currentMessage.jsonMessageId
|
||||
}
|
||||
|
||||
previousMessage.lastItemOfExpandableGroup = currentMessage.lastItemOfExpandableGroup
|
||||
previousMessage.expandableChildrenAmount = currentMessage.expandableChildrenAmount + 1
|
||||
}
|
||||
|
||||
private fun handleThreadMessages(chatMessageList: List<ChatMessage>): List<ChatMessage> {
|
||||
fun isThreadChildMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean =
|
||||
currentMessage.value.isThread &&
|
||||
currentMessage.value.threadId?.toInt() != currentMessage.value.jsonMessageId
|
||||
|
||||
val chatMessageMap = chatMessageList.associateBy { it.id }.toMutableMap()
|
||||
|
||||
if (conversationThreadId == null) {
|
||||
val chatMessageIterator = chatMessageMap.iterator()
|
||||
while (chatMessageIterator.hasNext()) {
|
||||
val currentMessage = chatMessageIterator.next()
|
||||
|
||||
if (isThreadChildMessage(currentMessage)) {
|
||||
chatMessageIterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chatMessageMap.values.toList()
|
||||
}
|
||||
|
||||
private fun isInfoMessageAboutDeletion(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean =
|
||||
currentMessage.value.parentMessageId != null &&
|
||||
currentMessage.value.systemMessageType == ChatMessage
|
||||
|
|
@ -3707,9 +3417,6 @@ class ChatActivity :
|
|||
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_DELETED ||
|
||||
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_REVOKED
|
||||
|
||||
private fun isThreadCreatedMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean =
|
||||
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.THREAD_CREATED
|
||||
|
||||
private fun isEditMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean =
|
||||
currentMessage.value.parentMessageId != null &&
|
||||
currentMessage.value.systemMessageType == ChatMessage
|
||||
|
|
@ -3782,10 +3489,6 @@ class ChatActivity :
|
|||
}
|
||||
}
|
||||
|
||||
override fun openThread(chatMessage: ChatMessage) {
|
||||
openThread(chatMessage.jsonMessageId.toLong())
|
||||
}
|
||||
|
||||
override fun onLongClickReactions(chatMessage: ChatMessage) {
|
||||
ShowReactionsDialog(
|
||||
this,
|
||||
|
|
@ -4058,14 +3761,7 @@ class ChatActivity :
|
|||
val type = message.getCalculateMessageType()
|
||||
when (type) {
|
||||
ChatMessage.MessageType.VOICE_MESSAGE -> {
|
||||
uploadFile(
|
||||
shareUri.toString(),
|
||||
true,
|
||||
roomToken = roomToken,
|
||||
caption = "",
|
||||
replyToMessageId = getReplyToMessageId(),
|
||||
displayName = currentConversation?.displayName ?: ""
|
||||
)
|
||||
uploadFile(shareUri.toString(), true, token = roomToken)
|
||||
showSnackBar(roomToken)
|
||||
}
|
||||
|
||||
|
|
@ -4074,26 +3770,12 @@ class ChatActivity :
|
|||
if (null != shareUri) {
|
||||
try {
|
||||
context.contentResolver.openInputStream(shareUri)?.close()
|
||||
uploadFile(
|
||||
fileUri = shareUri.toString(),
|
||||
isVoiceMessage = false,
|
||||
caption = caption!!,
|
||||
roomToken = roomToken,
|
||||
replyToMessageId = getReplyToMessageId(),
|
||||
displayName = currentConversation?.displayName ?: ""
|
||||
)
|
||||
uploadFile(shareUri.toString(), false, caption!!, roomToken)
|
||||
showSnackBar(roomToken)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "File corresponding to the uri does not exist $shareUri", e)
|
||||
} catch (e: java.lang.Exception) {
|
||||
Log.w(TAG, "File corresponding to the uri does not exist $shareUri")
|
||||
downloadFileToCache(message, false) {
|
||||
uploadFile(
|
||||
fileUri = shareUri.toString(),
|
||||
isVoiceMessage = false,
|
||||
caption = caption!!,
|
||||
roomToken = roomToken,
|
||||
replyToMessageId = getReplyToMessageId(),
|
||||
displayName = currentConversation?.displayName ?: ""
|
||||
)
|
||||
uploadFile(shareUri.toString(), false, caption!!, roomToken)
|
||||
showSnackBar(roomToken)
|
||||
}
|
||||
}
|
||||
|
|
@ -4415,10 +4097,6 @@ class ChatActivity :
|
|||
pollVoteDialog.show(supportFragmentManager, TAG)
|
||||
}
|
||||
|
||||
fun createThread() {
|
||||
messageInputViewModel.startThreadCreation()
|
||||
}
|
||||
|
||||
fun jumpToQuotedMessage(parentMessage: ChatMessage) {
|
||||
var foundMessage = false
|
||||
for (position in 0 until (adapter!!.items.size)) {
|
||||
|
|
@ -4435,34 +4113,6 @@ class ChatActivity :
|
|||
}
|
||||
}
|
||||
|
||||
private fun isChatThread(): Boolean = conversationThreadId != null && conversationThreadId!! > 0
|
||||
|
||||
fun openThread(messageId: Long) {
|
||||
val bundle = Bundle()
|
||||
bundle.putString(KEY_ROOM_TOKEN, roomToken)
|
||||
bundle.putLong(KEY_THREAD_ID, messageId)
|
||||
val chatIntent = Intent(context, ChatActivity::class.java)
|
||||
chatIntent.putExtras(bundle)
|
||||
startActivity(chatIntent)
|
||||
}
|
||||
|
||||
fun openThreadsOverview() {
|
||||
val threadsUrl = ApiUtils.getUrlForRecentThreads(
|
||||
version = 1,
|
||||
baseUrl = conversationUser!!.baseUrl,
|
||||
token = roomToken
|
||||
)
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putString(KEY_ROOM_TOKEN, roomToken)
|
||||
bundle.putString(ThreadsOverviewActivity.KEY_APPBAR_TITLE, getString(R.string.recent_threads))
|
||||
bundle.putString(ThreadsOverviewActivity.KEY_THREADS_SOURCE_URL, threadsUrl)
|
||||
|
||||
val threadsOverviewIntent = Intent(context, ThreadsOverviewActivity::class.java)
|
||||
threadsOverviewIntent.putExtras(bundle)
|
||||
startActivity(threadsOverviewIntent)
|
||||
}
|
||||
|
||||
override fun joinAudioCall() {
|
||||
startACall(true, false)
|
||||
}
|
||||
|
|
@ -4508,37 +4158,6 @@ class ChatActivity :
|
|||
)
|
||||
}
|
||||
|
||||
fun uploadFile(
|
||||
fileUri: String,
|
||||
isVoiceMessage: Boolean,
|
||||
caption: String = "",
|
||||
roomToken: String = "",
|
||||
replyToMessageId: Int? = null,
|
||||
displayName: String
|
||||
) {
|
||||
chatViewModel.uploadFile(
|
||||
fileUri,
|
||||
isVoiceMessage,
|
||||
caption,
|
||||
roomToken,
|
||||
replyToMessageId,
|
||||
displayName
|
||||
)
|
||||
cancelReply()
|
||||
}
|
||||
|
||||
fun cancelReply() {
|
||||
messageInputViewModel.reply(null)
|
||||
chatViewModel.messageDraft.quotedMessageText = null
|
||||
chatViewModel.messageDraft.quotedDisplayName = null
|
||||
chatViewModel.messageDraft.quotedImageUrl = null
|
||||
chatViewModel.messageDraft.quotedJsonId = null
|
||||
}
|
||||
|
||||
fun cancelCreateThread() {
|
||||
chatViewModel.clearThreadTitle()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = ChatActivity::class.simpleName
|
||||
private const val CONTENT_TYPE_CALL_STARTED: Byte = 1
|
||||
|
|
@ -4558,6 +4177,7 @@ class ChatActivity :
|
|||
private const val REQUEST_RECORD_AUDIO_PERMISSION = 222
|
||||
private const val REQUEST_READ_CONTACT_PERMISSION = 234
|
||||
private const val REQUEST_CAMERA_PERMISSION = 223
|
||||
private const val VOICE_MESSAGE_META_DATA = "{\"messageType\":\"voice-message\"}"
|
||||
private const val FILE_DATE_PATTERN = "yyyy-MM-dd HH-mm-ss"
|
||||
private const val VIDEO_SUFFIX = ".mp4"
|
||||
private const val FULLY_OPAQUE_INT: Int = 255
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import android.widget.LinearLayout
|
|||
import android.widget.PopupMenu
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.SeekBar
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.content.ContextCompat
|
||||
|
|
@ -60,7 +61,6 @@ import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedA
|
|||
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.chat.viewmodels.MessageInputViewModel
|
||||
import com.nextcloud.talk.data.network.NetworkMonitor
|
||||
import com.nextcloud.talk.databinding.FragmentMessageInputBinding
|
||||
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
|
||||
|
|
@ -75,28 +75,45 @@ import com.nextcloud.talk.users.UserManager
|
|||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.CapabilitiesUtil
|
||||
import com.nextcloud.talk.utils.CharPolicy
|
||||
import com.nextcloud.talk.utils.EmojiTextInputEditText
|
||||
import com.nextcloud.talk.utils.ImageEmojiEditText
|
||||
import com.nextcloud.talk.utils.SpreedFeatures
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
|
||||
import com.nextcloud.talk.utils.message.MessageUtils
|
||||
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.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.Objects
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList", "TooManyFunctions", "LargeClass", "LongMethod")
|
||||
@Suppress("LongParameterList", "TooManyFunctions")
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class MessageInputFragment : Fragment() {
|
||||
|
||||
companion object {
|
||||
fun newInstance() = MessageInputFragment()
|
||||
private val TAG: String = MessageInputFragment::class.java.simpleName
|
||||
private const val TYPING_DURATION_TO_SEND_NEXT_TYPING_MESSAGE = 10000L
|
||||
private const val TYPING_INTERVAL_TO_SEND_NEXT_TYPING_MESSAGE = 1000L
|
||||
private const val TYPING_STARTED_SIGNALING_MESSAGE_TYPE = "startedTyping"
|
||||
private const val TYPING_STOPPED_SIGNALING_MESSAGE_TYPE = "stoppedTyping"
|
||||
const val VOICE_MESSAGE_META_DATA = "{\"messageType\":\"voice-message\"}"
|
||||
private const val QUOTED_MESSAGE_IMAGE_MAX_HEIGHT = 96f
|
||||
private const val MENTION_AUTO_COMPLETE_ELEVATION = 6f
|
||||
private const val MINIMUM_VOICE_RECORD_DURATION: Int = 1000
|
||||
private const val ANIMATION_DURATION: Long = 750
|
||||
private const val VOICE_RECORD_CANCEL_SLIDER_X: Int = -150
|
||||
private const val VOICE_RECORD_LOCK_THRESHOLD: Float = 100f
|
||||
private const val INCREMENT = 8f
|
||||
private const val CURSOR_KEY = "_cursor"
|
||||
private const val CONNECTION_ESTABLISHED_ANIM_DURATION: Long = 3000
|
||||
private const val FULLY_OPAQUE: Float = 1.0f
|
||||
private const val FULLY_TRANSPARENT: Float = 0.0f
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var viewThemeUtils: ViewThemeUtils
|
||||
|
||||
|
|
@ -127,12 +144,6 @@ class MessageInputFragment : Fragment() {
|
|||
super.onCreate(savedInstanceState)
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
conversationInternalId = arguments?.getString(ChatActivity.CONVERSATION_INTERNAL_ID).orEmpty()
|
||||
chatActivity = requireActivity() as ChatActivity
|
||||
val sharedText = arguments?.getString(BundleKeys.KEY_SHARED_TEXT).orEmpty()
|
||||
if (sharedText.isNotEmpty()) {
|
||||
chatActivity.chatViewModel.messageDraft.messageText = sharedText
|
||||
chatActivity.chatViewModel.saveMessageDraft()
|
||||
}
|
||||
if (conversationInternalId.isEmpty()) {
|
||||
Log.e(TAG, "internalId for conversation passed to MessageInputFragment is empty")
|
||||
}
|
||||
|
|
@ -140,75 +151,45 @@ class MessageInputFragment : Fragment() {
|
|||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = FragmentMessageInputBinding.inflate(inflater)
|
||||
chatActivity = requireActivity() as ChatActivity
|
||||
themeMessageInputView()
|
||||
initMessageInputView()
|
||||
initSmileyKeyboardToggler()
|
||||
setupMentionAutocomplete()
|
||||
initVoiceRecordButton()
|
||||
initThreadHandling()
|
||||
restoreState()
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
saveState()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) {
|
||||
mentionAutocomplete?.dismissPopup()
|
||||
}
|
||||
clearEditUI()
|
||||
cancelReply()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initObservers()
|
||||
|
||||
binding.fragmentCreateThreadView.createThreadView.findViewById<EmojiTextInputEditText>(
|
||||
R.id
|
||||
.createThreadInput
|
||||
).doAfterTextChanged { text ->
|
||||
val threadTitle = text.toString()
|
||||
chatActivity.chatViewModel.messageDraft.threadTitle = threadTitle
|
||||
}
|
||||
}
|
||||
|
||||
private fun initObservers() {
|
||||
Log.d(TAG, "LifeCyclerOwner is: ${viewLifecycleOwner.lifecycle}")
|
||||
chatActivity.messageInputViewModel.getReplyChatMessage.observe(viewLifecycleOwner) { message ->
|
||||
message?.let {
|
||||
chatActivity.chatViewModel.messageDraft.quotedMessageText = message.text
|
||||
chatActivity.chatViewModel.messageDraft.quotedDisplayName = message.actorDisplayName
|
||||
chatActivity.chatViewModel.messageDraft.quotedImageUrl = message.imageUrl
|
||||
chatActivity.chatViewModel.messageDraft.quotedJsonId = message.jsonMessageId
|
||||
replyToMessage(
|
||||
message.text,
|
||||
message.actorDisplayName,
|
||||
message.imageUrl
|
||||
)
|
||||
} ?: clearReplyUi()
|
||||
message?.let { replyToMessage(message) }
|
||||
}
|
||||
|
||||
chatActivity.messageInputViewModel.getEditChatMessage.observe(viewLifecycleOwner) { message ->
|
||||
message?.let { setEditUI(it as ChatMessage) }
|
||||
}
|
||||
|
||||
chatActivity.messageInputViewModel.createThreadViewState.observe(viewLifecycleOwner) { state ->
|
||||
when (state) {
|
||||
is MessageInputViewModel.CreateThreadStartState ->
|
||||
binding.fragmentCreateThreadView.createThreadView.visibility = View.GONE
|
||||
|
||||
is MessageInputViewModel.CreateThreadEditState -> {
|
||||
binding.fragmentCreateThreadView.createThreadView.visibility = View.VISIBLE
|
||||
binding.fragmentCreateThreadView.createThreadView
|
||||
.findViewById<EmojiTextInputEditText>(R.id.createThreadInput)?.setText(
|
||||
chatActivity.chatViewModel.messageDraft.threadTitle
|
||||
)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
initVoiceRecordButton()
|
||||
}
|
||||
|
||||
chatActivity.chatViewModel.leaveRoomViewState.observe(viewLifecycleOwner) { state ->
|
||||
when (state) {
|
||||
is ChatViewModel.LeaveRoomSuccessState -> sendStopTypingMessage()
|
||||
|
|
@ -318,29 +299,33 @@ class MessageInputFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun restoreState() {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
chatActivity.chatViewModel.updateMessageDraft()
|
||||
if (binding.fragmentMessageInputView.inputEditText.text.isEmpty()) {
|
||||
requireContext().getSharedPreferences(chatActivity.localClassName, AppCompatActivity.MODE_PRIVATE).apply {
|
||||
val text = getString(chatActivity.roomToken, "")
|
||||
val cursor = getInt(chatActivity.roomToken + CURSOR_KEY, 0)
|
||||
binding.fragmentMessageInputView.messageInput.setText(text)
|
||||
binding.fragmentMessageInputView.messageInput.setSelection(cursor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
val draft = chatActivity.chatViewModel.messageDraft
|
||||
binding.fragmentMessageInputView.messageInput.setText(draft.messageText)
|
||||
binding.fragmentMessageInputView.messageInput.setSelection(draft.messageCursor)
|
||||
private fun saveState() {
|
||||
val text = binding.fragmentMessageInputView.messageInput.text.toString()
|
||||
val cursor = binding.fragmentMessageInputView.messageInput.selectionStart
|
||||
val previous = requireContext().getSharedPreferences(
|
||||
chatActivity.localClassName,
|
||||
AppCompatActivity
|
||||
.MODE_PRIVATE
|
||||
).getString(chatActivity.roomToken, "null")
|
||||
|
||||
if (draft.threadTitle?.isNotEmpty() == true) {
|
||||
chatActivity.messageInputViewModel.startThreadCreation()
|
||||
}
|
||||
|
||||
if (draft.messageText != "") {
|
||||
binding.fragmentMessageInputView.messageInput.requestFocus()
|
||||
}
|
||||
|
||||
if (isInReplyState()) {
|
||||
replyToMessage(
|
||||
chatActivity.chatViewModel.messageDraft.quotedMessageText,
|
||||
chatActivity.chatViewModel.messageDraft.quotedDisplayName,
|
||||
chatActivity.chatViewModel.messageDraft.quotedImageUrl
|
||||
)
|
||||
}
|
||||
if (text != previous) {
|
||||
requireContext().getSharedPreferences(
|
||||
chatActivity.localClassName,
|
||||
AppCompatActivity.MODE_PRIVATE
|
||||
).edit().apply {
|
||||
putString(chatActivity.roomToken, text)
|
||||
putInt(chatActivity.roomToken + CURSOR_KEY, cursor)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -403,11 +388,7 @@ class MessageInputFragment : Fragment() {
|
|||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
val cursor = binding.fragmentMessageInputView.messageInput.selectionStart
|
||||
val text = binding.fragmentMessageInputView.messageInput.text.toString()
|
||||
chatActivity.chatViewModel.messageDraft.messageCursor = cursor
|
||||
chatActivity.chatViewModel.messageDraft.messageText = text
|
||||
handleButtonsVisibility()
|
||||
// unused atm
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -415,14 +396,11 @@ class MessageInputFragment : Fragment() {
|
|||
// See: https://developer.android.com/guide/topics/text/image-keyboard
|
||||
|
||||
(binding.fragmentMessageInputView.inputEditText as ImageEmojiEditText).onCommitContentListener = {
|
||||
chatActivity.chatViewModel.uploadFile(
|
||||
fileUri = it.toString(),
|
||||
isVoiceMessage = false,
|
||||
caption = "",
|
||||
roomToken = chatActivity.roomToken,
|
||||
replyToMessageId = chatActivity.getReplyToMessageId(),
|
||||
displayName = chatActivity.currentConversation?.displayName!!
|
||||
)
|
||||
uploadFile(it.toString(), false)
|
||||
}
|
||||
|
||||
if (chatActivity.sharedText.isNotEmpty()) {
|
||||
binding.fragmentMessageInputView.inputEditText?.setText(chatActivity.sharedText)
|
||||
}
|
||||
|
||||
binding.fragmentMessageInputView.setAttachmentsListener {
|
||||
|
|
@ -461,9 +439,6 @@ class MessageInputFragment : Fragment() {
|
|||
binding.fragmentEditView.clearEdit.setOnClickListener {
|
||||
clearEditUI()
|
||||
}
|
||||
binding.fragmentCreateThreadView.abortCreateThread.setOnClickListener {
|
||||
cancelCreateThread()
|
||||
}
|
||||
|
||||
if (CapabilitiesUtil.hasSpreedFeatureCapability(chatActivity.spreedCapabilities, SpreedFeatures.SILENT_SEND)) {
|
||||
binding.fragmentMessageInputView.button?.setOnLongClickListener {
|
||||
|
|
@ -491,10 +466,6 @@ class MessageInputFragment : Fragment() {
|
|||
binding.fragmentCallStarted.callStartedSecondaryText.visibility = if (collapsed) View.VISIBLE else View.GONE
|
||||
setDropDown(collapsed)
|
||||
}
|
||||
|
||||
binding.fragmentMessageInputView.findViewById<ImageButton>(R.id.cancelReplyButton)?.setOnClickListener {
|
||||
cancelReply()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setDropDown(collapsed: Boolean) {
|
||||
|
|
@ -509,7 +480,32 @@ class MessageInputFragment : Fragment() {
|
|||
|
||||
@Suppress("ClickableViewAccessibility", "CyclomaticComplexMethod", "LongMethod")
|
||||
private fun initVoiceRecordButton() {
|
||||
handleButtonsVisibility()
|
||||
if (binding.fragmentMessageInputView.messageInput.text.isNullOrBlank()) {
|
||||
binding.fragmentMessageInputView.messageSendButton.visibility = View.GONE
|
||||
binding.fragmentMessageInputView.recordAudioButton.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.fragmentMessageInputView.messageSendButton.visibility = View.VISIBLE
|
||||
binding.fragmentMessageInputView.recordAudioButton.visibility = View.GONE
|
||||
}
|
||||
binding.fragmentMessageInputView.inputEditText.doAfterTextChanged {
|
||||
binding.fragmentMessageInputView.recordAudioButton.visibility =
|
||||
if (binding.fragmentMessageInputView.inputEditText.text.isEmpty() &&
|
||||
chatActivity.messageInputViewModel.getEditChatMessage.value == null
|
||||
) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
|
||||
binding.fragmentMessageInputView.messageSendButton.visibility =
|
||||
if (binding.fragmentMessageInputView.inputEditText.text.isEmpty() ||
|
||||
binding.fragmentEditView.editMessageView.isVisible
|
||||
) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
var prevDx = 0f
|
||||
var voiceRecordStartTime = 0L
|
||||
|
|
@ -572,9 +568,9 @@ class MessageInputFragment : Fragment() {
|
|||
return@setOnTouchListener false
|
||||
} else {
|
||||
chatActivity.chatViewModel.stopAndSendAudioRecording(
|
||||
roomToken = chatActivity.roomToken,
|
||||
replyToMessageId = chatActivity.getReplyToMessageId(),
|
||||
displayName = chatActivity.currentConversation!!.displayName
|
||||
chatActivity.roomToken,
|
||||
chatActivity.currentConversation!!.displayName,
|
||||
VOICE_MESSAGE_META_DATA
|
||||
)
|
||||
}
|
||||
resetSlider()
|
||||
|
|
@ -619,67 +615,7 @@ class MessageInputFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
}
|
||||
v?.onTouchEvent(event) != false
|
||||
}
|
||||
}
|
||||
|
||||
private fun initThreadHandling() {
|
||||
binding.fragmentMessageInputView.submitThreadButton.setOnClickListener {
|
||||
submitMessage(false)
|
||||
}
|
||||
|
||||
binding.fragmentCreateThreadView.createThreadInput.doAfterTextChanged {
|
||||
handleButtonsVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleButtonsVisibility() {
|
||||
fun View.setVisible(isVisible: Boolean) {
|
||||
visibility = if (isVisible) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
val isEditModeActive = binding.fragmentEditView.editMessageView.isVisible
|
||||
val isThreadCreateModeActive = binding.fragmentCreateThreadView.createThreadView.isVisible
|
||||
val inputContainsText = binding.fragmentMessageInputView.messageInput.text.isNotEmpty()
|
||||
val threadTitleContainsText = binding.fragmentCreateThreadView.createThreadInput.text?.isNotEmpty() ?: false
|
||||
|
||||
binding.fragmentMessageInputView.apply {
|
||||
when {
|
||||
isEditModeActive -> {
|
||||
messageSendButton.setVisible(false)
|
||||
recordAudioButton.setVisible(false)
|
||||
submitThreadButton.setVisible(false)
|
||||
attachmentButton.setVisible(true)
|
||||
}
|
||||
|
||||
isThreadCreateModeActive -> {
|
||||
messageSendButton.setVisible(false)
|
||||
recordAudioButton.setVisible(false)
|
||||
attachmentButton.setVisible(false)
|
||||
submitThreadButton.setVisible(true)
|
||||
if (inputContainsText && threadTitleContainsText) {
|
||||
submitThreadButton.isEnabled = true
|
||||
submitThreadButton.alpha = FULLY_OPAQUE
|
||||
} else {
|
||||
submitThreadButton.isEnabled = false
|
||||
submitThreadButton.alpha = OPACITY_DISABLED
|
||||
}
|
||||
}
|
||||
|
||||
inputContainsText -> {
|
||||
recordAudioButton.setVisible(false)
|
||||
submitThreadButton.setVisible(false)
|
||||
messageSendButton.setVisible(true)
|
||||
attachmentButton.setVisible(true)
|
||||
}
|
||||
|
||||
else -> {
|
||||
messageSendButton.setVisible(false)
|
||||
submitThreadButton.setVisible(false)
|
||||
recordAudioButton.setVisible(true)
|
||||
attachmentButton.setVisible(true)
|
||||
}
|
||||
}
|
||||
v?.onTouchEvent(event) ?: true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -781,45 +717,52 @@ class MessageInputFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun replyToMessage(quotedMessageText: String?, quotedActorDisplayName: String?, quotedImageUrl: String?) {
|
||||
private fun replyToMessage(message: IMessage?) {
|
||||
Log.d(TAG, "Reply")
|
||||
val view = binding.fragmentMessageInputView
|
||||
view.findViewById<ImageButton>(R.id.cancelReplyButton)?.visibility =
|
||||
View.VISIBLE
|
||||
val chatMessage = message as ChatMessage?
|
||||
chatMessage?.let {
|
||||
val view = binding.fragmentMessageInputView
|
||||
view.findViewById<ImageButton>(R.id.attachmentButton)?.visibility =
|
||||
View.GONE
|
||||
view.findViewById<ImageButton>(R.id.cancelReplyButton)?.visibility =
|
||||
View.VISIBLE
|
||||
|
||||
val quotedMessage = view.findViewById<EmojiTextView>(R.id.quotedMessage)
|
||||
val quotedMessage = view.findViewById<EmojiTextView>(R.id.quotedMessage)
|
||||
|
||||
quotedMessage?.maxLines = 2
|
||||
quotedMessage?.ellipsize = TextUtils.TruncateAt.END
|
||||
quotedMessage?.text = quotedMessageText
|
||||
view.findViewById<EmojiTextView>(R.id.quotedMessageAuthor)?.text =
|
||||
quotedActorDisplayName ?: requireContext().getText(R.string.nc_nick_guest)
|
||||
quotedMessage?.maxLines = 2
|
||||
quotedMessage?.ellipsize = TextUtils.TruncateAt.END
|
||||
quotedMessage?.text = it.text
|
||||
view.findViewById<EmojiTextView>(R.id.quotedMessageAuthor)?.text =
|
||||
it.actorDisplayName ?: requireContext().getText(R.string.nc_nick_guest)
|
||||
|
||||
chatActivity.conversationUser?.let {
|
||||
val quotedMessageImage = view.findViewById<ImageView>(R.id.quotedMessageImage)
|
||||
quotedImageUrl?.let { previewImageUrl ->
|
||||
quotedMessageImage?.visibility = View.VISIBLE
|
||||
chatActivity.conversationUser?.let {
|
||||
val quotedMessageImage = view.findViewById<ImageView>(R.id.quotedMessageImage)
|
||||
chatMessage.imageUrl?.let { previewImageUrl ->
|
||||
quotedMessageImage?.visibility = View.VISIBLE
|
||||
|
||||
val px = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
QUOTED_MESSAGE_IMAGE_MAX_HEIGHT,
|
||||
resources.displayMetrics
|
||||
)
|
||||
val px = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
QUOTED_MESSAGE_IMAGE_MAX_HEIGHT,
|
||||
resources.displayMetrics
|
||||
)
|
||||
|
||||
quotedMessageImage?.maxHeight = px.toInt()
|
||||
val layoutParams = quotedMessageImage?.layoutParams as FlexboxLayout.LayoutParams
|
||||
layoutParams.flexGrow = 0f
|
||||
quotedMessageImage.layoutParams = layoutParams
|
||||
quotedMessageImage.load(previewImageUrl) {
|
||||
addHeader("Authorization", chatActivity.credentials!!)
|
||||
quotedMessageImage?.maxHeight = px.toInt()
|
||||
val layoutParams = quotedMessageImage?.layoutParams as FlexboxLayout.LayoutParams
|
||||
layoutParams.flexGrow = 0f
|
||||
quotedMessageImage.layoutParams = layoutParams
|
||||
quotedMessageImage.load(previewImageUrl) {
|
||||
addHeader("Authorization", chatActivity.credentials!!)
|
||||
}
|
||||
} ?: run {
|
||||
view.findViewById<ImageView>(R.id.quotedMessageImage)?.visibility = View.GONE
|
||||
}
|
||||
} ?: run {
|
||||
view.findViewById<ImageView>(R.id.quotedMessageImage)?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
val quotedChatMessageView = view.findViewById<RelativeLayout>(R.id.quotedChatMessageView)
|
||||
quotedChatMessageView?.visibility = View.VISIBLE
|
||||
val quotedChatMessageView =
|
||||
view.findViewById<RelativeLayout>(R.id.quotedChatMessageView)
|
||||
quotedChatMessageView?.tag = message?.jsonMessageId
|
||||
quotedChatMessageView?.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
fun updateOwnTypingStatus(typedText: CharSequence) {
|
||||
|
|
@ -886,34 +829,58 @@ class MessageInputFragment : Fragment() {
|
|||
private fun isTypingStatusEnabled(): Boolean =
|
||||
!CapabilitiesUtil.isTypingStatusPrivate(chatActivity.conversationUser!!)
|
||||
|
||||
private fun uploadFile(fileUri: String, isVoiceMessage: Boolean, caption: String = "", token: String = "") {
|
||||
var metaData = ""
|
||||
val room: String
|
||||
|
||||
if (!chatActivity.participantPermissions.hasChatPermission()) {
|
||||
Log.w(ChatActivity.TAG, "uploading file(s) is forbidden because of missing attendee permissions")
|
||||
return
|
||||
}
|
||||
|
||||
if (isVoiceMessage) {
|
||||
metaData = VOICE_MESSAGE_META_DATA
|
||||
}
|
||||
|
||||
if (caption != "") {
|
||||
metaData = "{\"caption\":\"$caption\"}"
|
||||
}
|
||||
|
||||
if (token == "") room = chatActivity.roomToken else room = token
|
||||
|
||||
chatActivity.chatViewModel.uploadFile(fileUri, room, chatActivity.currentConversation!!.displayName, metaData)
|
||||
}
|
||||
|
||||
private fun submitMessage(sendWithoutNotification: Boolean) {
|
||||
if (binding.fragmentMessageInputView.inputEditText != null) {
|
||||
val editable = binding.fragmentMessageInputView.inputEditText!!.editableText
|
||||
replaceMentionChipSpans(editable)
|
||||
binding.fragmentMessageInputView.inputEditText?.setText("")
|
||||
sendStopTypingMessage()
|
||||
val replyMessageId = binding.fragmentMessageInputView
|
||||
.findViewById<RelativeLayout>(R.id.quotedChatMessageView)?.tag as Int? ?: 0
|
||||
|
||||
sendMessage(
|
||||
editable.toString(),
|
||||
replyMessageId,
|
||||
sendWithoutNotification
|
||||
)
|
||||
cancelReply()
|
||||
cancelCreateThread()
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendMessage(message: String, sendWithoutNotification: Boolean) {
|
||||
private fun sendMessage(message: String, replyTo: Int?, sendWithoutNotification: Boolean) {
|
||||
chatActivity.messageInputViewModel.sendChatMessage(
|
||||
credentials = chatActivity.conversationUser!!.getCredentials(),
|
||||
url = ApiUtils.getUrlForChat(
|
||||
chatActivity.conversationUser!!.getCredentials(),
|
||||
ApiUtils.getUrlForChat(
|
||||
chatActivity.chatApiVersion,
|
||||
chatActivity.conversationUser!!.baseUrl!!,
|
||||
chatActivity.roomToken
|
||||
),
|
||||
message = message,
|
||||
displayName = chatActivity.conversationUser!!.displayName ?: "",
|
||||
replyTo = chatActivity.getReplyToMessageId(),
|
||||
sendWithoutNotification = sendWithoutNotification,
|
||||
threadTitle = chatActivity.chatViewModel.messageDraft.threadTitle
|
||||
message,
|
||||
chatActivity.conversationUser!!.displayName ?: "",
|
||||
replyTo ?: 0,
|
||||
sendWithoutNotification
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -997,7 +964,6 @@ class MessageInputFragment : Fragment() {
|
|||
binding.fragmentMessageInputView.inputEditText.setSelection(end)
|
||||
binding.fragmentMessageInputView.messageSendButton.visibility = View.GONE
|
||||
binding.fragmentMessageInputView.recordAudioButton.visibility = View.GONE
|
||||
binding.fragmentMessageInputView.submitThreadButton.visibility = View.GONE
|
||||
binding.fragmentMessageInputView.editMessageButton.visibility = View.VISIBLE
|
||||
binding.fragmentEditView.editMessageView.visibility = View.VISIBLE
|
||||
binding.fragmentMessageInputView.attachmentButton.visibility = View.GONE
|
||||
|
|
@ -1009,12 +975,15 @@ class MessageInputFragment : Fragment() {
|
|||
binding.fragmentEditView.editMessageView.visibility = View.GONE
|
||||
binding.fragmentMessageInputView.attachmentButton.visibility = View.VISIBLE
|
||||
chatActivity.messageInputViewModel.edit(null)
|
||||
handleButtonsVisibility()
|
||||
}
|
||||
|
||||
private fun themeMessageInputView() {
|
||||
binding.fragmentMessageInputView.button?.let { viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY) }
|
||||
|
||||
binding.fragmentMessageInputView.findViewById<ImageButton>(R.id.cancelReplyButton)?.setOnClickListener {
|
||||
cancelReply()
|
||||
}
|
||||
|
||||
binding.fragmentMessageInputView.findViewById<ImageButton>(R.id.cancelReplyButton)?.let {
|
||||
viewThemeUtils.platform
|
||||
.themeImageButton(it)
|
||||
|
|
@ -1052,9 +1021,6 @@ class MessageInputFragment : Fragment() {
|
|||
binding.fragmentEditView.clearEdit.let {
|
||||
viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY)
|
||||
}
|
||||
binding.fragmentCreateThreadView.abortCreateThread.let {
|
||||
viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY)
|
||||
}
|
||||
|
||||
binding.fragmentCallStarted.callStartedBackground.apply {
|
||||
viewThemeUtils.talk.themeOutgoingMessageBubble(this, grouped = true, false)
|
||||
|
|
@ -1071,56 +1037,14 @@ class MessageInputFragment : Fragment() {
|
|||
binding.fragmentCallStarted.callStartedCloseBtn.apply {
|
||||
viewThemeUtils.platform.colorImageView(this, ColorRole.PRIMARY)
|
||||
}
|
||||
|
||||
binding.fragmentMessageInputView.submitThreadButton.apply {
|
||||
viewThemeUtils.platform.colorImageView(this, ColorRole.SECONDARY)
|
||||
}
|
||||
|
||||
binding.fragmentCreateThreadView.createThreadInput.apply {
|
||||
viewThemeUtils.platform.colorEditText(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelCreateThread() {
|
||||
chatActivity.cancelCreateThread()
|
||||
chatActivity.messageInputViewModel.stopThreadCreation()
|
||||
binding.fragmentCreateThreadView.createThreadView.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun cancelReply() {
|
||||
chatActivity.cancelReply()
|
||||
clearReplyUi()
|
||||
}
|
||||
|
||||
private fun clearReplyUi() {
|
||||
val quote = binding.fragmentMessageInputView.findViewById<RelativeLayout>(R.id.quotedChatMessageView)
|
||||
val quote = binding.fragmentMessageInputView
|
||||
.findViewById<RelativeLayout>(R.id.quotedChatMessageView)
|
||||
quote.visibility = View.GONE
|
||||
quote.tag = null
|
||||
binding.fragmentMessageInputView.findViewById<ImageButton>(R.id.attachmentButton)?.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun isInReplyState(): Boolean {
|
||||
val jsonId = chatActivity.chatViewModel.messageDraft.quotedJsonId
|
||||
return jsonId != null
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance() = MessageInputFragment()
|
||||
private val TAG: String = MessageInputFragment::class.java.simpleName
|
||||
private const val TYPING_DURATION_TO_SEND_NEXT_TYPING_MESSAGE = 10000L
|
||||
private const val TYPING_INTERVAL_TO_SEND_NEXT_TYPING_MESSAGE = 1000L
|
||||
private const val TYPING_STARTED_SIGNALING_MESSAGE_TYPE = "startedTyping"
|
||||
private const val TYPING_STOPPED_SIGNALING_MESSAGE_TYPE = "stoppedTyping"
|
||||
private const val QUOTED_MESSAGE_IMAGE_MAX_HEIGHT = 96f
|
||||
private const val MENTION_AUTO_COMPLETE_ELEVATION = 6f
|
||||
private const val MINIMUM_VOICE_RECORD_DURATION: Int = 1000
|
||||
private const val ANIMATION_DURATION: Long = 750
|
||||
private const val VOICE_RECORD_CANCEL_SLIDER_X: Int = -150
|
||||
private const val VOICE_RECORD_LOCK_THRESHOLD: Float = 100f
|
||||
private const val INCREMENT = 8f
|
||||
private const val CURSOR_KEY = "_cursor"
|
||||
private const val CONNECTION_ESTABLISHED_ANIM_DURATION: Long = 3000
|
||||
private const val FULLY_OPAQUE: Float = 1.0f
|
||||
private const val FULLY_TRANSPARENT: Float = 0.0f
|
||||
private const val OPACITY_DISABLED = 0.7f
|
||||
chatActivity.messageInputViewModel.reply(null)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,9 +114,9 @@ class MessageInputVoiceRecordingFragment : Fragment() {
|
|||
|
||||
binding.sendVoiceRecording.setOnClickListener {
|
||||
chatActivity.chatViewModel.stopAndSendAudioRecording(
|
||||
roomToken = chatActivity.roomToken,
|
||||
replyToMessageId = chatActivity.getReplyToMessageId(),
|
||||
displayName = chatActivity.currentConversation!!.displayName
|
||||
chatActivity.roomToken,
|
||||
chatActivity.currentConversation!!.displayName,
|
||||
MessageInputFragment.VOICE_MESSAGE_META_DATA
|
||||
)
|
||||
clear()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,160 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.chat
|
||||
|
||||
import android.view.View
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Popup
|
||||
import com.nextcloud.talk.R
|
||||
|
||||
data class MenuItemData(val title: String, val subtitle: String? = null, val icon: Int? = null, val onClick: () -> Unit)
|
||||
|
||||
@Composable
|
||||
fun OverflowMenu(anchor: View?, expanded: Boolean, items: List<MenuItemData>, onDismiss: () -> Unit) {
|
||||
if (!expanded) return
|
||||
|
||||
val rect = anchor?.boundsInWindow()
|
||||
val xOffset = rect?.left ?: 0
|
||||
val yOffset = rect?.bottom ?: 0
|
||||
|
||||
Popup(
|
||||
onDismissRequest = onDismiss,
|
||||
offset = IntOffset(xOffset, yOffset)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.width(IntrinsicSize.Max)
|
||||
.background(
|
||||
color = colorResource(id = R.color.bg_default),
|
||||
shape = RoundedCornerShape(1.dp)
|
||||
)
|
||||
.shadow(
|
||||
elevation = 1.dp,
|
||||
shape = RoundedCornerShape(1.dp),
|
||||
clip = false
|
||||
)
|
||||
) {
|
||||
items.forEach { item ->
|
||||
DynamicMenuItem(
|
||||
item.copy(
|
||||
onClick = {
|
||||
item.onClick()
|
||||
onDismiss()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DynamicMenuItem(item: MenuItemData) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
onClick = item.onClick,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
)
|
||||
.padding(horizontal = 12.dp, vertical = 12.dp)
|
||||
) {
|
||||
item.icon?.let { icon ->
|
||||
Icon(
|
||||
painter = painterResource(icon),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
}
|
||||
|
||||
Column {
|
||||
Text(item.title, color = MaterialTheme.colorScheme.onSurface)
|
||||
item.subtitle?.let {
|
||||
Text(
|
||||
it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun View.boundsInWindow(): android.graphics.Rect {
|
||||
val location = IntArray(2)
|
||||
getLocationOnScreen(location)
|
||||
return android.graphics.Rect(
|
||||
location[0],
|
||||
location[1],
|
||||
location[0] + width,
|
||||
location[1] + height
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun OverflowMenuPreview() {
|
||||
val items = listOf(
|
||||
MenuItemData(
|
||||
title = "first item title",
|
||||
subtitle = "first item subtitle",
|
||||
icon = R.drawable.baseline_notifications_24,
|
||||
onClick = {}
|
||||
),
|
||||
MenuItemData(
|
||||
title = "second item title",
|
||||
subtitle = null,
|
||||
icon = R.drawable.outline_notifications_active_24,
|
||||
onClick = {}
|
||||
),
|
||||
MenuItemData(
|
||||
title = "third item title",
|
||||
subtitle = null,
|
||||
icon = R.drawable.baseline_notifications_24,
|
||||
onClick = {}
|
||||
),
|
||||
MenuItemData(
|
||||
title = "fourth item title",
|
||||
subtitle = null,
|
||||
icon = R.drawable.baseline_notifications_24,
|
||||
onClick = {}
|
||||
)
|
||||
)
|
||||
|
||||
OverflowMenu(
|
||||
anchor = null,
|
||||
expanded = true,
|
||||
items = items,
|
||||
onDismiss = { }
|
||||
)
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ interface ChatMessageRepository : LifecycleAwareManager {
|
|||
|
||||
val removeMessageFlow: Flow<ChatMessage>
|
||||
|
||||
fun initData(credentials: String, urlForChatting: String, roomToken: String, threadId: Long?)
|
||||
fun initData(credentials: String, urlForChatting: String, roomToken: String)
|
||||
|
||||
fun updateConversation(conversationModel: ConversationModel)
|
||||
|
||||
|
|
@ -76,8 +76,6 @@ interface ChatMessageRepository : LifecycleAwareManager {
|
|||
*/
|
||||
suspend fun getMessage(messageId: Long, bundle: Bundle): Flow<ChatMessage>
|
||||
|
||||
suspend fun getNumberOfThreadReplies(threadId: Long): Int
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun sendChatMessage(
|
||||
credentials: String,
|
||||
|
|
@ -86,8 +84,7 @@ interface ChatMessageRepository : LifecycleAwareManager {
|
|||
displayName: String,
|
||||
replyTo: Int,
|
||||
sendWithoutNotification: Boolean,
|
||||
referenceId: String,
|
||||
threadTitle: String?
|
||||
referenceId: String
|
||||
): Flow<Result<ChatMessage?>>
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
|
|||
|
|
@ -45,14 +45,6 @@ data class ChatMessage(
|
|||
|
||||
var token: String? = null,
|
||||
|
||||
var threadId: Long? = null,
|
||||
|
||||
var isThread: Boolean = false,
|
||||
|
||||
var threadTitle: String? = null,
|
||||
|
||||
var threadReplies: Int? = 0,
|
||||
|
||||
// guests or users
|
||||
var actorType: String? = null,
|
||||
|
||||
|
|
@ -432,8 +424,7 @@ data class ChatMessage(
|
|||
AVATAR_REMOVED,
|
||||
FEDERATED_USER_ADDED,
|
||||
FEDERATED_USER_REMOVED,
|
||||
PHONE_ADDED,
|
||||
THREAD_CREATED
|
||||
PHONE_ADDED
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -59,8 +59,7 @@ interface ChatNetworkDataSource {
|
|||
displayName: String,
|
||||
replyTo: Int,
|
||||
sendWithoutNotification: Boolean,
|
||||
referenceId: String,
|
||||
threadTitle: String?
|
||||
referenceId: String
|
||||
): ChatOverallSingleMessage
|
||||
|
||||
fun pullChatMessages(credentials: String, url: String, fieldMap: HashMap<String, Int>): Observable<Response<*>>
|
||||
|
|
|
|||
|
|
@ -119,13 +119,11 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
private lateinit var conversationModel: ConversationModel
|
||||
private lateinit var credentials: String
|
||||
private lateinit var urlForChatting: String
|
||||
private var threadId: Long? = null
|
||||
|
||||
override fun initData(credentials: String, urlForChatting: String, roomToken: String, threadId: Long?) {
|
||||
override fun initData(credentials: String, urlForChatting: String, roomToken: String) {
|
||||
internalConversationId = currentUser.id.toString() + "@" + roomToken
|
||||
this.credentials = credentials
|
||||
this.urlForChatting = urlForChatting
|
||||
this.threadId = threadId
|
||||
}
|
||||
|
||||
override fun updateConversation(conversationModel: ConversationModel) {
|
||||
|
|
@ -145,7 +143,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
Log.d(TAG, "conversationModel.internalId: " + conversationModel.internalId)
|
||||
Log.d(TAG, "conversationModel.lastReadMessage:" + conversationModel.lastReadMessage)
|
||||
|
||||
var newestMessageIdFromDb = chatBlocksDao.getNewestMessageIdFromChatBlocks(internalConversationId, threadId)
|
||||
var newestMessageIdFromDb = chatDao.getNewestMessageId(internalConversationId)
|
||||
Log.d(TAG, "newestMessageIdFromDb: $newestMessageIdFromDb")
|
||||
|
||||
val weAlreadyHaveSomeOfflineMessages = newestMessageIdFromDb > 0
|
||||
|
|
@ -191,7 +189,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
Log.e(TAG, "initial loading of messages failed")
|
||||
}
|
||||
|
||||
newestMessageIdFromDb = chatBlocksDao.getNewestMessageIdFromChatBlocks(internalConversationId, threadId)
|
||||
newestMessageIdFromDb = chatDao.getNewestMessageId(internalConversationId)
|
||||
Log.d(TAG, "newestMessageIdFromDb after sync: $newestMessageIdFromDb")
|
||||
}
|
||||
|
||||
|
|
@ -205,9 +203,9 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
val limit = getCappedMessagesAmountOfChatBlock(newestMessageIdFromDb)
|
||||
|
||||
val list = getMessagesBeforeAndEqual(
|
||||
messageId = newestMessageIdFromDb,
|
||||
internalConversationId = internalConversationId,
|
||||
messageLimit = limit
|
||||
newestMessageIdFromDb,
|
||||
internalConversationId,
|
||||
limit
|
||||
)
|
||||
if (list.isNotEmpty()) {
|
||||
handleNewAndTempMessages(
|
||||
|
|
@ -236,8 +234,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
val amountBetween = chatDao.getCountBetweenMessageIds(
|
||||
internalConversationId,
|
||||
messageId,
|
||||
chatBlock.oldestMessageId,
|
||||
threadId
|
||||
chatBlock.oldestMessageId
|
||||
)
|
||||
|
||||
Log.d(TAG, "amount of messages between newestMessageId and oldest message of same ChatBlock:$amountBetween")
|
||||
|
|
@ -287,7 +284,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
)
|
||||
withNetworkParams.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
|
||||
|
||||
val loadFromServer = hasToLoadPreviousMessagesFromServer(beforeMessageId, DEFAULT_MESSAGES_LIMIT)
|
||||
val loadFromServer = hasToLoadPreviousMessagesFromServer(beforeMessageId)
|
||||
|
||||
if (loadFromServer) {
|
||||
Log.d(TAG, "Starting online request for loadMoreMessages")
|
||||
|
|
@ -349,10 +346,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
|
||||
updateUiForLastCommonRead()
|
||||
|
||||
val newestMessage = chatBlocksDao.getNewestMessageIdFromChatBlocks(
|
||||
internalConversationId,
|
||||
threadId
|
||||
).toInt()
|
||||
val newestMessage = chatDao.getNewestMessageId(internalConversationId).toInt()
|
||||
|
||||
// update field map vars for next cycle
|
||||
fieldMap = getFieldMap(
|
||||
|
|
@ -415,7 +409,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
_messageFlow.emit(triple)
|
||||
}
|
||||
|
||||
private suspend fun hasToLoadPreviousMessagesFromServer(beforeMessageId: Long, amountToCheck: Int): Boolean {
|
||||
private suspend fun hasToLoadPreviousMessagesFromServer(beforeMessageId: Long): Boolean {
|
||||
val loadFromServer: Boolean
|
||||
|
||||
val blockForMessage = getBlockOfMessage(beforeMessageId.toInt())
|
||||
|
|
@ -427,25 +421,26 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
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 DEFAULT_MESSAGES_LIMIT entries before beforeMessageId, we calculate if these
|
||||
// messages are DEFAULT_MESSAGES_LIMIT entries apart from each other
|
||||
|
||||
val amountBetween = chatDao.getCountBetweenMessageIds(
|
||||
internalConversationId,
|
||||
beforeMessageId,
|
||||
blockForMessage.oldestMessageId,
|
||||
threadId
|
||||
blockForMessage.oldestMessageId
|
||||
)
|
||||
loadFromServer = amountBetween < amountToCheck
|
||||
loadFromServer = amountBetween < DEFAULT_MESSAGES_LIMIT
|
||||
|
||||
Log.d(
|
||||
TAG,
|
||||
"Amount between messageId " + beforeMessageId + " and " + blockForMessage.oldestMessageId +
|
||||
" is: " + amountBetween + " and $amountToCheck were needed, so 'loadFromServer' is " +
|
||||
loadFromServer
|
||||
" is: " + amountBetween + " so 'loadFromServer' is " + loadFromServer
|
||||
)
|
||||
}
|
||||
return loadFromServer
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun getFieldMap(
|
||||
lookIntoFuture: Boolean,
|
||||
timeout: Int,
|
||||
|
|
@ -466,23 +461,17 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
fieldMap["lastCommonReadId"] = it
|
||||
}
|
||||
|
||||
threadId?.let { fieldMap["threadId"] = it.toInt() }
|
||||
|
||||
fieldMap["timeout"] = timeout
|
||||
fieldMap["limit"] = limit
|
||||
|
||||
fieldMap["lookIntoFuture"] = if (lookIntoFuture) 1 else 0
|
||||
fieldMap["setReadMarker"] = if (setReadMarker) 1 else 0
|
||||
|
||||
return fieldMap
|
||||
}
|
||||
|
||||
override suspend fun getNumberOfThreadReplies(threadId: Long): Int =
|
||||
chatDao.getNumberOfThreadReplies(internalConversationId, threadId)
|
||||
|
||||
override suspend fun getMessage(messageId: Long, bundle: Bundle): Flow<ChatMessage> {
|
||||
Log.d(TAG, "Get message with id $messageId")
|
||||
val loadFromServer = hasToLoadPreviousMessagesFromServer(messageId, 1)
|
||||
val loadFromServer = hasToLoadPreviousMessagesFromServer(messageId)
|
||||
|
||||
if (loadFromServer) {
|
||||
val fieldMap = getFieldMap(
|
||||
|
|
@ -498,10 +487,8 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
Log.d(TAG, "Starting online request for single message (e.g. a reply)")
|
||||
sync(bundle)
|
||||
}
|
||||
return chatDao.getChatMessageForConversation(
|
||||
internalConversationId,
|
||||
messageId
|
||||
).map(ChatMessageEntity::asModel)
|
||||
return chatDao.getChatMessageForConversation(internalConversationId, messageId)
|
||||
.map(ChatMessageEntity::asModel)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "MagicNumber", "Detekt.TooGenericExceptionCaught")
|
||||
|
|
@ -665,12 +652,11 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
internalConversationId = internalConversationId,
|
||||
accountId = conversationModel.accountId,
|
||||
token = conversationModel.token,
|
||||
threadId = threadId,
|
||||
oldestMessageId = oldestMessageIdForNewChatBlock,
|
||||
newestMessageId = newestMessageIdForNewChatBlock,
|
||||
hasHistory = hasHistory
|
||||
)
|
||||
chatBlocksDao.upsertChatBlock(newChatBlock) // crash when no conversation thread exists!
|
||||
chatBlocksDao.upsertChatBlock(newChatBlock)
|
||||
|
||||
updateBlocks(newChatBlock)
|
||||
return chatMessagesFromSyncToProcess
|
||||
|
|
@ -727,11 +713,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
var blockContainingQueriedMessage: ChatBlockEntity? = null
|
||||
if (queriedMessageId != null) {
|
||||
val blocksContainingQueriedMessage =
|
||||
chatBlocksDao.getChatBlocksContainingMessageId(
|
||||
internalConversationId = internalConversationId,
|
||||
threadId = threadId,
|
||||
messageId = queriedMessageId.toLong()
|
||||
)
|
||||
chatBlocksDao.getChatBlocksContainingMessageId(internalConversationId, queriedMessageId.toLong())
|
||||
|
||||
val chatBlocks = blocksContainingQueriedMessage.first()
|
||||
if (chatBlocks.size > 1) {
|
||||
|
|
@ -750,10 +732,9 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
private suspend fun updateBlocks(chatBlock: ChatBlockEntity): ChatBlockEntity? {
|
||||
val connectedChatBlocks =
|
||||
chatBlocksDao.getConnectedChatBlocks(
|
||||
internalConversationId = internalConversationId,
|
||||
threadId = threadId,
|
||||
oldestMessageId = chatBlock.oldestMessageId,
|
||||
newestMessageId = chatBlock.newestMessageId
|
||||
internalConversationId,
|
||||
chatBlock.oldestMessageId,
|
||||
chatBlock.newestMessageId
|
||||
).first()
|
||||
|
||||
return if (connectedChatBlocks.size == 1) {
|
||||
|
|
@ -780,7 +761,6 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
internalConversationId = internalConversationId,
|
||||
accountId = conversationModel.accountId,
|
||||
token = conversationModel.token,
|
||||
threadId = threadId,
|
||||
oldestMessageId = oldestIdFromDbChatBlocks,
|
||||
newestMessageId = newestIdFromDbChatBlocks,
|
||||
hasHistory = hasHistory
|
||||
|
|
@ -804,8 +784,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
chatDao.getMessagesForConversationBeforeAndEqual(
|
||||
internalConversationId,
|
||||
messageId,
|
||||
messageLimit,
|
||||
threadId
|
||||
messageLimit
|
||||
).map {
|
||||
it.map(ChatMessageEntity::asModel)
|
||||
}.first()
|
||||
|
|
@ -819,8 +798,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
chatDao.getMessagesForConversationBefore(
|
||||
internalConversationId,
|
||||
messageId,
|
||||
messageLimit,
|
||||
threadId
|
||||
messageLimit
|
||||
).map {
|
||||
it.map(ChatMessageEntity::asModel)
|
||||
}.first()
|
||||
|
|
@ -860,8 +838,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
displayName: String,
|
||||
replyTo: Int,
|
||||
sendWithoutNotification: Boolean,
|
||||
referenceId: String,
|
||||
threadTitle: String?
|
||||
referenceId: String
|
||||
): Flow<Result<ChatMessage?>> {
|
||||
if (!networkMonitor.isOnline.value) {
|
||||
return flow {
|
||||
|
|
@ -877,16 +854,14 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
displayName,
|
||||
replyTo,
|
||||
sendWithoutNotification,
|
||||
referenceId,
|
||||
threadTitle
|
||||
referenceId
|
||||
)
|
||||
|
||||
val chatMessageModel = response.ocs?.data?.asModel()
|
||||
|
||||
val sentMessage = chatDao.getTempMessageForConversation(
|
||||
internalConversationId,
|
||||
referenceId,
|
||||
threadId
|
||||
referenceId
|
||||
).firstOrNull()
|
||||
|
||||
sentMessage?.let {
|
||||
|
|
@ -902,8 +877,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
|
||||
val failedMessage = chatDao.getTempMessageForConversation(
|
||||
internalConversationId,
|
||||
referenceId,
|
||||
threadId
|
||||
referenceId
|
||||
).firstOrNull()
|
||||
failedMessage?.let {
|
||||
it.sendStatus = SendStatus.FAILED
|
||||
|
|
@ -926,11 +900,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
sendWithoutNotification: Boolean,
|
||||
referenceId: String
|
||||
): Flow<Result<ChatMessage?>> {
|
||||
val messageToResend = chatDao.getTempMessageForConversation(
|
||||
internalConversationId,
|
||||
referenceId,
|
||||
threadId
|
||||
).firstOrNull()
|
||||
val messageToResend = chatDao.getTempMessageForConversation(internalConversationId, referenceId).firstOrNull()
|
||||
return if (messageToResend != null) {
|
||||
messageToResend.sendStatus = SendStatus.PENDING
|
||||
chatDao.updateChatMessage(messageToResend)
|
||||
|
|
@ -939,14 +909,13 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
_updateMessageFlow.emit(messageToResendModel)
|
||||
|
||||
sendChatMessage(
|
||||
credentials = credentials,
|
||||
url = url,
|
||||
message = message,
|
||||
displayName = displayName,
|
||||
replyTo = replyTo,
|
||||
sendWithoutNotification = sendWithoutNotification,
|
||||
referenceId = referenceId,
|
||||
threadTitle = null
|
||||
credentials,
|
||||
url,
|
||||
message,
|
||||
displayName,
|
||||
replyTo,
|
||||
sendWithoutNotification,
|
||||
referenceId
|
||||
)
|
||||
} else {
|
||||
flow {
|
||||
|
|
@ -980,7 +949,8 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
try {
|
||||
val messageToEdit = chatDao.getChatMessageForConversation(
|
||||
internalConversationId,
|
||||
message.jsonMessageId.toLong()
|
||||
message.jsonMessageId
|
||||
.toLong()
|
||||
).first()
|
||||
messageToEdit.message = editedMessageText
|
||||
chatDao.upsertChatMessage(messageToEdit)
|
||||
|
|
@ -994,7 +964,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
}
|
||||
|
||||
override suspend fun sendUnsentChatMessages(credentials: String, url: String) {
|
||||
val tempMessages = chatDao.getTempUnsentMessagesForConversation(internalConversationId, threadId).first()
|
||||
val tempMessages = chatDao.getTempUnsentMessagesForConversation(internalConversationId).first()
|
||||
tempMessages.sortedBy { it.internalId }.onEach {
|
||||
sendChatMessage(
|
||||
credentials,
|
||||
|
|
@ -1003,8 +973,7 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
it.actorDisplayName,
|
||||
it.parentMessageId?.toIntOrZero() ?: 0,
|
||||
it.silent,
|
||||
it.referenceId.orEmpty(),
|
||||
null
|
||||
it.referenceId.orEmpty()
|
||||
).collect { result ->
|
||||
if (result.isSuccess) {
|
||||
Log.d(TAG, "Sent temp message")
|
||||
|
|
@ -1073,7 +1042,6 @@ class OfflineFirstChatRepository @Inject constructor(
|
|||
internalId = "$internalConversationId@_temp_$currentTimeMillies",
|
||||
internalConversationId = internalConversationId,
|
||||
id = currentTimeWithoutYear.toLong(),
|
||||
threadId = threadId,
|
||||
message = message,
|
||||
deleted = false,
|
||||
token = conversationModel.token,
|
||||
|
|
|
|||
|
|
@ -144,8 +144,7 @@ class RetrofitChatNetwork(private val ncApi: NcApi, private val ncApiCoroutines:
|
|||
displayName: String,
|
||||
replyTo: Int,
|
||||
sendWithoutNotification: Boolean,
|
||||
referenceId: String,
|
||||
threadTitle: String?
|
||||
referenceId: String
|
||||
): ChatOverallSingleMessage =
|
||||
ncApiCoroutines.sendChatMessage(
|
||||
credentials,
|
||||
|
|
@ -154,8 +153,7 @@ class RetrofitChatNetwork(private val ncApi: NcApi, private val ncApiCoroutines:
|
|||
displayName,
|
||||
replyTo,
|
||||
sendWithoutNotification,
|
||||
referenceId,
|
||||
threadTitle
|
||||
referenceId
|
||||
)
|
||||
|
||||
override fun pullChatMessages(
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ import androidx.lifecycle.LiveData
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.google.gson.Gson
|
||||
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
|
||||
import com.nextcloud.talk.chat.data.ChatMessageRepository
|
||||
import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager
|
||||
import com.nextcloud.talk.chat.data.io.MediaPlayerManager
|
||||
|
|
@ -26,11 +24,9 @@ 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.conversationlist.viewmodels.ConversationsListViewModel.Companion.FOLLOWED_THREADS_EXIST
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.extensions.toIntOrZero
|
||||
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
|
||||
import com.nextcloud.talk.models.MessageDraft
|
||||
import com.nextcloud.talk.models.domain.ConversationModel
|
||||
import com.nextcloud.talk.models.domain.ReactionAddedModel
|
||||
import com.nextcloud.talk.models.domain.ReactionDeletedModel
|
||||
|
|
@ -41,13 +37,9 @@ import com.nextcloud.talk.models.json.conversations.RoomOverall
|
|||
import com.nextcloud.talk.models.json.generic.GenericOverall
|
||||
import com.nextcloud.talk.models.json.opengraph.Reference
|
||||
import com.nextcloud.talk.models.json.reminder.Reminder
|
||||
import com.nextcloud.talk.models.json.threads.ThreadInfo
|
||||
import com.nextcloud.talk.models.json.userAbsence.UserAbsenceData
|
||||
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
|
||||
import com.nextcloud.talk.threadsoverview.data.ThreadsRepository
|
||||
import com.nextcloud.talk.ui.PlaybackSpeed
|
||||
import com.nextcloud.talk.utils.ParticipantPermissions
|
||||
import com.nextcloud.talk.utils.UserIdUtils
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
|
|
@ -59,8 +51,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
|
@ -75,7 +65,6 @@ class ChatViewModel @Inject constructor(
|
|||
private val appPreferences: AppPreferences,
|
||||
private val chatNetworkDataSource: ChatNetworkDataSource,
|
||||
private val chatRepository: ChatMessageRepository,
|
||||
private val threadsRepository: ThreadsRepository,
|
||||
private val conversationRepository: OfflineConversationsRepository,
|
||||
private val reactionsRepository: ReactionsRepository,
|
||||
private val mediaRecorderManager: MediaRecorderManager,
|
||||
|
|
@ -84,9 +73,6 @@ class ChatViewModel @Inject constructor(
|
|||
) : ViewModel(),
|
||||
DefaultLifecycleObserver {
|
||||
|
||||
@Inject
|
||||
lateinit var arbitraryStorageManager: ArbitraryStorageManager
|
||||
|
||||
enum class LifeCycleFlag {
|
||||
PAUSED,
|
||||
RESUMED,
|
||||
|
|
@ -98,9 +84,6 @@ class ChatViewModel @Inject constructor(
|
|||
val disposableSet = mutableSetOf<Disposable>()
|
||||
var mediaPlayerDuration = mediaPlayerManager.mediaPlayerDuration
|
||||
val mediaPlayerPosition = mediaPlayerManager.mediaPlayerPosition
|
||||
var chatRoomToken: String = ""
|
||||
var messageDraft: MessageDraft = MessageDraft()
|
||||
lateinit var participantPermissions: ParticipantPermissions
|
||||
|
||||
fun getChatRepository(): ChatMessageRepository = chatRepository
|
||||
|
||||
|
|
@ -120,8 +103,6 @@ class ChatViewModel @Inject constructor(
|
|||
mediaRecorderManager.handleOnPause()
|
||||
chatRepository.handleOnPause()
|
||||
mediaPlayerManager.handleOnPause()
|
||||
|
||||
saveMessageDraft()
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
|
|
@ -175,9 +156,6 @@ class ChatViewModel @Inject constructor(
|
|||
val getContextChatMessages: LiveData<List<ChatMessageJson>>
|
||||
get() = _getContextChatMessages
|
||||
|
||||
private val _threadRetrieveState = MutableStateFlow<ThreadRetrieveUiState>(ThreadRetrieveUiState.None)
|
||||
val threadRetrieveState: StateFlow<ThreadRetrieveUiState> = _threadRetrieveState
|
||||
|
||||
val getOpenGraph: LiveData<Reference>
|
||||
get() = _getOpenGraph
|
||||
private val _getOpenGraph: MutableLiveData<Reference> = MutableLiveData()
|
||||
|
|
@ -292,9 +270,8 @@ class ChatViewModel @Inject constructor(
|
|||
val reactionDeletedViewState: LiveData<ViewState>
|
||||
get() = _reactionDeletedViewState
|
||||
|
||||
fun initData(credentials: String, urlForChatting: String, roomToken: String, threadId: Long?) {
|
||||
chatRepository.initData(credentials, urlForChatting, roomToken, threadId)
|
||||
chatRoomToken = roomToken
|
||||
fun initData(credentials: String, urlForChatting: String, roomToken: String) {
|
||||
chatRepository.initData(credentials, urlForChatting, roomToken)
|
||||
}
|
||||
|
||||
fun updateConversation(currentConversation: ConversationModel) {
|
||||
|
|
@ -316,10 +293,6 @@ class ChatViewModel @Inject constructor(
|
|||
} else {
|
||||
_getCapabilitiesViewState.value = GetCapabilitiesUpdateState(user.capabilities!!.spreedCapability!!)
|
||||
}
|
||||
participantPermissions = ParticipantPermissions(
|
||||
user.capabilities!!.spreedCapability!!,
|
||||
conversationModel
|
||||
)
|
||||
} else {
|
||||
chatNetworkDataSource.getCapabilities(user, token)
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
|
@ -335,10 +308,6 @@ class ChatViewModel @Inject constructor(
|
|||
} else {
|
||||
_getCapabilitiesViewState.value = GetCapabilitiesUpdateState(spreedCapabilities)
|
||||
}
|
||||
participantPermissions = ParticipantPermissions(
|
||||
spreedCapabilities,
|
||||
conversationModel
|
||||
)
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
|
|
@ -453,45 +422,6 @@ class ChatViewModel @Inject constructor(
|
|||
})
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
fun getThread(credentials: String, url: String) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val thread = threadsRepository.getThread(credentials, url)
|
||||
_threadRetrieveState.value = ThreadRetrieveUiState.Success(thread.ocs?.data)
|
||||
} catch (exception: Exception) {
|
||||
_threadRetrieveState.value = ThreadRetrieveUiState.Error(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught", "MagicNumber")
|
||||
fun setThreadNotificationLevel(credentials: String, url: String, level: Int) {
|
||||
fun updateFollowedThreadsIndicator(notificationLevel: Int?) {
|
||||
when (notificationLevel) {
|
||||
1, 2 -> {
|
||||
val accountId = UserIdUtils.getIdForUser(userProvider.currentUser.blockingGet())
|
||||
arbitraryStorageManager.storeStorageSetting(
|
||||
accountId,
|
||||
FOLLOWED_THREADS_EXIST,
|
||||
true.toString(),
|
||||
""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val thread = threadsRepository.setThreadNotificationLevel(credentials, url, level)
|
||||
updateFollowedThreadsIndicator(thread.ocs?.data?.attendee?.notificationLevel)
|
||||
_threadRetrieveState.value = ThreadRetrieveUiState.Success(thread.ocs?.data)
|
||||
} catch (exception: Exception) {
|
||||
_threadRetrieveState.value = ThreadRetrieveUiState.Error(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadMessages(withCredentials: String, withUrl: String) {
|
||||
val bundle = Bundle()
|
||||
bundle.putString(BundleKeys.KEY_CHAT_URL, withUrl)
|
||||
|
|
@ -703,20 +633,13 @@ class ChatViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun stopAndSendAudioRecording(roomToken: String = "", replyToMessageId: Int? = null, displayName: String) {
|
||||
fun stopAndSendAudioRecording(room: String, displayName: String, metaData: String) {
|
||||
stopAudioRecording()
|
||||
|
||||
if (mediaRecorderManager.mediaRecorderState != MediaRecorderManager.MediaRecorderState.ERROR) {
|
||||
val uri = Uri.fromFile(File(mediaRecorderManager.currentVoiceRecordFile))
|
||||
Log.d(TAG, "File uploaded")
|
||||
uploadFile(
|
||||
fileUri = uri.toString(),
|
||||
isVoiceMessage = true,
|
||||
caption = "",
|
||||
roomToken = roomToken,
|
||||
replyToMessageId = replyToMessageId,
|
||||
displayName = displayName
|
||||
)
|
||||
uploadFile(uri.toString(), room, displayName, metaData)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -729,38 +652,7 @@ class ChatViewModel @Inject constructor(
|
|||
|
||||
fun getCurrentVoiceRecordFile(): String = mediaRecorderManager.currentVoiceRecordFile
|
||||
|
||||
fun uploadFile(
|
||||
fileUri: String,
|
||||
isVoiceMessage: Boolean,
|
||||
caption: String = "",
|
||||
roomToken: String = "",
|
||||
replyToMessageId: Int? = null,
|
||||
displayName: String
|
||||
) {
|
||||
val metaDataMap = mutableMapOf<String, Any>()
|
||||
var room = ""
|
||||
|
||||
if (!participantPermissions.hasChatPermission()) {
|
||||
Log.w(TAG, "uploading file(s) is forbidden because of missing attendee permissions")
|
||||
return
|
||||
}
|
||||
|
||||
if (replyToMessageId != 0) {
|
||||
metaDataMap["replyTo"] = replyToMessageId.toString()
|
||||
}
|
||||
|
||||
if (isVoiceMessage) {
|
||||
metaDataMap["messageType"] = "voice-message"
|
||||
}
|
||||
|
||||
if (caption != "") {
|
||||
metaDataMap["caption"] = caption
|
||||
}
|
||||
|
||||
val metaData = Gson().toJson(metaDataMap)
|
||||
|
||||
room = if (roomToken == "") chatRoomToken else roomToken
|
||||
|
||||
fun uploadFile(fileUri: String, room: String, displayName: String, metaData: String) {
|
||||
try {
|
||||
require(fileUri.isNotEmpty())
|
||||
UploadAndShareFilesWorker.upload(
|
||||
|
|
@ -807,8 +699,6 @@ class ChatViewModel @Inject constructor(
|
|||
emit(message.first())
|
||||
}
|
||||
|
||||
suspend fun getNumberOfThreadReplies(threadId: Long): Int = chatRepository.getNumberOfThreadReplies(threadId)
|
||||
|
||||
fun setPlayBack(speed: PlaybackSpeed) {
|
||||
mediaPlayerManager.setPlayBackSpeed(speed)
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
|
|
@ -964,28 +854,6 @@ class ChatViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun updateMessageDraft() {
|
||||
val model = conversationRepository.getLocallyStoredConversation(chatRoomToken)
|
||||
model?.messageDraft?.let {
|
||||
messageDraft = it
|
||||
}
|
||||
}
|
||||
|
||||
fun saveMessageDraft() {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val model = conversationRepository.getLocallyStoredConversation(chatRoomToken)
|
||||
model?.let {
|
||||
it.messageDraft = messageDraft
|
||||
conversationRepository.updateConversation(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearThreadTitle() {
|
||||
messageDraft.threadTitle = ""
|
||||
saveMessageDraft()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = ChatViewModel::class.simpleName
|
||||
const val JOIN_ROOM_RETRY_COUNT: Long = 3
|
||||
|
|
@ -1003,10 +871,4 @@ class ChatViewModel @Inject constructor(
|
|||
data class Success(val statusCode: Int) : UnbindRoomUiState()
|
||||
data class Error(val message: String) : UnbindRoomUiState()
|
||||
}
|
||||
|
||||
sealed class ThreadRetrieveUiState {
|
||||
data object None : ThreadRetrieveUiState()
|
||||
data class Success(val thread: ThreadInfo?) : ThreadRetrieveUiState()
|
||||
data class Error(val exception: Exception) : ThreadRetrieveUiState()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,19 +90,11 @@ class MessageInputViewModel @Inject constructor(
|
|||
val getEditChatMessage: LiveData<IMessage?>
|
||||
get() = _getEditChatMessage
|
||||
|
||||
private val _getReplyChatMessage: MutableLiveData<ChatMessage?> = MutableLiveData()
|
||||
val getReplyChatMessage: LiveData<ChatMessage?>
|
||||
private val _getReplyChatMessage: MutableLiveData<IMessage?> = MutableLiveData()
|
||||
val getReplyChatMessage: LiveData<IMessage?>
|
||||
get() = _getReplyChatMessage
|
||||
|
||||
object CreateThreadStartState : ViewState
|
||||
class CreateThreadEditState : ViewState
|
||||
|
||||
private val _createThreadViewState: MutableLiveData<ViewState> = MutableLiveData(CreateThreadStartState)
|
||||
val createThreadViewState: LiveData<ViewState>
|
||||
get() = _createThreadViewState
|
||||
|
||||
sealed interface ViewState
|
||||
|
||||
object SendChatMessageStartState : ViewState
|
||||
class SendChatMessageSuccessState(val message: CharSequence) : ViewState
|
||||
class SendChatMessageErrorState(val message: CharSequence) : ViewState
|
||||
|
|
@ -133,8 +125,7 @@ class MessageInputViewModel @Inject constructor(
|
|||
message: String,
|
||||
displayName: String,
|
||||
replyTo: Int,
|
||||
sendWithoutNotification: Boolean,
|
||||
threadTitle: String?
|
||||
sendWithoutNotification: Boolean
|
||||
) {
|
||||
val referenceId = SendMessageUtils().generateReferenceId()
|
||||
Log.d(TAG, "Random SHA-256 Hash: $referenceId")
|
||||
|
|
@ -165,8 +156,7 @@ class MessageInputViewModel @Inject constructor(
|
|||
displayName,
|
||||
replyTo,
|
||||
sendWithoutNotification,
|
||||
referenceId,
|
||||
threadTitle
|
||||
referenceId
|
||||
).collect { result ->
|
||||
if (result.isSuccess) {
|
||||
Log.d(TAG, "received ref id: " + (result.getOrNull()?.referenceId ?: "none"))
|
||||
|
|
@ -213,7 +203,7 @@ class MessageInputViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun reply(message: ChatMessage?) {
|
||||
fun reply(message: IMessage?) {
|
||||
_getReplyChatMessage.postValue(message)
|
||||
}
|
||||
|
||||
|
|
@ -266,14 +256,6 @@ class MessageInputViewModel @Inject constructor(
|
|||
_callStartedFlow.postValue(Pair(recent, show))
|
||||
}
|
||||
|
||||
fun startThreadCreation() {
|
||||
_createThreadViewState.postValue(CreateThreadEditState())
|
||||
}
|
||||
|
||||
fun stopThreadCreation() {
|
||||
_createThreadViewState.postValue(CreateThreadStartState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = MessageInputViewModel::class.java.simpleName
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,29 +51,27 @@ fun StandardAppBar(title: String, menuItems: List<Pair<String, () -> Unit>>?) {
|
|||
}
|
||||
},
|
||||
actions = {
|
||||
if (!menuItems.isNullOrEmpty()) {
|
||||
Box {
|
||||
IconButton(onClick = { expanded = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = stringResource(R.string.nc_common_more_options)
|
||||
)
|
||||
}
|
||||
Box {
|
||||
IconButton(onClick = { expanded = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = stringResource(R.string.nc_common_more_options)
|
||||
)
|
||||
}
|
||||
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
modifier = Modifier.background(color = colorResource(id = R.color.bg_default))
|
||||
) {
|
||||
menuItems?.forEach { (label, action) ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(label) },
|
||||
onClick = {
|
||||
action()
|
||||
expanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false },
|
||||
modifier = Modifier.background(color = colorResource(id = R.color.bg_default))
|
||||
) {
|
||||
menuItems?.forEach { (label, action) ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(label) },
|
||||
onClick = {
|
||||
action()
|
||||
expanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ package com.nextcloud.talk.contacts
|
|||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.displayCutoutPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.material3.Scaffold
|
||||
|
|
@ -36,8 +35,7 @@ fun ContactsScreen(contactsViewModel: ContactsViewModel, uiState: ContactsUiStat
|
|||
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.displayCutoutPadding(),
|
||||
.statusBarsPadding(),
|
||||
topBar = {
|
||||
if (isSearchActive) {
|
||||
ContactsSearchAppBar(
|
||||
|
|
@ -66,8 +64,8 @@ fun ContactsScreen(contactsViewModel: ContactsViewModel, uiState: ContactsUiStat
|
|||
content = { paddingValues ->
|
||||
Column(
|
||||
Modifier
|
||||
.padding(0.dp, paddingValues.calculateTopPadding(), 0.dp, 0.dp)
|
||||
.background(colorResource(id = R.color.bg_default))
|
||||
.padding(0.dp, paddingValues.calculateTopPadding(), 0.dp, paddingValues.calculateBottomPadding())
|
||||
) {
|
||||
if (!isAddParticipants) {
|
||||
ConversationCreationOptions()
|
||||
|
|
|
|||
|
|
@ -29,14 +29,12 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.displayCutoutPadding
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
|
|
@ -175,9 +173,6 @@ fun ConversationCreationScreen(
|
|||
|
||||
ColoredStatusBar()
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.displayCutoutPadding(),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(text = stringResource(id = R.string.nc_new_conversation)) },
|
||||
|
|
@ -196,8 +191,8 @@ fun ConversationCreationScreen(
|
|||
content = { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(0.dp, paddingValues.calculateTopPadding(), 0.dp, 0.dp)
|
||||
.background(colorResource(id = R.color.bg_default))
|
||||
.padding(0.dp, paddingValues.calculateTopPadding(), 0.dp, paddingValues.calculateBottomPadding())
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -82,7 +82,6 @@ import com.nextcloud.talk.models.json.participants.Participant.ActorType.USERS
|
|||
import com.nextcloud.talk.models.json.participants.ParticipantsOverall
|
||||
import com.nextcloud.talk.repositories.conversations.ConversationsRepository
|
||||
import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
|
||||
import com.nextcloud.talk.threadsoverview.ThreadsOverviewActivity
|
||||
import com.nextcloud.talk.ui.dialog.DialogBanListFragment
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.CapabilitiesUtil
|
||||
|
|
@ -602,22 +601,6 @@ class ConversationInfoActivity :
|
|||
startActivity(intent)
|
||||
}
|
||||
|
||||
fun openThreadsOverview() {
|
||||
val threadsUrl = ApiUtils.getUrlForRecentThreads(
|
||||
version = 1,
|
||||
baseUrl = conversationUser.baseUrl,
|
||||
token = conversationToken
|
||||
)
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putString(KEY_ROOM_TOKEN, conversationToken)
|
||||
bundle.putString(ThreadsOverviewActivity.KEY_APPBAR_TITLE, getString(R.string.recent_threads))
|
||||
bundle.putString(ThreadsOverviewActivity.KEY_THREADS_SOURCE_URL, threadsUrl)
|
||||
val threadsOverviewIntent = Intent(context, ThreadsOverviewActivity::class.java)
|
||||
threadsOverviewIntent.putExtras(bundle)
|
||||
startActivity(threadsOverviewIntent)
|
||||
}
|
||||
|
||||
private fun setupWebinaryView() {
|
||||
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.WEBINARY_LOBBY) &&
|
||||
webinaryRoomType(conversation!!) &&
|
||||
|
|
@ -1074,17 +1057,9 @@ class ConversationInfoActivity :
|
|||
) {
|
||||
binding.sharedItemsButton.setOnClickListener { showSharedItems() }
|
||||
} else {
|
||||
binding.sharedItemsButton.visibility = GONE
|
||||
binding.sharedItems.visibility = GONE
|
||||
}
|
||||
|
||||
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.THREADS)) {
|
||||
binding.sharedItems.visibility = VISIBLE
|
||||
binding.showThreadsButton.setOnClickListener { openThreadsOverview() }
|
||||
} else {
|
||||
binding.showThreadsButton.visibility = GONE
|
||||
}
|
||||
|
||||
if (conversation!!.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
|
||||
hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.CONVERSATION_CREATION_ALL)
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -111,7 +111,6 @@ import com.nextcloud.talk.models.json.converters.EnumActorTypeConverter
|
|||
import com.nextcloud.talk.models.json.participants.Participant
|
||||
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
|
||||
import com.nextcloud.talk.settings.SettingsActivity
|
||||
import com.nextcloud.talk.threadsoverview.ThreadsOverviewActivity
|
||||
import com.nextcloud.talk.ui.BackgroundVoiceMessageCard
|
||||
import com.nextcloud.talk.ui.dialog.ChooseAccountDialogFragment
|
||||
import com.nextcloud.talk.ui.dialog.ChooseAccountShareToDialogFragment
|
||||
|
|
@ -268,8 +267,9 @@ class ConversationsListActivity :
|
|||
setupActionBar()
|
||||
setContentView(binding.root)
|
||||
initSystemBars()
|
||||
|
||||
viewThemeUtils.material.themeSearchCardView(binding.searchToolbar)
|
||||
viewThemeUtils.material.colorMaterialButtonContent(binding.menuButton, ColorRole.ON_SURFACE_VARIANT)
|
||||
viewThemeUtils.material.colorMaterialButtonContent(binding.menuButton, ColorRole.ON_SURFACE)
|
||||
viewThemeUtils.platform.colorTextView(binding.searchText, ColorRole.ON_SURFACE_VARIANT)
|
||||
|
||||
forwardMessage = intent.getBooleanExtra(KEY_FORWARD_MSG_FLAG, false)
|
||||
|
|
@ -343,7 +343,6 @@ class ConversationsListActivity :
|
|||
}
|
||||
|
||||
showSearchOrToolbar()
|
||||
conversationsListViewModel.checkIfThreadsExist()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
|
@ -360,7 +359,7 @@ class ConversationsListActivity :
|
|||
@Suppress("MagicNumber")
|
||||
private fun addEmptyItemForEdgeToEdgeIfNecessary() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||
adapter?.addScrollableFooter(SpacerItem(100))
|
||||
adapter?.addScrollableFooter(SpacerItem(200))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -429,23 +428,6 @@ class ConversationsListActivity :
|
|||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
conversationsListViewModel.threadsExistState.collect { state ->
|
||||
when (state) {
|
||||
is ConversationsListViewModel.ThreadsExistUiState.Success -> {
|
||||
binding.threadsButton.visibility = if (state.threadsExistence == true) {
|
||||
View.VISIBLE
|
||||
} else {
|
||||
View.GONE
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
binding.threadsButton.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
conversationsListViewModel.getRoomsFlow
|
||||
.onEach { list ->
|
||||
|
|
@ -725,7 +707,6 @@ class ConversationsListActivity :
|
|||
binding.newMentionPopupBubble.visibility = View.GONE
|
||||
}
|
||||
|
||||
layoutManager?.scrollToPositionWithOffset(0, 0)
|
||||
updateFilterConversationButtonColor()
|
||||
}
|
||||
|
||||
|
|
@ -1013,7 +994,7 @@ class ConversationsListActivity :
|
|||
private fun showSearchBar() {
|
||||
val layoutParams = binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
|
||||
binding.searchToolbar.visibility = View.VISIBLE
|
||||
binding.searchText.text = getString(R.string.appbar_search_in, getString(R.string.nc_app_product_name))
|
||||
binding.searchText.hint = getString(R.string.appbar_search_in, getString(R.string.nc_app_product_name))
|
||||
binding.conversationListToolbar.visibility = View.GONE
|
||||
// layoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout
|
||||
// .LayoutParams.SCROLL_FLAG_SNAP | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
|
||||
|
|
@ -1323,13 +1304,6 @@ class ConversationsListActivity :
|
|||
newFragment.show(supportFragmentManager, FilterConversationFragment.TAG)
|
||||
}
|
||||
|
||||
binding.threadsButton.setOnClickListener {
|
||||
openFollowedThreadsOverview()
|
||||
}
|
||||
binding.threadsButton.let {
|
||||
viewThemeUtils.platform.colorImageView(it, ColorRole.ON_SURFACE_VARIANT)
|
||||
}
|
||||
|
||||
binding.newMentionPopupBubble.visibility = View.GONE
|
||||
binding.newMentionPopupBubble.setOnClickListener {
|
||||
val layoutManager = binding.recyclerView.layoutManager as SmoothScrollLinearLayoutManager?
|
||||
|
|
@ -2231,32 +2205,17 @@ class ConversationsListActivity :
|
|||
binding.filterConversationsButton.let {
|
||||
viewThemeUtils.platform.colorImageView(
|
||||
it,
|
||||
ColorRole.ON_SURFACE_VARIANT
|
||||
ColorRole.ON_SURFACE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun openFollowedThreadsOverview() {
|
||||
val threadsUrl = ApiUtils.getUrlForSubscribedThreads(
|
||||
version = 1,
|
||||
baseUrl = currentUser!!.baseUrl
|
||||
)
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putString(ThreadsOverviewActivity.KEY_APPBAR_TITLE, getString(R.string.followed_threads))
|
||||
bundle.putString(ThreadsOverviewActivity.KEY_THREADS_SOURCE_URL, threadsUrl)
|
||||
val threadsOverviewIntent = Intent(context, ThreadsOverviewActivity::class.java)
|
||||
threadsOverviewIntent.putExtras(bundle)
|
||||
startActivity(threadsOverviewIntent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = ConversationsListActivity::class.java.simpleName
|
||||
const val UNREAD_BUBBLE_DELAY = 2500
|
||||
const val BOTTOM_SHEET_DELAY: Long = 2500
|
||||
private const val KEY_SEARCH_QUERY = "ConversationsListActivity.searchQuery"
|
||||
private const val CHAT_ACTIVITY_LOCAL_NAME = "com.nextcloud.talk.chat.ChatActivity"
|
||||
const val SEARCH_DEBOUNCE_INTERVAL_MS = 300
|
||||
const val SEARCH_MIN_CHARS = 1
|
||||
const val HTTP_UNAUTHORIZED = 401
|
||||
|
|
|
|||
|
|
@ -36,8 +36,4 @@ interface OfflineConversationsRepository {
|
|||
* to be handled asynchronously.
|
||||
*/
|
||||
fun getRoom(roomToken: String): Job
|
||||
|
||||
suspend fun updateConversation(conversationModel: ConversationModel)
|
||||
|
||||
suspend fun getLocallyStoredConversation(roomToken: String): ConversationModel?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,22 +98,12 @@ class OfflineFirstConversationsRepository @Inject constructor(
|
|||
runBlocking {
|
||||
_conversationFlow.emit(model)
|
||||
val entityList = listOf(model.asEntity())
|
||||
dao.upsertConversations(user.id!!, entityList)
|
||||
dao.upsertConversations(entityList)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override suspend fun updateConversation(conversationModel: ConversationModel) {
|
||||
val entity = conversationModel.asEntity()
|
||||
dao.updateConversation(entity)
|
||||
}
|
||||
|
||||
override suspend fun getLocallyStoredConversation(roomToken: String): ConversationModel? {
|
||||
val id = user.id!!
|
||||
return getConversation(id, roomToken)
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private suspend fun getRoomsFromServer(): List<ConversationEntity>? {
|
||||
var conversationsFromSync: List<ConversationEntity>? = null
|
||||
|
|
@ -136,7 +126,7 @@ class OfflineFirstConversationsRepository @Inject constructor(
|
|||
}
|
||||
|
||||
deleteLeftConversations(conversationsFromSync)
|
||||
dao.upsertConversations(user.id!!, conversationsFromSync)
|
||||
dao.upsertConversations(conversationsFromSync)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Something went wrong when fetching conversations", e)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,35 +10,21 @@ import android.util.Log
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
|
||||
import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.invitation.data.InvitationsModel
|
||||
import com.nextcloud.talk.invitation.data.InvitationsRepository
|
||||
import com.nextcloud.talk.threadsoverview.data.ThreadsRepository
|
||||
import com.nextcloud.talk.users.UserManager
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
|
||||
import com.nextcloud.talk.utils.SpreedFeatures
|
||||
import com.nextcloud.talk.utils.UserIdUtils
|
||||
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 kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
class ConversationsListViewModel @Inject constructor(
|
||||
private val repository: OfflineConversationsRepository,
|
||||
private val threadsRepository: ThreadsRepository,
|
||||
private val currentUserProvider: CurrentUserProviderNew,
|
||||
var userManager: UserManager
|
||||
) : ViewModel() {
|
||||
|
||||
|
|
@ -46,23 +32,10 @@ class ConversationsListViewModel @Inject constructor(
|
|||
lateinit var invitationsRepository: InvitationsRepository
|
||||
|
||||
@Inject
|
||||
lateinit var arbitraryStorageManager: ArbitraryStorageManager
|
||||
|
||||
private val _currentUser = currentUserProvider.currentUser.blockingGet()
|
||||
val currentUser: User = _currentUser
|
||||
val credentials = ApiUtils.getCredentials(_currentUser.username, _currentUser.token) ?: ""
|
||||
lateinit var currentUserProvider: CurrentUserProviderNew
|
||||
|
||||
sealed interface ViewState
|
||||
|
||||
sealed class ThreadsExistUiState {
|
||||
data object None : ThreadsExistUiState()
|
||||
data class Success(val threadsExistence: Boolean?) : ThreadsExistUiState()
|
||||
data class Error(val exception: Exception) : ThreadsExistUiState()
|
||||
}
|
||||
|
||||
private val _threadsExistState = MutableStateFlow<ThreadsExistUiState>(ThreadsExistUiState.None)
|
||||
val threadsExistState: StateFlow<ThreadsExistUiState> = _threadsExistState
|
||||
|
||||
object GetRoomsStartState : ViewState
|
||||
object GetRoomsErrorState : ViewState
|
||||
open class GetRoomsSuccessState(val listIsNotEmpty: Boolean) : ViewState
|
||||
|
|
@ -114,79 +87,6 @@ class ConversationsListViewModel @Inject constructor(
|
|||
repository.getRooms()
|
||||
}
|
||||
|
||||
fun checkIfThreadsExist() {
|
||||
val limitForFollowedThreadsExistenceCheck = 1
|
||||
val accountId = UserIdUtils.getIdForUser(currentUserProvider.currentUser.blockingGet())
|
||||
|
||||
fun isLastCheckTooOld(lastCheckDate: Long): Boolean {
|
||||
val currentTimeMillis = System.currentTimeMillis()
|
||||
val differenceMillis = currentTimeMillis - lastCheckDate
|
||||
val checkIntervalInMillies = TimeUnit.HOURS.toMillis(2)
|
||||
return differenceMillis > checkIntervalInMillies
|
||||
}
|
||||
|
||||
fun checkIfFollowedThreadsExist() {
|
||||
val threadsUrl = ApiUtils.getUrlForSubscribedThreads(
|
||||
version = 1,
|
||||
baseUrl = currentUser.baseUrl
|
||||
)
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val threads =
|
||||
threadsRepository.getThreads(credentials, threadsUrl, limitForFollowedThreadsExistenceCheck)
|
||||
val followedThreadsExistNew = threads.ocs?.data?.isNotEmpty()
|
||||
_threadsExistState.value = ThreadsExistUiState.Success(followedThreadsExistNew)
|
||||
val followedThreadsExistLastCheckNew = System.currentTimeMillis()
|
||||
arbitraryStorageManager.storeStorageSetting(
|
||||
accountId,
|
||||
FOLLOWED_THREADS_EXIST_LAST_CHECK,
|
||||
followedThreadsExistLastCheckNew.toString(),
|
||||
""
|
||||
)
|
||||
arbitraryStorageManager.storeStorageSetting(
|
||||
accountId,
|
||||
FOLLOWED_THREADS_EXIST,
|
||||
followedThreadsExistNew.toString(),
|
||||
""
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
_threadsExistState.value = ThreadsExistUiState.Error(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasSpreedFeatureCapability(currentUser.capabilities!!.spreedCapability!!, SpreedFeatures.THREADS)) {
|
||||
_threadsExistState.value = ThreadsExistUiState.Success(false)
|
||||
return
|
||||
}
|
||||
|
||||
val followedThreadsExistOld = arbitraryStorageManager.getStorageSetting(
|
||||
accountId,
|
||||
FOLLOWED_THREADS_EXIST,
|
||||
""
|
||||
).blockingGet()?.value?.toBoolean() ?: false
|
||||
|
||||
val followedThreadsExistLastCheckOld = arbitraryStorageManager.getStorageSetting(
|
||||
accountId,
|
||||
FOLLOWED_THREADS_EXIST_LAST_CHECK,
|
||||
""
|
||||
).blockingGet()?.value?.toLong()
|
||||
|
||||
if (followedThreadsExistOld) {
|
||||
Log.d(TAG, "followed threads exist for this user. No need to check again.")
|
||||
_threadsExistState.value = ThreadsExistUiState.Success(true)
|
||||
} else {
|
||||
if (followedThreadsExistLastCheckOld == null || isLastCheckTooOld(followedThreadsExistLastCheckOld)) {
|
||||
Log.d(TAG, "check if followed threads exist never happened or is too old. Checking now...")
|
||||
checkIfFollowedThreadsExist()
|
||||
} else {
|
||||
_threadsExistState.value = ThreadsExistUiState.Success(false)
|
||||
Log.d(TAG, "already checked in the last 2 hours if followed threads exist. Skip check.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class FederatedInvitationsObserver : Observer<InvitationsModel> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
// unused atm
|
||||
|
|
@ -222,7 +122,5 @@ class ConversationsListViewModel @Inject constructor(
|
|||
|
||||
companion object {
|
||||
private val TAG = ConversationsListViewModel::class.simpleName
|
||||
const val FOLLOWED_THREADS_EXIST_LAST_CHECK = "FOLLOWED_THREADS_EXIST_LAST_CHECK"
|
||||
const val FOLLOWED_THREADS_EXIST = "FOLLOWED_THREADS_EXIST"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,10 +9,6 @@
|
|||
*/
|
||||
package com.nextcloud.talk.dagger.modules
|
||||
|
||||
import android.content.Context
|
||||
import com.nextcloud.talk.account.data.LoginRepository
|
||||
import com.nextcloud.talk.account.data.io.LocalLoginDataSource
|
||||
import com.nextcloud.talk.account.data.network.NetworkLoginDataSource
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.api.NcApiCoroutines
|
||||
import com.nextcloud.talk.chat.data.ChatMessageRepository
|
||||
|
|
@ -58,14 +54,10 @@ import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
|
|||
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepositoryImpl
|
||||
import com.nextcloud.talk.shareditems.repositories.SharedItemsRepository
|
||||
import com.nextcloud.talk.shareditems.repositories.SharedItemsRepositoryImpl
|
||||
import com.nextcloud.talk.threadsoverview.data.ThreadsRepository
|
||||
import com.nextcloud.talk.threadsoverview.data.ThreadsRepositoryImpl
|
||||
import com.nextcloud.talk.translate.repositories.TranslateRepository
|
||||
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
|
||||
|
|
@ -192,27 +184,4 @@ class RepositoryModule {
|
|||
ncApiCoroutines: NcApiCoroutines,
|
||||
currentUserProviderNew: CurrentUserProviderNew
|
||||
): ConversationCreationRepository = ConversationCreationRepositoryImpl(ncApiCoroutines, currentUserProviderNew)
|
||||
|
||||
@Provides
|
||||
fun provideThreadsRepository(
|
||||
ncApiCoroutines: NcApiCoroutines,
|
||||
currentUserProviderNew: CurrentUserProviderNew
|
||||
): ThreadsRepository = ThreadsRepositoryImpl(ncApiCoroutines, currentUserProviderNew)
|
||||
|
||||
@Provides
|
||||
fun provideNetworkDataSource(okHttpClient: OkHttpClient): NetworkLoginDataSource =
|
||||
NetworkLoginDataSource(okHttpClient)
|
||||
|
||||
@Provides
|
||||
fun providesLocalDataSource(
|
||||
userManager: UserManager,
|
||||
appPreferences: AppPreferences,
|
||||
context: Context
|
||||
): LocalLoginDataSource = LocalLoginDataSource(userManager, appPreferences, context)
|
||||
|
||||
@Provides
|
||||
fun provideLoginRepository(
|
||||
networkLoginDataSource: NetworkLoginDataSource,
|
||||
localLoginDataSource: LocalLoginDataSource
|
||||
): LoginRepository = LoginRepository(networkLoginDataSource, localLoginDataSource)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,10 +34,9 @@ import java.security.KeyStoreException;
|
|||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.X509KeyManager;
|
||||
|
|
@ -49,7 +48,6 @@ import dagger.Provides;
|
|||
import io.reactivex.schedulers.Schedulers;
|
||||
import okhttp3.Authenticator;
|
||||
import okhttp3.Cache;
|
||||
import okhttp3.ConnectionSpec;
|
||||
import okhttp3.Credentials;
|
||||
import okhttp3.Dispatcher;
|
||||
import okhttp3.Interceptor;
|
||||
|
|
@ -62,6 +60,7 @@ import okhttp3.internal.tls.OkHostnameVerifier;
|
|||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
@Module(includes = DatabaseModule.class)
|
||||
public class RestModule {
|
||||
|
|
@ -219,16 +218,6 @@ public class RestModule {
|
|||
|
||||
httpClient.addInterceptor(new HeadersInterceptor());
|
||||
|
||||
List<ConnectionSpec> specs = new ArrayList<>();
|
||||
if (BuildConfig.DEBUG) {
|
||||
specs.add(ConnectionSpec.COMPATIBLE_TLS);
|
||||
specs.add(ConnectionSpec.CLEARTEXT);
|
||||
httpClient.connectionSpecs(specs);
|
||||
} else {
|
||||
specs.add(ConnectionSpec.COMPATIBLE_TLS);
|
||||
httpClient.connectionSpecs(specs);
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG && !context.getResources().getBoolean(R.bool.nc_is_debug)) {
|
||||
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
|
||||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||
|
|
@ -268,8 +257,8 @@ public class RestModule {
|
|||
|
||||
public static class HttpAuthenticator implements Authenticator {
|
||||
|
||||
private final String credentials;
|
||||
private final String authenticatorType;
|
||||
private String credentials;
|
||||
private String authenticatorType;
|
||||
|
||||
public HttpAuthenticator(@NonNull String credentials, @NonNull String authenticatorType) {
|
||||
this.credentials = credentials;
|
||||
|
|
@ -302,7 +291,7 @@ public class RestModule {
|
|||
|
||||
private class GetProxyRunnable implements Runnable {
|
||||
private volatile Proxy proxy;
|
||||
private final AppPreferences appPreferences;
|
||||
private AppPreferences appPreferences;
|
||||
|
||||
GetProxyRunnable(AppPreferences appPreferences) {
|
||||
this.appPreferences = appPreferences;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ package com.nextcloud.talk.dagger.modules
|
|||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.nextcloud.talk.account.viewmodels.BrowserLoginActivityViewModel
|
||||
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
|
||||
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel
|
||||
import com.nextcloud.talk.contacts.ContactsViewModel
|
||||
|
|
@ -28,7 +27,6 @@ import com.nextcloud.talk.polls.viewmodels.PollVoteViewModel
|
|||
import com.nextcloud.talk.raisehand.viewmodel.RaiseHandViewModel
|
||||
import com.nextcloud.talk.remotefilebrowser.viewmodels.RemoteFileBrowserItemsViewModel
|
||||
import com.nextcloud.talk.shareditems.viewmodels.SharedItemsViewModel
|
||||
import com.nextcloud.talk.threadsoverview.viewmodels.ThreadsOverviewViewModel
|
||||
import com.nextcloud.talk.translate.viewmodels.TranslateViewModel
|
||||
import com.nextcloud.talk.viewmodels.CallRecordingViewModel
|
||||
import dagger.Binds
|
||||
|
|
@ -156,14 +154,4 @@ abstract class ViewModelModule {
|
|||
@IntoMap
|
||||
@ViewModelKey(DiagnoseViewModel::class)
|
||||
abstract fun diagnoseViewModel(viewModel: DiagnoseViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(ThreadsOverviewViewModel::class)
|
||||
abstract fun threadsOverviewViewModel(viewModel: ThreadsOverviewViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(BrowserLoginActivityViewModel::class)
|
||||
abstract fun browserLoginActivityViewModel(viewModel: BrowserLoginActivityViewModel): ViewModel
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,24 +25,43 @@ interface ChatBlocksDao {
|
|||
SELECT *
|
||||
FROM ChatBlocks
|
||||
WHERE internalConversationId in (:internalConversationId)
|
||||
AND (threadId = :threadId OR (threadId IS NULL AND :threadId IS NULL))
|
||||
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,
|
||||
threadId: Long?,
|
||||
messageId: Long
|
||||
): Flow<List<ChatBlockEntity?>>
|
||||
fun getChatBlocksContainingMessageId(internalConversationId: String, messageId: Long): Flow<List<ChatBlockEntity?>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM ChatBlocks
|
||||
WHERE internalConversationId = :internalConversationId
|
||||
AND (threadId = :threadId OR (threadId IS NULL AND :threadId IS NULL))
|
||||
AND(
|
||||
(oldestMessageId <= :oldestMessageId AND newestMessageId >= :oldestMessageId)
|
||||
OR
|
||||
|
|
@ -55,24 +74,21 @@ interface ChatBlocksDao {
|
|||
)
|
||||
fun getConnectedChatBlocks(
|
||||
internalConversationId: String,
|
||||
threadId: Long?,
|
||||
oldestMessageId: Long,
|
||||
newestMessageId: Long
|
||||
): Flow<List<ChatBlockEntity>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT MAX(newestMessageId) as max_items
|
||||
FROM ChatBlocks
|
||||
WHERE internalConversationId = :internalConversationId
|
||||
AND (threadId = :threadId OR (threadId IS NULL AND :threadId IS NULL))
|
||||
"""
|
||||
)
|
||||
fun getNewestMessageIdFromChatBlocks(internalConversationId: String, threadId: Long?): Long
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertChatBlock(chatBlock: ChatBlockEntity)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
DELETE FROM ChatBlocks
|
||||
WHERE internalConversationId LIKE :pattern
|
||||
"""
|
||||
)
|
||||
fun clearChatBlocksForUser(pattern: String)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
DELETE FROM ChatBlocks
|
||||
|
|
|
|||
|
|
@ -18,6 +18,16 @@ import kotlinx.coroutines.flow.Flow
|
|||
@Dao
|
||||
@Suppress("Detekt.TooManyFunctions")
|
||||
interface ChatMessagesDao {
|
||||
@Query(
|
||||
"""
|
||||
SELECT MAX(id) as max_items
|
||||
FROM ChatMessages
|
||||
WHERE internalConversationId = :internalConversationId
|
||||
AND isTemporary = 0
|
||||
"""
|
||||
)
|
||||
fun getNewestMessageId(internalConversationId: String): Long
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
|
|
@ -47,14 +57,10 @@ interface ChatMessagesDao {
|
|||
WHERE internalConversationId = :internalConversationId
|
||||
AND isTemporary = 1
|
||||
AND sendStatus != 'SENT_PENDING_ACK'
|
||||
AND (:threadId IS NULL OR threadId = :threadId)
|
||||
ORDER BY timestamp DESC, id DESC
|
||||
"""
|
||||
)
|
||||
fun getTempUnsentMessagesForConversation(
|
||||
internalConversationId: String,
|
||||
threadId: Long?
|
||||
): Flow<List<ChatMessageEntity>>
|
||||
fun getTempUnsentMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
|
|
@ -63,15 +69,10 @@ interface ChatMessagesDao {
|
|||
WHERE internalConversationId = :internalConversationId
|
||||
AND referenceId = :referenceId
|
||||
AND isTemporary = 1
|
||||
AND (:threadId IS NULL OR threadId = :threadId)
|
||||
ORDER BY timestamp DESC, id DESC
|
||||
"""
|
||||
)
|
||||
fun getTempMessageForConversation(
|
||||
internalConversationId: String,
|
||||
referenceId: String,
|
||||
threadId: Long?
|
||||
): Flow<ChatMessageEntity?>
|
||||
fun getTempMessageForConversation(internalConversationId: String, referenceId: String): Flow<ChatMessageEntity?>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertChatMessages(chatMessages: List<ChatMessageEntity>)
|
||||
|
|
@ -83,8 +84,7 @@ interface ChatMessagesDao {
|
|||
"""
|
||||
SELECT *
|
||||
FROM ChatMessages
|
||||
WHERE internalConversationId = :internalConversationId
|
||||
AND id = :messageId
|
||||
WHERE internalConversationId = :internalConversationId AND id = :messageId
|
||||
"""
|
||||
)
|
||||
fun getChatMessageForConversation(internalConversationId: String, messageId: Long): Flow<ChatMessageEntity>
|
||||
|
|
@ -126,15 +126,10 @@ interface ChatMessagesDao {
|
|||
FROM ChatMessages
|
||||
WHERE internalConversationId = :internalConversationId AND id >= :messageId
|
||||
AND isTemporary = 0
|
||||
AND (:threadId IS NULL OR threadId = :threadId)
|
||||
ORDER BY timestamp ASC, id ASC
|
||||
"""
|
||||
)
|
||||
fun getMessagesForConversationSince(
|
||||
internalConversationId: String,
|
||||
messageId: Long,
|
||||
threadId: Long?
|
||||
): Flow<List<ChatMessageEntity>>
|
||||
fun getMessagesForConversationSince(internalConversationId: String, messageId: Long): Flow<List<ChatMessageEntity>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
|
|
@ -143,7 +138,6 @@ interface ChatMessagesDao {
|
|||
WHERE internalConversationId = :internalConversationId
|
||||
AND isTemporary = 0
|
||||
AND id < :messageId
|
||||
AND (:threadId IS NULL OR threadId = :threadId)
|
||||
ORDER BY timestamp DESC, id DESC
|
||||
LIMIT :limit
|
||||
"""
|
||||
|
|
@ -151,8 +145,7 @@ interface ChatMessagesDao {
|
|||
fun getMessagesForConversationBefore(
|
||||
internalConversationId: String,
|
||||
messageId: Long,
|
||||
limit: Int,
|
||||
threadId: Long?
|
||||
limit: Int
|
||||
): Flow<List<ChatMessageEntity>>
|
||||
|
||||
@Query(
|
||||
|
|
@ -162,7 +155,6 @@ interface ChatMessagesDao {
|
|||
WHERE internalConversationId = :internalConversationId
|
||||
AND isTemporary = 0
|
||||
AND id <= :messageId
|
||||
AND (:threadId IS NULL OR threadId = :threadId)
|
||||
ORDER BY timestamp DESC, id DESC
|
||||
LIMIT :limit
|
||||
"""
|
||||
|
|
@ -170,8 +162,7 @@ interface ChatMessagesDao {
|
|||
fun getMessagesForConversationBeforeAndEqual(
|
||||
internalConversationId: String,
|
||||
messageId: Long,
|
||||
limit: Int,
|
||||
threadId: Long?
|
||||
limit: Int
|
||||
): Flow<List<ChatMessageEntity>>
|
||||
|
||||
@Query(
|
||||
|
|
@ -180,16 +171,10 @@ interface ChatMessagesDao {
|
|||
FROM ChatMessages
|
||||
WHERE internalConversationId = :internalConversationId
|
||||
AND isTemporary = 0
|
||||
AND (:threadId IS NULL OR threadId = :threadId)
|
||||
AND id BETWEEN :newestMessageId AND :oldestMessageId
|
||||
"""
|
||||
)
|
||||
fun getCountBetweenMessageIds(
|
||||
internalConversationId: String,
|
||||
oldestMessageId: Long,
|
||||
newestMessageId: Long,
|
||||
threadId: Long?
|
||||
): Int
|
||||
fun getCountBetweenMessageIds(internalConversationId: String, oldestMessageId: Long, newestMessageId: Long): Int
|
||||
|
||||
@Query(
|
||||
"""
|
||||
|
|
@ -207,18 +192,4 @@ interface ChatMessagesDao {
|
|||
"""
|
||||
)
|
||||
fun deleteMessagesOlderThan(internalConversationId: String, messageId: Long)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT COUNT(*)
|
||||
FROM ChatMessages AS child
|
||||
INNER JOIN ChatMessages AS parent
|
||||
ON child.parent = parent.id
|
||||
WHERE child.internalConversationId = :internalConversationId
|
||||
AND child.isTemporary = 0
|
||||
AND child.messageType = 'comment'
|
||||
AND parent.threadId = :threadId
|
||||
"""
|
||||
)
|
||||
fun getNumberOfThreadReplies(internalConversationId: String, threadId: Long): Int
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,14 +8,11 @@
|
|||
package com.nextcloud.talk.data.database.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy.Companion.REPLACE
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Update
|
||||
import androidx.room.Upsert
|
||||
import com.nextcloud.talk.data.database.model.ConversationEntity
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
@Dao
|
||||
interface ConversationsDao {
|
||||
|
|
@ -25,19 +22,8 @@ interface ConversationsDao {
|
|||
@Query("SELECT * FROM Conversations where accountId = :accountId AND token = :token")
|
||||
fun getConversationForUser(accountId: Long, token: String): Flow<ConversationEntity?>
|
||||
|
||||
@Transaction
|
||||
suspend fun upsertConversations(accountId: Long, serverItems: List<ConversationEntity>) {
|
||||
serverItems.forEach { serverItem ->
|
||||
val existingItem = getConversationForUser(accountId, serverItem.token).first()
|
||||
if (existingItem != null) {
|
||||
val mergedItem = serverItem.copy()
|
||||
mergedItem.messageDraft = existingItem.messageDraft
|
||||
updateConversation(mergedItem)
|
||||
} else {
|
||||
insertConversation(serverItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@Upsert
|
||||
fun upsertConversations(conversationEntities: List<ConversationEntity>)
|
||||
|
||||
/**
|
||||
* Deletes rows in the db matching the specified [conversationIds]
|
||||
|
|
@ -50,12 +36,9 @@ interface ConversationsDao {
|
|||
)
|
||||
fun deleteConversations(conversationIds: List<String>)
|
||||
|
||||
@Update(onConflict = REPLACE)
|
||||
@Update
|
||||
fun updateConversation(conversationEntity: ConversationEntity)
|
||||
|
||||
@Insert(onConflict = REPLACE)
|
||||
fun insertConversation(conversation: ConversationEntity)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
DELETE FROM Conversations
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ fun ChatMessageJson.asEntity(accountId: Long) =
|
|||
accountId = accountId,
|
||||
id = id,
|
||||
internalConversationId = "$accountId@$token",
|
||||
threadId = threadId,
|
||||
isThread = hasThread,
|
||||
message = message!!,
|
||||
token = token!!,
|
||||
actorType = actorType!!,
|
||||
|
|
@ -42,9 +40,7 @@ fun ChatMessageJson.asEntity(accountId: Long) =
|
|||
lastEditTimestamp = lastEditTimestamp,
|
||||
deleted = deleted,
|
||||
referenceId = referenceId,
|
||||
silent = silent,
|
||||
threadTitle = threadTitle,
|
||||
threadReplies = threadReplies
|
||||
silent = silent
|
||||
)
|
||||
|
||||
fun ChatMessageEntity.asModel() =
|
||||
|
|
@ -52,8 +48,6 @@ fun ChatMessageEntity.asModel() =
|
|||
jsonMessageId = id.toInt(),
|
||||
message = message,
|
||||
token = token,
|
||||
threadId = threadId,
|
||||
isThread = isThread,
|
||||
actorType = actorType,
|
||||
actorId = actorId,
|
||||
actorDisplayName = actorDisplayName,
|
||||
|
|
@ -76,9 +70,7 @@ fun ChatMessageEntity.asModel() =
|
|||
isTemporary = isTemporary,
|
||||
sendStatus = sendStatus,
|
||||
readStatus = ReadStatus.NONE,
|
||||
silent = silent,
|
||||
threadTitle = threadTitle,
|
||||
threadReplies = threadReplies
|
||||
silent = silent
|
||||
)
|
||||
|
||||
fun ChatMessageJson.asModel() =
|
||||
|
|
@ -86,8 +78,6 @@ fun ChatMessageJson.asModel() =
|
|||
jsonMessageId = id.toInt(),
|
||||
message = message,
|
||||
token = token,
|
||||
threadId = threadId,
|
||||
isThread = hasThread,
|
||||
actorType = actorType,
|
||||
actorId = actorId,
|
||||
actorDisplayName = actorDisplayName,
|
||||
|
|
@ -107,7 +97,5 @@ fun ChatMessageJson.asModel() =
|
|||
lastEditTimestamp = lastEditTimestamp,
|
||||
isDeleted = deleted,
|
||||
referenceId = referenceId,
|
||||
silent = silent,
|
||||
threadTitle = threadTitle,
|
||||
threadReplies = threadReplies
|
||||
silent = silent
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
|
|
@ -63,8 +63,7 @@ fun ConversationModel.asEntity() =
|
|||
remoteToken = remoteToken,
|
||||
hasArchived = hasArchived,
|
||||
hasSensitive = hasSensitive,
|
||||
hasImportant = hasImportant,
|
||||
messageDraft = messageDraft
|
||||
hasImportant = hasImportant
|
||||
)
|
||||
|
||||
fun ConversationEntity.asModel() =
|
||||
|
|
@ -118,8 +117,7 @@ fun ConversationEntity.asModel() =
|
|||
remoteToken = remoteToken,
|
||||
hasArchived = hasArchived,
|
||||
hasSensitive = hasSensitive,
|
||||
hasImportant = hasImportant,
|
||||
messageDraft = messageDraft
|
||||
hasImportant = hasImportant
|
||||
)
|
||||
|
||||
fun Conversation.asEntity(accountId: Long) =
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ data class ChatBlockEntity(
|
|||
@ColumnInfo(name = "internalConversationId") var internalConversationId: String,
|
||||
@ColumnInfo(name = "accountId") var accountId: Long? = null,
|
||||
@ColumnInfo(name = "token") var token: String?,
|
||||
@ColumnInfo(name = "threadId") var threadId: Long? = null,
|
||||
@ColumnInfo(name = "oldestMessageId") var oldestMessageId: Long,
|
||||
@ColumnInfo(name = "newestMessageId") var newestMessageId: Long,
|
||||
@ColumnInfo(name = "hasHistory") var hasHistory: Boolean
|
||||
|
|
|
|||
|
|
@ -41,8 +41,7 @@ data class ChatMessageEntity(
|
|||
@ColumnInfo(name = "id") var id: Long = 0,
|
||||
// accountId@roomtoken
|
||||
@ColumnInfo(name = "internalConversationId") var internalConversationId: String,
|
||||
@ColumnInfo(name = "threadId") var threadId: Long? = null,
|
||||
@ColumnInfo(name = "isThread") var isThread: Boolean = false,
|
||||
|
||||
@ColumnInfo(name = "actorDisplayName") var actorDisplayName: String,
|
||||
@ColumnInfo(name = "message") var message: String,
|
||||
|
||||
|
|
@ -68,7 +67,5 @@ data class ChatMessageEntity(
|
|||
@ColumnInfo(name = "sendStatus") var sendStatus: SendStatus? = null,
|
||||
@ColumnInfo(name = "silent") var silent: Boolean = false,
|
||||
@ColumnInfo(name = "systemMessage") var systemMessageType: ChatMessage.SystemMessageType,
|
||||
@ColumnInfo(name = "threadTitle") var threadTitle: String? = null,
|
||||
@ColumnInfo(name = "threadReplies") var threadReplies: Int? = 0,
|
||||
@ColumnInfo(name = "timestamp") var timestamp: Long = 0
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import androidx.room.ForeignKey
|
|||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import com.nextcloud.talk.data.user.model.UserEntity
|
||||
import com.nextcloud.talk.models.MessageDraft
|
||||
import com.nextcloud.talk.models.json.conversations.ConversationEnums
|
||||
import com.nextcloud.talk.models.json.participants.Participant
|
||||
|
||||
|
|
@ -97,8 +96,7 @@ data class ConversationEntity(
|
|||
@ColumnInfo(name = "unreadMessages") var unreadMessages: Int = 0,
|
||||
@ColumnInfo(name = "hasArchived") var hasArchived: Boolean = false,
|
||||
@ColumnInfo(name = "hasSensitive") var hasSensitive: Boolean = false,
|
||||
@ColumnInfo(name = "hasImportant") var hasImportant: Boolean = false,
|
||||
@ColumnInfo(name = "messageDraft") var messageDraft: MessageDraft? = MessageDraft()
|
||||
@ColumnInfo(name = "hasImportant") var hasImportant: Boolean = false
|
||||
// missing/not needed: attendeeId
|
||||
// missing/not needed: attendeePin
|
||||
// missing/not needed: attendeePermissions
|
||||
|
|
|
|||
|
|
@ -89,17 +89,6 @@ object Migrations {
|
|||
}
|
||||
}
|
||||
|
||||
val MIGRATION_17_19 = object : Migration(17, 19) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
Log.i(
|
||||
"Migrations",
|
||||
"Migrating 17 to 19 (migration 17 to 18 had bugs in app version v22.0.0 Alpha 11 and " +
|
||||
"v22.0.0 Alpha 12)"
|
||||
)
|
||||
migrateToMessageThreads(db)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
fun migrateToRoom(db: SupportSQLiteDatabase) {
|
||||
|
|
@ -308,7 +297,7 @@ object Migrations {
|
|||
"ADD COLUMN hasArchived INTEGER NOT NULL DEFAULT 0;"
|
||||
)
|
||||
} catch (e: SQLException) {
|
||||
Log.i("Migrations", "hasArchived already exists", e)
|
||||
Log.i("Migrations", "hasArchived already exists")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -319,7 +308,7 @@ object Migrations {
|
|||
"ADD COLUMN objectId TEXT NOT NULL DEFAULT '';"
|
||||
)
|
||||
} catch (e: SQLException) {
|
||||
Log.i("Migrations", "Something went wrong when adding column objectId to table Conversations", e)
|
||||
Log.i("Migrations", "Something went wrong when adding column objectId to table Conversations")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -330,7 +319,7 @@ object Migrations {
|
|||
"ADD COLUMN hasSensitive INTEGER NOT NULL DEFAULT 0;"
|
||||
)
|
||||
} catch (e: SQLException) {
|
||||
Log.i("Migrations", "Something went wrong when adding column hasSensitive to table Conversations", e)
|
||||
Log.i("Migrations", "Something went wrong when adding column hasSensitive to table Conversations")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -341,43 +330,7 @@ object Migrations {
|
|||
"ADD COLUMN hasImportant INTEGER NOT NULL DEFAULT 0;"
|
||||
)
|
||||
} catch (e: SQLException) {
|
||||
Log.i("Migrations", "Something went wrong when adding column hasImportant to table Conversations", e)
|
||||
}
|
||||
}
|
||||
|
||||
fun migrateToMessageThreads(db: SupportSQLiteDatabase) {
|
||||
try {
|
||||
db.execSQL(
|
||||
"ALTER TABLE ChatBlocks " +
|
||||
"ADD COLUMN threadId INTEGER;"
|
||||
)
|
||||
|
||||
db.execSQL(
|
||||
"ALTER TABLE ChatMessages " +
|
||||
"ADD COLUMN threadId INTEGER;"
|
||||
)
|
||||
|
||||
db.execSQL(
|
||||
"ALTER TABLE ChatMessages " +
|
||||
"ADD COLUMN isThread INTEGER NOT NULL DEFAULT 0;"
|
||||
)
|
||||
|
||||
// Foreign key constraints are not active during migration.
|
||||
// At least db.execSQL("PRAGMA foreign_keys=ON;") etc did not help.
|
||||
// Because of this it is not enough to just clear the Conversations table (to have cascade deletion in
|
||||
// other tables), but all related tables have to be cleared with SQL statement as well.
|
||||
|
||||
db.execSQL(
|
||||
"DELETE FROM Conversations"
|
||||
)
|
||||
db.execSQL(
|
||||
"DELETE FROM ChatMessages"
|
||||
)
|
||||
db.execSQL(
|
||||
"DELETE FROM ChatBlocks"
|
||||
)
|
||||
} catch (e: SQLException) {
|
||||
Log.i("Migrations", "Something went wrong when migrating to messageThreads", e)
|
||||
Log.i("Migrations", "Something went wrong when adding column hasImportant to table Conversations")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -388,7 +341,7 @@ object Migrations {
|
|||
"ADD COLUMN referenceId TEXT;"
|
||||
)
|
||||
} catch (e: SQLException) {
|
||||
Log.i("Migrations", "Something went wrong when adding column referenceId to table ChatMessages", e)
|
||||
Log.i("Migrations", "Something went wrong when adding column referenceId to table ChatMessages")
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -397,7 +350,7 @@ object Migrations {
|
|||
"ADD COLUMN isTemporary INTEGER NOT NULL DEFAULT 0;"
|
||||
)
|
||||
} catch (e: SQLException) {
|
||||
Log.i("Migrations", "Something went wrong when adding column isTemporary to table ChatMessages", e)
|
||||
Log.i("Migrations", "Something went wrong when adding column isTemporary to table ChatMessages")
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -406,7 +359,7 @@ object Migrations {
|
|||
"ADD COLUMN sendingFailed INTEGER NOT NULL DEFAULT 0;"
|
||||
)
|
||||
} catch (e: SQLException) {
|
||||
Log.i("Migrations", "Something went wrong when adding column sendingFailed to table ChatMessages", e)
|
||||
Log.i("Migrations", "Something went wrong when adding column sendingFailed to table ChatMessages")
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -415,7 +368,7 @@ object Migrations {
|
|||
"ADD COLUMN silent INTEGER NOT NULL DEFAULT 0;"
|
||||
)
|
||||
} catch (e: SQLException) {
|
||||
Log.i("Migrations", "Something went wrong when adding column silent to table ChatMessages", e)
|
||||
Log.i("Migrations", "Something went wrong when adding column silent to table ChatMessages")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import com.nextcloud.talk.data.storage.ArbitraryStoragesDao
|
|||
import com.nextcloud.talk.data.storage.model.ArbitraryStorageEntity
|
||||
import com.nextcloud.talk.data.user.UsersDao
|
||||
import com.nextcloud.talk.data.user.model.UserEntity
|
||||
import com.nextcloud.talk.models.MessageDraftConverter
|
||||
import net.zetetic.database.sqlcipher.SupportOpenHelperFactory
|
||||
import java.util.Locale
|
||||
|
||||
|
|
@ -48,12 +47,10 @@ import java.util.Locale
|
|||
ChatMessageEntity::class,
|
||||
ChatBlockEntity::class
|
||||
],
|
||||
version = 21,
|
||||
version = 17,
|
||||
autoMigrations = [
|
||||
AutoMigration(from = 9, to = 10),
|
||||
AutoMigration(from = 16, to = 17, spec = AutoMigration16To17::class),
|
||||
AutoMigration(from = 19, to = 20),
|
||||
AutoMigration(from = 20, to = 21)
|
||||
AutoMigration(from = 16, to = 17, spec = AutoMigration16To17::class)
|
||||
],
|
||||
exportSchema = true
|
||||
)
|
||||
|
|
@ -66,10 +63,8 @@ import java.util.Locale
|
|||
HashMapHashMapConverter::class,
|
||||
LinkedHashMapConverter::class,
|
||||
ArrayListConverter::class,
|
||||
SendStatusConverter::class,
|
||||
MessageDraftConverter::class
|
||||
SendStatusConverter::class
|
||||
)
|
||||
@Suppress("MagicNumber")
|
||||
abstract class TalkDatabase : RoomDatabase() {
|
||||
abstract fun usersDao(): UsersDao
|
||||
abstract fun conversationsDao(): ConversationsDao
|
||||
|
|
@ -90,21 +85,6 @@ abstract class TalkDatabase : RoomDatabase() {
|
|||
instance ?: build(context).also { instance = it }
|
||||
}
|
||||
|
||||
// If editing the migrations, please add a test case in MigrationsTest under androidTest/data
|
||||
val MIGRATIONS = arrayOf(
|
||||
Migrations.MIGRATION_6_8,
|
||||
Migrations.MIGRATION_7_8,
|
||||
Migrations.MIGRATION_8_9,
|
||||
Migrations.MIGRATION_10_11,
|
||||
Migrations.MIGRATION_11_12,
|
||||
Migrations.MIGRATION_12_13,
|
||||
Migrations.MIGRATION_13_14,
|
||||
Migrations.MIGRATION_14_15,
|
||||
Migrations.MIGRATION_15_16,
|
||||
Migrations.MIGRATION_17_19
|
||||
)
|
||||
|
||||
@Suppress("SpreadOperator")
|
||||
private fun build(context: Context): TalkDatabase {
|
||||
val passCharArray = context.getString(R.string.nc_talk_database_encryption_key).toCharArray()
|
||||
val passphrase: ByteArray = getBytesFromChars(passCharArray)
|
||||
|
|
@ -124,8 +104,17 @@ abstract class TalkDatabase : RoomDatabase() {
|
|||
.databaseBuilder(context.applicationContext, TalkDatabase::class.java, dbName)
|
||||
// comment out openHelperFactory to view the database entries in Android Studio for debugging
|
||||
.openHelperFactory(factory)
|
||||
.fallbackToDestructiveMigrationFrom(true, 18)
|
||||
.addMigrations(*MIGRATIONS) // * converts migrations to vararg
|
||||
.addMigrations(
|
||||
Migrations.MIGRATION_6_8,
|
||||
Migrations.MIGRATION_7_8,
|
||||
Migrations.MIGRATION_8_9,
|
||||
Migrations.MIGRATION_10_11,
|
||||
Migrations.MIGRATION_11_12,
|
||||
Migrations.MIGRATION_12_13,
|
||||
Migrations.MIGRATION_13_14,
|
||||
Migrations.MIGRATION_14_15,
|
||||
Migrations.MIGRATION_15_16
|
||||
)
|
||||
.allowMainThreadQueries()
|
||||
.addCallback(
|
||||
object : Callback() {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import android.widget.Toast
|
|||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.displayCutoutPadding
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
|
|
@ -115,8 +114,7 @@ class DiagnoseActivity : BaseActivity() {
|
|||
ColoredStatusBar()
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.displayCutoutPadding(),
|
||||
.statusBarsPadding(),
|
||||
topBar = {
|
||||
StandardAppBar(
|
||||
title = stringResource(R.string.nc_settings_diagnose_title),
|
||||
|
|
@ -128,13 +126,8 @@ class DiagnoseActivity : BaseActivity() {
|
|||
|
||||
Column(
|
||||
Modifier
|
||||
.padding(0.dp, paddingValues.calculateTopPadding(), 0.dp, 0.dp)
|
||||
.background(backgroundColor)
|
||||
.padding(
|
||||
0.dp,
|
||||
paddingValues.calculateTopPadding(),
|
||||
0.dp,
|
||||
paddingValues.calculateBottomPadding()
|
||||
)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
DiagnoseContentComposable(
|
||||
|
|
|
|||
|
|
@ -11,18 +11,9 @@
|
|||
package com.nextcloud.talk.extensions
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapShader
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.graphics.Shader
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.util.Log
|
||||
|
|
@ -30,7 +21,6 @@ import android.widget.ImageView
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.createBitmap
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import coil.annotation.ExperimentalCoilApi
|
||||
import coil.imageLoader
|
||||
|
|
@ -52,7 +42,6 @@ import com.nextcloud.talk.utils.ApiUtils
|
|||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
import com.nextcloud.talk.utils.TextDrawable
|
||||
import java.util.Locale
|
||||
import kotlin.math.min
|
||||
|
||||
private const val ROUNDING_PIXEL = 16f
|
||||
private const val TAG = "ImageViewExtensions"
|
||||
|
|
@ -302,12 +291,18 @@ fun ImageView.loadSystemAvatar(): io.reactivex.disposables.Disposable {
|
|||
)
|
||||
}
|
||||
|
||||
fun ImageView.loadNoteToSelfAvatar() {
|
||||
fun ImageView.loadNoteToSelfAvatar(): io.reactivex.disposables.Disposable {
|
||||
val layers = arrayOfNulls<Drawable>(2)
|
||||
layers[0] = ContextCompat.getDrawable(context, R.drawable.ic_launcher_background)
|
||||
layers[1] = ContextCompat.getDrawable(context, R.drawable.ic_note_to_self)
|
||||
val layerDrawable = LayerDrawable(layers)
|
||||
setImageDrawable(CircularDrawable(layerDrawable))
|
||||
val data: Any = layerDrawable
|
||||
|
||||
return DisposableWrapper(
|
||||
load(data) {
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun ImageView.loadFirstLetterAvatar(name: String): io.reactivex.disposables.Disposable {
|
||||
|
|
@ -421,77 +416,3 @@ private class DisposableWrapper(private val disposable: coil.request.Disposable)
|
|||
|
||||
override fun isDisposed(): Boolean = disposable.isDisposed
|
||||
}
|
||||
|
||||
private class CircularDrawable(private val sourceDrawable: Drawable) : Drawable() {
|
||||
|
||||
private val bitmap: Bitmap = drawableToBitmap(sourceDrawable)
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
|
||||
}
|
||||
|
||||
private val rect = RectF()
|
||||
private var radius = 0f
|
||||
|
||||
override fun onBoundsChange(bounds: Rect) {
|
||||
super.onBoundsChange(bounds)
|
||||
rect.set(bounds)
|
||||
|
||||
radius = min(rect.width() / 2.0f, rect.height() / 2.0f)
|
||||
|
||||
val matrix = Matrix()
|
||||
val scale: Float
|
||||
var dx = 0f
|
||||
var dy = 0f
|
||||
|
||||
if (bitmap.width * rect.height() > rect.width() * bitmap.height) {
|
||||
// Taller than wide, scale to height and center horizontally
|
||||
scale = rect.height() / bitmap.height.toFloat()
|
||||
dx = (rect.width() - bitmap.width * scale) / 2.0f
|
||||
} else {
|
||||
// Wider than tall, scale to width and center vertically
|
||||
scale = rect.width() / bitmap.width.toFloat()
|
||||
dy = (rect.height() - bitmap.height * scale) / 2.0f
|
||||
}
|
||||
|
||||
matrix.setScale(scale, scale)
|
||||
matrix.postTranslate(dx.toInt().toFloat() + rect.left, dy.toInt().toFloat() + rect.top)
|
||||
paint.shader.setLocalMatrix(matrix)
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas) {
|
||||
canvas.drawCircle(rect.centerX(), rect.centerY(), radius, paint)
|
||||
}
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
paint.alpha = alpha
|
||||
}
|
||||
|
||||
override fun setColorFilter(colorFilter: ColorFilter?) {
|
||||
paint.colorFilter = colorFilter
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
"This method is no longer used in graphics optimizations",
|
||||
ReplaceWith("PixelFormat.TRANSLUCENT", "android.graphics.PixelFormat")
|
||||
)
|
||||
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
|
||||
|
||||
override fun getIntrinsicWidth(): Int = sourceDrawable.intrinsicWidth
|
||||
|
||||
override fun getIntrinsicHeight(): Int = sourceDrawable.intrinsicHeight
|
||||
|
||||
companion object {
|
||||
|
||||
private fun drawableToBitmap(drawable: Drawable): Bitmap {
|
||||
if (drawable is BitmapDrawable) {
|
||||
if (drawable.bitmap != null) {
|
||||
return drawable.bitmap
|
||||
}
|
||||
}
|
||||
|
||||
val width = if (drawable.intrinsicWidth > 0) drawable.intrinsicWidth else 1
|
||||
val height = if (drawable.intrinsicHeight > 0) drawable.intrinsicHeight else 1
|
||||
return drawable.toBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2021-2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-FileCopyrightText: 2021-2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-FileCopyrightText: 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* SPDX-FileCopyrightText: 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
@ -134,6 +134,8 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
private lateinit var signatureVerification: SignatureVerification
|
||||
private var context: Context? = null
|
||||
private var conversationType: String? = "one2one"
|
||||
private var muteCall = false
|
||||
private var importantConversation = false
|
||||
private lateinit var notificationManager: NotificationManagerCompat
|
||||
|
||||
override fun doWork(): Result {
|
||||
|
|
@ -184,7 +186,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
|
||||
private fun handleTestPushMessage() {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.flags = getIntentFlags()
|
||||
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
showNotification(intent, null)
|
||||
}
|
||||
|
||||
|
|
@ -199,7 +201,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
|
||||
private fun handleRemoteTalkSharePushMessage() {
|
||||
val mainActivityIntent = Intent(context, MainActivity::class.java)
|
||||
mainActivityIntent.flags = getIntentFlags()
|
||||
mainActivityIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
val bundle = Bundle()
|
||||
bundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!)
|
||||
bundle.putBoolean(KEY_REMOTE_TALK_SHARE, true)
|
||||
|
|
@ -253,7 +255,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
val bundle = createBundle(conversation)
|
||||
|
||||
fullScreenIntent.putExtras(bundle)
|
||||
fullScreenIntent.flags = getIntentFlags()
|
||||
fullScreenIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
|
||||
val requestCode = System.currentTimeMillis().toInt()
|
||||
|
||||
|
|
@ -423,7 +425,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
pushMessage.timestamp = ncNotification.datetime!!.millis
|
||||
|
||||
if (ncNotification.messageRichParameters != null &&
|
||||
ncNotification.messageRichParameters!!.isNotEmpty()
|
||||
ncNotification.messageRichParameters!!.size > 0
|
||||
) {
|
||||
pushMessage.text = getParsedMessage(
|
||||
ncNotification.messageRich,
|
||||
|
|
@ -434,11 +436,11 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
}
|
||||
|
||||
val subjectRichParameters = ncNotification.subjectRichParameters
|
||||
if (subjectRichParameters != null && subjectRichParameters.isNotEmpty()) {
|
||||
if (subjectRichParameters != null && subjectRichParameters.size > 0) {
|
||||
val callHashMap = subjectRichParameters["call"]
|
||||
val userHashMap = subjectRichParameters["user"]
|
||||
val guestHashMap = subjectRichParameters["guest"]
|
||||
if (callHashMap != null && callHashMap.isNotEmpty() && callHashMap.containsKey("name")) {
|
||||
if (callHashMap != null && callHashMap.size > 0 && callHashMap.containsKey("name")) {
|
||||
if (subjectRichParameters.containsKey("reaction")) {
|
||||
pushMessage.subject = ""
|
||||
} else if (ncNotification.objectType == "chat") {
|
||||
|
|
@ -488,7 +490,15 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
else -> Log.e(TAG, "unknown pushMessage.type")
|
||||
}
|
||||
|
||||
val pendingIntent = createUniquePendingIntent(intent)
|
||||
// Use unique request code to make sure that a new PendingIntent gets created for each notification
|
||||
// See https://github.com/nextcloud/talk-android/issues/2111
|
||||
val requestCode = System.currentTimeMillis().toInt()
|
||||
val intentFlag: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.FLAG_MUTABLE
|
||||
} else {
|
||||
0
|
||||
}
|
||||
val pendingIntent = PendingIntent.getActivity(context, requestCode, intent, intentFlag)
|
||||
val uri = signatureVerification.user!!.baseUrl!!.toUri()
|
||||
val baseUrl = uri.host
|
||||
|
||||
|
|
@ -520,7 +530,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
if ((TYPE_CHAT == pushMessage.type || TYPE_REMINDER == pushMessage.type) &&
|
||||
pushMessage.notificationUser != null
|
||||
) {
|
||||
prepareChatNotification(notificationBuilder, activeStatusBarNotification)
|
||||
prepareChatNotification(notificationBuilder, activeStatusBarNotification, systemNotificationId)
|
||||
addReplyAction(notificationBuilder, systemNotificationId)
|
||||
addMarkAsReadAction(notificationBuilder, systemNotificationId)
|
||||
}
|
||||
|
|
@ -626,7 +636,8 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
|
||||
private fun prepareChatNotification(
|
||||
notificationBuilder: NotificationCompat.Builder,
|
||||
activeStatusBarNotification: StatusBarNotification?
|
||||
activeStatusBarNotification: StatusBarNotification?,
|
||||
systemNotificationId: Int
|
||||
) {
|
||||
val notificationUser = pushMessage.notificationUser
|
||||
val userType = notificationUser!!.type
|
||||
|
|
@ -936,14 +947,14 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
)
|
||||
|
||||
if (isOngoingCallNotificationVisible) {
|
||||
val notificationBuilder = NotificationCompat.Builder(
|
||||
val notificationBuilder: NotificationCompat.Builder?
|
||||
|
||||
notificationBuilder = NotificationCompat.Builder(
|
||||
context!!,
|
||||
NotificationUtils.NotificationChannels
|
||||
.NOTIFICATION_CHANNEL_MESSAGES_V4.name
|
||||
)
|
||||
|
||||
val intent = createMainActivityIntent()
|
||||
|
||||
val notification: Notification = notificationBuilder
|
||||
.setContentTitle(
|
||||
String.format(
|
||||
|
|
@ -955,7 +966,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
.setOngoing(false)
|
||||
.setAutoCancel(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setContentIntent(createUniquePendingIntent(intent))
|
||||
.setContentIntent(getIntentToOpenConversation())
|
||||
.build()
|
||||
|
||||
val notificationId: Int = SystemClock.uptimeMillis().toInt()
|
||||
|
|
@ -978,7 +989,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
|
||||
private fun createMainActivityIntent(): Intent {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.flags = getIntentFlags()
|
||||
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
val bundle = Bundle()
|
||||
bundle.putString(KEY_ROOM_TOKEN, pushMessage.id)
|
||||
bundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!)
|
||||
|
|
@ -986,20 +997,23 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
|||
return intent
|
||||
}
|
||||
|
||||
private fun createUniquePendingIntent(intent: Intent): PendingIntent? {
|
||||
// Use unique request code to make sure that a new PendingIntent gets created for each notification
|
||||
// See https://github.com/nextcloud/talk-android/issues/2111
|
||||
private fun getIntentToOpenConversation(): PendingIntent? {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
val bundle = Bundle()
|
||||
bundle.putString(KEY_ROOM_TOKEN, pushMessage.id)
|
||||
bundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!)
|
||||
intent.putExtras(bundle)
|
||||
|
||||
val requestCode = System.currentTimeMillis().toInt()
|
||||
val intentFlag: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
PendingIntent.FLAG_MUTABLE
|
||||
} else {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
0
|
||||
}
|
||||
return PendingIntent.getActivity(context, requestCode, intent, intentFlag)
|
||||
}
|
||||
|
||||
private fun getIntentFlags(): Int = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
|
||||
companion object {
|
||||
val TAG = NotificationWorker::class.simpleName
|
||||
private const val TYPE_CHAT = "chat"
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.models
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.TypeConverter
|
||||
import com.bluelinelabs.logansquare.LoganSquare
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Parcelize
|
||||
@JsonObject
|
||||
@Serializable
|
||||
data class MessageDraft(
|
||||
@JsonField(name = ["messageText"])
|
||||
var messageText: String = "",
|
||||
@JsonField(name = ["messageCursor"])
|
||||
var messageCursor: Int = 0,
|
||||
@JsonField(name = ["quotedJsonId"])
|
||||
var quotedJsonId: Int? = null,
|
||||
@JsonField(name = ["quotedDisplayName"])
|
||||
var quotedDisplayName: String? = null,
|
||||
@JsonField(name = ["quotedMessageText"])
|
||||
var quotedMessageText: String? = null,
|
||||
@JsonField(name = ["quoteImageUrl"])
|
||||
var quotedImageUrl: String? = null,
|
||||
@JsonField(name = ["threadTitle"])
|
||||
var threadTitle: String? = null
|
||||
) : Parcelable {
|
||||
constructor() : this("", 0, null, null, null, null, null)
|
||||
}
|
||||
|
||||
class MessageDraftConverter {
|
||||
|
||||
@TypeConverter
|
||||
fun fromMessageDraftToString(messageDraft: MessageDraft?): String =
|
||||
if (messageDraft == null) {
|
||||
""
|
||||
} else {
|
||||
LoganSquare.serialize(messageDraft)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromStringToMessageDraft(value: String): MessageDraft? =
|
||||
if (value.isBlank()) {
|
||||
null
|
||||
} else {
|
||||
LoganSquare.parse(value, MessageDraft::class.java)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,13 +8,12 @@
|
|||
package com.nextcloud.talk.models.domain
|
||||
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.models.MessageDraft
|
||||
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
|
||||
|
||||
data class ConversationModel(
|
||||
class ConversationModel(
|
||||
var internalId: String,
|
||||
var accountId: Long,
|
||||
var token: String,
|
||||
|
|
@ -66,8 +65,7 @@ data class ConversationModel(
|
|||
var hasImportant: Boolean = false,
|
||||
|
||||
// attributes that don't come from API. This should be changed?!
|
||||
var password: String? = null,
|
||||
var messageDraft: MessageDraft? = MessageDraft()
|
||||
var password: String? = null
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -19,12 +19,6 @@ import kotlinx.parcelize.Parcelize
|
|||
data class ChatMessageJson(
|
||||
@JsonField(name = ["id"]) var id: Long = 0,
|
||||
@JsonField(name = ["token"]) var token: String? = null,
|
||||
@JsonField(name = ["threadId"]) var threadId: Long? = null,
|
||||
|
||||
// Be aware that variables with "is" at the beginning will lead to the error:
|
||||
// "@JsonField annotation can only be used on private fields if both getter and setter are present."
|
||||
// Instead, name it with "has" at the beginning: isThread -> hasThread
|
||||
@JsonField(name = ["isThread"]) var hasThread: Boolean = false,
|
||||
@JsonField(name = ["actorType"]) var actorType: String? = null,
|
||||
@JsonField(name = ["actorId"]) var actorId: String? = null,
|
||||
@JsonField(name = ["actorDisplayName"]) var actorDisplayName: String? = null,
|
||||
|
|
@ -50,7 +44,5 @@ data class ChatMessageJson(
|
|||
@JsonField(name = ["lastEditTimestamp"]) var lastEditTimestamp: Long? = 0,
|
||||
@JsonField(name = ["deleted"]) var deleted: Boolean = false,
|
||||
@JsonField(name = ["referenceId"]) var referenceId: String? = null,
|
||||
@JsonField(name = ["silent"]) var silent: Boolean = false,
|
||||
@JsonField(name = ["threadTitle"]) var threadTitle: String? = null,
|
||||
@JsonField(name = ["threadReplies"]) var threadReplies: Int? = 0
|
||||
@JsonField(name = ["silent"]) var silent: Boolean = false
|
||||
) : Parcelable
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.RECORDIN
|
|||
import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.USER_ADDED
|
||||
import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.USER_REMOVED
|
||||
import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.PHONE_ADDED
|
||||
import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.THREAD_CREATED
|
||||
|
||||
/*
|
||||
* see https://nextcloud-talk.readthedocs.io/en/latest/chat/#system-messages
|
||||
|
|
@ -144,7 +143,6 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
|
|||
"federated_user_added" -> FEDERATED_USER_ADDED
|
||||
"federated_user_removed" -> FEDERATED_USER_REMOVED
|
||||
"phone_added" -> PHONE_ADDED
|
||||
"thread_created" -> THREAD_CREATED
|
||||
else -> DUMMY
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +212,6 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
|
|||
FEDERATED_USER_ADDED -> "federated_user_added"
|
||||
FEDERATED_USER_REMOVED -> "federated_user_removed"
|
||||
PHONE_ADDED -> "phone_added"
|
||||
THREAD_CREATED -> "thread_created"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,5 @@ enum class StatusType(val string: String) {
|
|||
OFFLINE("offline"),
|
||||
DND("dnd"),
|
||||
AWAY("away"),
|
||||
BUSY("busy"),
|
||||
INVISIBLE("invisible")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.models.json.threads
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@JsonObject
|
||||
data class Thread(
|
||||
@JsonField(name = ["id"])
|
||||
var id: Int = 0,
|
||||
|
||||
@JsonField(name = ["roomToken"])
|
||||
var roomToken: String = "",
|
||||
|
||||
@JsonField(name = ["title"])
|
||||
var title: String = "",
|
||||
|
||||
@JsonField(name = ["lastMessageId"])
|
||||
var lastMessageId: Int = 0,
|
||||
|
||||
@JsonField(name = ["lastActivity"])
|
||||
var lastActivity: Int = 0,
|
||||
|
||||
@JsonField(name = ["numReplies"])
|
||||
var numReplies: Int = 0
|
||||
) : Parcelable
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.models.json.threads
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@JsonObject
|
||||
data class ThreadAttendee(
|
||||
@JsonField(name = ["notificationLevel"])
|
||||
var notificationLevel: Int = 0
|
||||
) : Parcelable
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.models.json.threads
|
||||
|
||||
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 kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@JsonObject
|
||||
data class ThreadInfo(
|
||||
@JsonField(name = ["thread"])
|
||||
var thread: Thread? = null,
|
||||
|
||||
@JsonField(name = ["attendee"])
|
||||
var attendee: ThreadAttendee? = null,
|
||||
|
||||
@JsonField(name = ["first"])
|
||||
var first: ChatMessageJson? = null,
|
||||
|
||||
@JsonField(name = ["last"])
|
||||
var last: ChatMessageJson? = null
|
||||
) : Parcelable
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.models.json.threads
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import com.nextcloud.talk.models.json.generic.GenericMeta
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@JsonObject
|
||||
data class ThreadOCS(
|
||||
@JsonField(name = ["meta"])
|
||||
var meta: GenericMeta?,
|
||||
@JsonField(name = ["data"])
|
||||
var data: ThreadInfo? = null
|
||||
) : Parcelable {
|
||||
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||
constructor() : this(null, null)
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.models.json.threads
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@JsonObject
|
||||
data class ThreadOverall(
|
||||
@JsonField(name = ["ocs"])
|
||||
var ocs: ThreadOCS? = null
|
||||
) : Parcelable {
|
||||
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||
constructor() : this(null)
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.models.json.threads
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import com.nextcloud.talk.models.json.generic.GenericMeta
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@JsonObject
|
||||
data class ThreadsOCS(
|
||||
@JsonField(name = ["meta"])
|
||||
var meta: GenericMeta?,
|
||||
@JsonField(name = ["data"])
|
||||
var data: List<ThreadInfo>? = null
|
||||
) : Parcelable {
|
||||
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||
constructor() : this(null, null)
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.models.json.threads
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
@JsonObject
|
||||
data class ThreadsOverall(
|
||||
@JsonField(name = ["ocs"])
|
||||
var ocs: ThreadsOCS? = null
|
||||
) : Parcelable {
|
||||
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||
constructor() : this(null)
|
||||
}
|
||||
|
|
@ -10,7 +10,6 @@ package com.nextcloud.talk.remotefilebrowser.activities
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
|
|
@ -19,8 +18,6 @@ import android.view.View
|
|||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
|
|
@ -86,7 +83,6 @@ class RemoteFileBrowserActivity :
|
|||
viewThemeUtils.material.colorMaterialTextButton(binding.pathNavigationBackButton)
|
||||
viewThemeUtils.platform.themeStatusBar(this)
|
||||
setContentView(binding.root)
|
||||
initSystemBars()
|
||||
|
||||
DisplayUtils.applyColorToNavigationBar(
|
||||
this.window,
|
||||
|
|
@ -246,21 +242,6 @@ class RemoteFileBrowserActivity :
|
|||
binding.recyclerView.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
fun initSystemBars() {
|
||||
val decorView = window.decorView
|
||||
decorView.setOnApplyWindowInsetsListener { view, insets ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
|
||||
val systemBars = insets.getInsets(
|
||||
WindowInsetsCompat.Type.systemBars() or
|
||||
WindowInsetsCompat.Type.displayCutout()
|
||||
)
|
||||
view.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
||||
}
|
||||
insets
|
||||
}
|
||||
ViewCompat.requestApplyInsets(decorView)
|
||||
}
|
||||
|
||||
override fun onRefresh() {
|
||||
refreshCurrentPath()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,10 +101,7 @@ class ReactionsRepositoryImpl @Inject constructor(
|
|||
val internalConversationId = "$accountId@$roomToken"
|
||||
val emoji = model.emoji
|
||||
|
||||
val message = dao.getChatMessageForConversation(
|
||||
internalConversationId,
|
||||
id
|
||||
).first()
|
||||
val message = dao.getChatMessageForConversation(internalConversationId, id).first()
|
||||
|
||||
// 2. Check state of entity, create params as needed
|
||||
if (message.reactions == null) {
|
||||
|
|
|
|||
|
|
@ -1410,6 +1410,10 @@ class SettingsActivity :
|
|||
json.toRequestBody("application/json".toMediaTypeOrNull())
|
||||
)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
loadCapabilitiesAndUpdateSettings()
|
||||
Log.i(TAG, "typing status set")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
withContext(Dispatchers.Main) {
|
||||
appPreferences.typingStatus = !newBoolean
|
||||
|
|
|
|||
|
|
@ -1,257 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.threadsoverview
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.text.format.DateUtils
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import autodagger.AutoInjector
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.chat.ChatActivity
|
||||
import com.nextcloud.talk.chat.ChatActivity.Companion.TAG
|
||||
import com.nextcloud.talk.components.ColoredStatusBar
|
||||
import com.nextcloud.talk.components.StandardAppBar
|
||||
import com.nextcloud.talk.data.database.mappers.asModel
|
||||
import com.nextcloud.talk.models.json.threads.ThreadInfo
|
||||
import com.nextcloud.talk.threadsoverview.components.ThreadRow
|
||||
import com.nextcloud.talk.threadsoverview.viewmodels.ThreadsOverviewViewModel
|
||||
import com.nextcloud.talk.users.UserManager
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_THREAD_ID
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class ThreadsOverviewActivity : BaseActivity() {
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||
|
||||
@Inject
|
||||
lateinit var ncApi: NcApi
|
||||
|
||||
@Inject
|
||||
lateinit var userManager: UserManager
|
||||
|
||||
lateinit var threadsOverviewViewModel: ThreadsOverviewViewModel
|
||||
|
||||
var threadsSourceUrl: String = ""
|
||||
var appbarTitle: String = ""
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
threadsOverviewViewModel = ViewModelProvider(
|
||||
this,
|
||||
viewModelFactory
|
||||
)[ThreadsOverviewViewModel::class.java]
|
||||
|
||||
val colorScheme = viewThemeUtils.getColorScheme(this)
|
||||
|
||||
val extras: Bundle? = intent.extras
|
||||
threadsSourceUrl = extras?.getString(KEY_THREADS_SOURCE_URL).orEmpty()
|
||||
appbarTitle = extras?.getString(KEY_APPBAR_TITLE).orEmpty()
|
||||
|
||||
setContent {
|
||||
val backgroundColor = colorResource(id = R.color.bg_default)
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme
|
||||
) {
|
||||
ColoredStatusBar()
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.statusBarsPadding(),
|
||||
topBar = {
|
||||
StandardAppBar(
|
||||
title = appbarTitle,
|
||||
null
|
||||
)
|
||||
},
|
||||
content = { paddingValues ->
|
||||
val uiState by threadsOverviewViewModel.threadsListState.collectAsState()
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.padding(0.dp, paddingValues.calculateTopPadding(), 0.dp, 0.dp)
|
||||
.background(backgroundColor)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
ThreadsOverviewScreen(
|
||||
uiState,
|
||||
onThreadClick = { roomToken, threadId ->
|
||||
navigateToChatActivity(roomToken, threadId)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToChatActivity(roomToken: String, threadId: Int) {
|
||||
val bundle = Bundle()
|
||||
bundle.putString(KEY_ROOM_TOKEN, roomToken)
|
||||
bundle.putLong(KEY_THREAD_ID, threadId.toLong())
|
||||
val chatIntent = Intent(context, ChatActivity::class.java)
|
||||
chatIntent.putExtras(bundle)
|
||||
startActivity(chatIntent)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
supportActionBar?.show()
|
||||
threadsOverviewViewModel.init(threadsSourceUrl)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = ThreadsOverviewActivity::class.java.simpleName
|
||||
val KEY_APPBAR_TITLE = "KEY_APPBAR_TITLE"
|
||||
val KEY_THREADS_SOURCE_URL = "KEY_THREADS_SOURCE_URL"
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ThreadsOverviewScreen(
|
||||
uiState: ThreadsOverviewViewModel.ThreadsListUiState,
|
||||
onThreadClick: (roomToken: String, threadId: Int) -> Unit
|
||||
) {
|
||||
when (val state = uiState) {
|
||||
is ThreadsOverviewViewModel.ThreadsListUiState.None -> {
|
||||
LoadingIndicator()
|
||||
}
|
||||
|
||||
is ThreadsOverviewViewModel.ThreadsListUiState.Success -> {
|
||||
ThreadsList(
|
||||
threads = state.threadsList!!,
|
||||
onThreadClick = onThreadClick
|
||||
)
|
||||
}
|
||||
|
||||
is ThreadsOverviewViewModel.ThreadsListUiState.Error -> {
|
||||
Log.e(TAG, "Error when retrieving threads", uiState.exception)
|
||||
ErrorView(message = stringResource(R.string.nc_common_error_sorry))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ThreadsList(threads: List<ThreadInfo>, onThreadClick: (roomToken: String, threadId: Int) -> Unit) {
|
||||
val space = ' '
|
||||
if (threads.isEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(stringResource(R.string.threads_list_empty))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
items(
|
||||
items = threads,
|
||||
key = { threadInfo -> threadInfo.thread!!.id }
|
||||
) { threadInfo ->
|
||||
val messageJson = threadInfo.last ?: threadInfo.first
|
||||
val messageModel = messageJson?.asModel()
|
||||
|
||||
ThreadRow(
|
||||
roomToken = threadInfo.thread!!.roomToken,
|
||||
threadId = threadInfo.thread!!.id,
|
||||
title = threadInfo.thread?.title.orEmpty(),
|
||||
numReplies = pluralStringResource(
|
||||
R.plurals.thread_replies,
|
||||
threadInfo.thread?.numReplies ?: 0,
|
||||
threadInfo.thread?.numReplies ?: 0
|
||||
),
|
||||
secondLineTitle = messageModel?.actorDisplayName?.substringBefore(space)?.let { "$it:" }.orEmpty(),
|
||||
secondLine = messageModel?.text.orEmpty(),
|
||||
date = getLastActivityDate(threadInfo), // replace with value from api when available
|
||||
onClick = onThreadClick
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(32.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun getLastActivityDate(threadInfo: ThreadInfo): String {
|
||||
val oneSecond = 1000L
|
||||
|
||||
val lastActivityTimestamp = threadInfo.thread?.lastActivity ?: 0
|
||||
|
||||
val lastActivityDate = DateUtils.getRelativeTimeSpanString(
|
||||
lastActivityTimestamp.times(oneSecond),
|
||||
System.currentTimeMillis(),
|
||||
0,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE
|
||||
).toString()
|
||||
return lastActivityDate
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LoadingIndicator() {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ErrorView(message: String) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(text = message, color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,213 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.threadsoverview.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.utils.ColorGenerator
|
||||
|
||||
@Suppress("LongParameterList", "Detekt.LongMethod")
|
||||
@Composable
|
||||
fun ThreadRow(
|
||||
roomToken: String,
|
||||
threadId: Int,
|
||||
title: String,
|
||||
secondLineTitle: String,
|
||||
secondLine: String,
|
||||
numReplies: String,
|
||||
date: String,
|
||||
onClick: ((String, Int) -> Unit?)?
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.Companion
|
||||
.fillMaxWidth()
|
||||
.clickable(enabled = onClick != null) {
|
||||
onClick?.invoke(roomToken, threadId)
|
||||
}
|
||||
.padding(vertical = 8.dp, horizontal = 8.dp),
|
||||
verticalAlignment = Alignment.Companion.CenterVertically
|
||||
) {
|
||||
ThreadsIcon(title)
|
||||
|
||||
Spacer(modifier = Modifier.Companion.width(12.dp))
|
||||
|
||||
Column {
|
||||
Row {
|
||||
Text(
|
||||
modifier = Modifier.Companion.weight(1f),
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontWeight = FontWeight.Medium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Companion.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.Companion.width(4.dp))
|
||||
Text(
|
||||
text = numReplies,
|
||||
style = MaterialTheme.typography.titleSmall
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.Companion.height(2.dp))
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.Companion.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = secondLineTitle,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Normal,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Companion.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.Companion.width(4.dp))
|
||||
Text(
|
||||
modifier = Modifier.Companion.weight(1f),
|
||||
text = secondLine,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Normal,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Companion.Ellipsis
|
||||
)
|
||||
Spacer(modifier = Modifier.Companion.width(4.dp))
|
||||
Text(
|
||||
text = date,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.Companion.width(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ThreadsIcon(title: String) {
|
||||
val baseColorInt = ColorGenerator.usernameToColor(title)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color(baseColorInt).copy(alpha = 0.1f)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.outline_forum_24),
|
||||
contentDescription = null,
|
||||
tint = Color(baseColorInt).copy(alpha = 0.9f),
|
||||
modifier = Modifier.size(32.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ThreadRowPreview() {
|
||||
ThreadRow(
|
||||
roomToken = "1234",
|
||||
threadId = 123,
|
||||
title = "title1",
|
||||
secondLine = "last message",
|
||||
secondLineTitle = "Mia:",
|
||||
numReplies = "12 replies",
|
||||
date = "14 sec ago",
|
||||
onClick = null
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(
|
||||
name = "Dark Mode",
|
||||
uiMode = Configuration.UI_MODE_NIGHT_YES
|
||||
)
|
||||
@Composable
|
||||
fun ThreadRowPreviewDark() {
|
||||
ThreadRow(
|
||||
roomToken = "1234",
|
||||
threadId = 123,
|
||||
title = "title2",
|
||||
secondLine = "last message",
|
||||
secondLineTitle = "Mia:",
|
||||
numReplies = "12 replies",
|
||||
date = "14 sec ago",
|
||||
onClick = null
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ThreadRowUnreadMessagePreview() {
|
||||
ThreadRow(
|
||||
roomToken = "1234",
|
||||
threadId = 123,
|
||||
title = "title3",
|
||||
secondLine = "last message",
|
||||
secondLineTitle = "Mia:",
|
||||
numReplies = "12 replies",
|
||||
date = "14 sec ago",
|
||||
onClick = null
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ThreadRowMentionPreview() {
|
||||
ThreadRow(
|
||||
roomToken = "1234",
|
||||
threadId = 123,
|
||||
title = "title3",
|
||||
secondLine = "last message",
|
||||
secondLineTitle = "Mia:",
|
||||
numReplies = "12 replies",
|
||||
date = "14 sec ago",
|
||||
onClick = null
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ThreadRowDirectMentionPreview() {
|
||||
ThreadRow(
|
||||
roomToken = "1234",
|
||||
threadId = 123,
|
||||
title = "title with a verrrrrrrrrrrrrrrrrrrrrrrrry long text",
|
||||
secondLine = "title with a verrrrrrrrrrrrrrrrrrrrrrrrry long text",
|
||||
secondLineTitle = "Mia:",
|
||||
numReplies = "12 replies",
|
||||
date = "14 sec ago",
|
||||
onClick = null
|
||||
)
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.threadsoverview.data
|
||||
|
||||
import com.nextcloud.talk.models.json.threads.ThreadOverall
|
||||
import com.nextcloud.talk.models.json.threads.ThreadsOverall
|
||||
|
||||
interface ThreadsRepository {
|
||||
|
||||
suspend fun getThreads(credentials: String, url: String, limit: Int?): ThreadsOverall
|
||||
|
||||
suspend fun getThread(credentials: String, url: String): ThreadOverall
|
||||
|
||||
suspend fun setThreadNotificationLevel(credentials: String, url: String, level: Int): ThreadOverall
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue