Compare commits

..

No commits in common. "master" and "alpha-220000002" have entirely different histories.

131 changed files with 645 additions and 5977 deletions

View File

@ -1,4 +1,4 @@
FROM ubuntu:noble@sha256:b59d21599a2b151e23eea5f6602f4af4d7d31c4e236d22bf0b62b86d2e386b8f
FROM ubuntu:noble@sha256:6015f66923d7afbc53558d7ccffd325d43b4e249f41a6e93eef074c9505d2233
ARG DEBIAN_FRONTEND=noninteractive
ENV ANDROID_HOME=/usr/lib/android-sdk

View File

@ -8,7 +8,7 @@ name: generic
steps:
- name: generic
image: ghcr.io/nextcloud/continuous-integration-android8:4
image: ghcr.io/nextcloud/continuous-integration-android8:3
commands:
- ./gradlew --console=plain assembleGeneric
@ -27,7 +27,7 @@ name: gplay
steps:
- name: gplay
image: ghcr.io/nextcloud/continuous-integration-android8:4
image: ghcr.io/nextcloud/continuous-integration-android8:3
commands:
- ./gradlew --console=plain assembleGplay
@ -46,7 +46,7 @@ name: tests
steps:
- name: all
image: ghcr.io/nextcloud/continuous-integration-android8:4
image: ghcr.io/nextcloud/continuous-integration-android8:3
privileged: true
commands:
- emulator -avd android -no-snapshot -gpu swiftshader_indirect -no-window -no-audio -skin 500x833 &
@ -81,6 +81,4 @@ trigger:
- pull_request
---
kind: signature
hmac: cf0c19e54fa45d1ee226f5f05202a32329b90aaf46711ea073c566a4c4a8a6c5
...
hmac: cdce3f7eea46ef85c0223f62f66d1fe53d7dad007ef095c9f70fa063450d8c75

View File

@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Disabled on forks
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }}
if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
run: |
echo 'Can not analyze PRs from forks'
exit 1

View File

@ -34,7 +34,7 @@ jobs:
java-version: 17
- name: Gradle validate
uses: gradle/actions/wrapper-validation@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
- name: Build ${{ matrix.flavor }}
run: |

View File

@ -43,7 +43,7 @@ jobs:
with:
swap-size-gb: 10
- name: Initialize CodeQL
uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
with:
languages: ${{ matrix.language }}
- name: Set up JDK 17
@ -57,4 +57,4 @@ jobs:
echo "org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > "$HOME/.gradle/gradle.properties"
./gradlew assembleDebug
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18

View File

@ -36,7 +36,7 @@ jobs:
blocklist=$(curl https://raw.githubusercontent.com/nextcloud/.github/master/non-community-usernames.txt | paste -s -d, -)
echo "blocklist=$blocklist" >> "$GITHUB_OUTPUT"
- uses: nextcloud/pr-feedback-action@f0cab224dea8e1f282f9451de322f323c78fc7a5 # main
- uses: nextcloud/pr-feedback-action@1883b38a033fb16f576875e0cf45f98b857655c4 # main
with:
feedback-message: |
Hello there,

View File

@ -34,7 +34,7 @@ jobs:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
with:
results_file: results.sarif
results_format: sarif
@ -42,6 +42,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
with:
sarif_file: results.sarif

View File

@ -33,7 +33,7 @@ jobs:
java-version: 17
- name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
- name: Run unit tests with coverage
run: ./gradlew testGplayDebugUnit

View File

@ -9,36 +9,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Types of changes can be: Added/Changed/Deprecated/Removed/Fixed/Security
## [21.1.0] - 2025-06-05
### Added
- Allow adding participants to one-to-one chats creating a new conversation
- Handling of event conversations
- Show info about participant (organization, role, timezone, ...) in 1:1 conversation info screen
- Added gallery option in chat attachment menu (access photos and videos with one click without giving permissions)
- Add self-test button for push notifications in diagnosis screen
- Edit checkbox in chat messages
- Team mentions in chat
- Add option to mark a conversation as sensitive
- Allow bluetooth headset to be discovered and used during a call (@gavine99)
### Changed
- Design of participants grid in call
- Improve subline in conversations screen when last message is attachment
- Improve message search
- In search window, show messages at last
- Switch video capture for calls between 4:3 and 16:9 ratio depending on portrait/ landscape mode
### Fixed
- Crashes
- Videos in videocall lost after app comes back to foreground
- Open conversations not being shown in search
- Minor bugs (@MmAaXx500)
Minimum: NC 17 Server, Android 8.0 Oreo
For a full list, please see https://github.com/nextcloud/talk-android/milestone/94?closed=1
## [21.0.1] - 2025-04-15
### Fixed

View File

@ -15,7 +15,7 @@ import com.github.spotbugs.snom.SpotBugsTask
plugins {
id "org.jetbrains.kotlin.plugin.compose" version "2.1.21"
id "org.jetbrains.kotlin.kapt"
id 'com.google.devtools.ksp' version '2.1.21-2.0.2'
id 'com.google.devtools.ksp' version '2.1.21-2.0.1'
}
apply plugin: 'com.android.application'
@ -39,8 +39,8 @@ android {
// mayor.minor.hotfix.increment (for increment: 01-50=Alpha / 51-89=RC / 90-99=stable)
// xx .xxx .xx .xx
versionCode 220000006
versionName "22.0.0 Alpha 06"
versionCode 220000002
versionName "22.0.0 Alpha 02"
flavorDimensions "default"
renderscriptTargetApi 19
@ -161,7 +161,7 @@ ext {
materialDialogsVersion = "3.3.0"
parcelerVersion = "1.1.13"
prismVersion = "2.0.0"
retrofit2Version = "2.12.0"
retrofit2Version = "2.11.0"
roomVersion = "2.7.1"
workVersion = "2.9.1"
espressoVersion = "3.6.1"
@ -180,20 +180,20 @@ configurations.configureEach {
dependencies {
spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0'
spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.10'
spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.9'
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.8")
implementation("androidx.compose.runtime:runtime:1.7.8")
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.datastore:datastore-core:1.1.6'
implementation 'androidx.datastore:datastore-preferences:1.1.6'
implementation 'androidx.test.ext:junit-ktx:1.2.1'
implementation fileTree(include: ['*'], dir: 'libs')
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1"
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'androidx.appcompat:appcompat:1.7.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"
@ -291,7 +291,6 @@ dependencies {
implementation "io.noties.markwon:core:$markwonVersion"
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
implementation "io.noties.markwon:ext-tasklist:$markwonVersion"
implementation "io.noties.markwon:ext-tables:$markwonVersion"
implementation 'com.github.nextcloud-deps:ImagePicker:2.1.0.2'
implementation 'io.github.elye:loaderviewlibrary:3.0.0'
@ -322,14 +321,14 @@ dependencies {
debugImplementation("androidx.compose.ui:ui-test-manifest")
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:5.18.0'
testImplementation 'org.mockito:mockito-core:5.17.0'
testImplementation 'androidx.arch.core:core-testing:2.2.0'
androidTestImplementation "androidx.test:core:1.6.1"
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2"
androidTestImplementation 'androidx.test:core-ktx:1.6.1'
androidTestImplementation 'org.mockito:mockito-android:5.18.0'
androidTestImplementation 'org.mockito:mockito-android:5.17.0'
androidTestImplementation "androidx.work:work-testing:${workVersion}"
// Espresso core
androidTestImplementation ("androidx.test.espresso:espresso-core:$espressoVersion", {
@ -347,7 +346,7 @@ dependencies {
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.1'
testImplementation 'org.junit.vintage:junit-vintage-engine:5.12.2'
}
tasks.register('installGitHooks', Copy) {

View File

@ -1,725 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 15,
"identityHash": "acac3fd21e35762b90f65f213be38ccd",
"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, 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
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"internalId"
]
},
"indices": [
{
"name": "index_Conversations_accountId",
"unique": false,
"columnNames": [
"accountId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_Conversations_accountId` ON `${TABLE_NAME}` (`accountId`)"
}
],
"foreignKeys": [
{
"table": "User",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"accountId"
],
"referencedColumns": [
"id"
]
}
]
},
{
"tableName": "ChatMessages",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `id` INTEGER NOT NULL, `internalConversationId` TEXT NOT NULL, `actorDisplayName` TEXT NOT NULL, `message` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `expirationTimestamp` INTEGER NOT NULL, `isReplyable` INTEGER NOT NULL, `isTemporary` INTEGER NOT NULL, `lastEditActorDisplayName` TEXT, `lastEditActorId` TEXT, `lastEditActorType` TEXT, `lastEditTimestamp` INTEGER, `markdown` INTEGER, `messageParameters` TEXT, `messageType` TEXT NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `referenceId` TEXT, `sendingFailed` INTEGER NOT NULL, `silent` INTEGER NOT NULL, `systemMessage` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "internalId",
"columnName": "internalId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "accountId",
"columnName": "accountId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "token",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "internalConversationId",
"columnName": "internalConversationId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "actorDisplayName",
"columnName": "actorDisplayName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "message",
"columnName": "message",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "actorId",
"columnName": "actorId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "actorType",
"columnName": "actorType",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "deleted",
"columnName": "deleted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "expirationTimestamp",
"columnName": "expirationTimestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "replyable",
"columnName": "isReplyable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "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": "sendingFailed",
"columnName": "sendingFailed",
"affinity": "INTEGER",
"notNull": true
},
{
"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, `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": "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, 'acac3fd21e35762b90f65f213be38ccd')"
]
}
}

View File

@ -1,731 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 16,
"identityHash": "bbf526d5c78a99eb951635cc46f4c59f",
"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, `actorDisplayName` TEXT NOT NULL, `message` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `expirationTimestamp` INTEGER NOT NULL, `isReplyable` INTEGER NOT NULL, `isTemporary` INTEGER NOT NULL, `lastEditActorDisplayName` TEXT, `lastEditActorId` TEXT, `lastEditActorType` TEXT, `lastEditTimestamp` INTEGER, `markdown` INTEGER, `messageParameters` TEXT, `messageType` TEXT NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `referenceId` TEXT, `sendingFailed` INTEGER NOT NULL, `silent` INTEGER NOT NULL, `systemMessage` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "internalId",
"columnName": "internalId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "accountId",
"columnName": "accountId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "token",
"columnName": "token",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "internalConversationId",
"columnName": "internalConversationId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "actorDisplayName",
"columnName": "actorDisplayName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "message",
"columnName": "message",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "actorId",
"columnName": "actorId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "actorType",
"columnName": "actorType",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "deleted",
"columnName": "deleted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "expirationTimestamp",
"columnName": "expirationTimestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "replyable",
"columnName": "isReplyable",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "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": "sendingFailed",
"columnName": "sendingFailed",
"affinity": "INTEGER",
"notNull": true
},
{
"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, `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": "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, 'bbf526d5c78a99eb951635cc46f4c59f')"
]
}
}

View File

@ -1,730 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 17,
"identityHash": "5bc4247e179307faa995552da5d34324",
"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, `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": "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, `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": "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, '5bc4247e179307faa995552da5d34324')"
]
}
}

View File

@ -286,7 +286,6 @@ class WebViewLoginActivity : BaseActivity() {
}
}
@SuppressLint("DiscouragedPrivateApi")
@Suppress("Detekt.TooGenericExceptionCaught")
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
try {

View File

@ -376,8 +376,6 @@ class CallActivity : CallBaseActivity() {
Log.d(TAG, "onCreate")
super.onCreate(savedInstanceState)
sharedApplication!!.componentApplication.inject(this)
rootEglBase = EglBase.create()
binding = CallActivityBinding.inflate(layoutInflater)
setContentView(binding!!.root)
hideNavigationIfNoPipAvailable()
@ -767,6 +765,7 @@ class CallActivity : CallBaseActivity() {
}
private fun basicInitialization() {
rootEglBase = EglBase.create()
createCameraEnumerator()
// Create a new PeerConnectionFactory instance.
@ -948,7 +947,8 @@ class CallActivity : CallBaseActivity() {
ParticipantGrid(
participantUiStates = participantUiStates,
eglBase = rootEglBase!!,
isVoiceOnlyCall = isVoiceOnlyCall
isVoiceOnlyCall = isVoiceOnlyCall,
isInPipMode = isInPipMode
) {
animateCallControls(true, 0)
}

View File

@ -19,7 +19,6 @@ import android.text.TextUtils
import android.text.format.DateUtils
import android.text.style.ImageSpan
import android.view.View
import android.widget.RelativeLayout
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import com.nextcloud.talk.R
@ -156,30 +155,6 @@ class ConversationItem(
} else {
holder.binding.userStatusImage.visibility = View.GONE
}
val dialogNameParams = holder.binding.dialogName.layoutParams as RelativeLayout.LayoutParams
val unreadBubbleParams = holder.binding.dialogUnreadBubble.layoutParams as RelativeLayout.LayoutParams
val relativeLayoutParams = holder.binding.relativeLayout.layoutParams as RelativeLayout.LayoutParams
if (model.hasSensitive == true) {
dialogNameParams.addRule(RelativeLayout.CENTER_VERTICAL)
relativeLayoutParams.addRule(RelativeLayout.ALIGN_TOP, R.id.dialogAvatarFrameLayout)
dialogNameParams.marginEnd =
context.resources.getDimensionPixelSize(R.dimen.standard_double_padding)
unreadBubbleParams.topMargin =
context.resources.getDimensionPixelSize(R.dimen.double_margin_between_elements)
unreadBubbleParams.addRule(RelativeLayout.CENTER_VERTICAL)
} else {
dialogNameParams.removeRule(RelativeLayout.CENTER_VERTICAL)
relativeLayoutParams.removeRule(RelativeLayout.ALIGN_TOP)
dialogNameParams.marginEnd = 0
unreadBubbleParams.topMargin = 0
unreadBubbleParams.removeRule(RelativeLayout.CENTER_VERTICAL)
}
holder.binding.relativeLayout.layoutParams = relativeLayoutParams
holder.binding.dialogUnreadBubble.layoutParams = unreadBubbleParams
holder.binding.dialogName.layoutParams = dialogNameParams
setLastMessage(holder, appContext)
showAvatar(holder)
}
@ -431,9 +406,9 @@ class ConversationItem(
)
return lastMessage
} else if (MessageType.SINGLE_NC_ATTACHMENT_MESSAGE == chatMessage?.getCalculateMessageType()) {
var attachmentName = chatMessage.text
var attachmentName = chatMessage.message
if (attachmentName == "{file}") {
attachmentName = chatMessage.messageParameters?.get("file")?.get("name") ?: ""
attachmentName = chatMessage.messageParameters?.get("file")?.get("name")
}
val author = authorName(chatMessage)

View File

@ -18,9 +18,7 @@ import androidx.core.content.ContextCompat
import androidx.core.text.toSpanned
import autodagger.AutoInjector
import coil.load
import com.google.android.flexbox.FlexboxLayout
import com.google.android.material.snackbar.Snackbar
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
@ -105,33 +103,10 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
true,
viewThemeUtils
)
val spansFromString: Array<Any> = processedMessageText!!.getSpans(
0,
processedMessageText.length,
Any::class.java
)
if (spansFromString.isNotEmpty()) {
binding.bubble.layoutParams.apply {
width = FlexboxLayout.LayoutParams.MATCH_PARENT
}
binding.messageText.layoutParams.apply {
width = FlexboxLayout.LayoutParams.MATCH_PARENT
}
} else {
binding.bubble.layoutParams.apply {
width = FlexboxLayout.LayoutParams.WRAP_CONTENT
}
binding.messageText.layoutParams.apply {
width = FlexboxLayout.LayoutParams.WRAP_CONTENT
}
}
processedMessageText = messageUtils.processMessageParameters(
binding.messageText.context,
viewThemeUtils,
processedMessageText,
processedMessageText!!,
message,
itemView
)
@ -158,7 +133,7 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
binding.messageEditIndicator.visibility = View.GONE
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
}
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
binding.messageTime.setTextColor(ContextCompat.getColor(context, R.color.no_emphasis_text))
// parent message handling
if (!message.isDeleted && message.parentMessageId != null) {
processParentMessage(message)

View File

@ -29,7 +29,6 @@ import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedA
import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.chat.data.ChatMessageRepository
import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.data.database.model.SendStatus
import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ItemCustomOutcomingTextMessageBinding
@ -106,6 +105,7 @@ class OutcomingTextMessageViewHolder(itemView: View) :
if (!hasCheckboxes) {
realView.isSelected = false
layoutParams.isWrapBefore = false
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
binding.messageText.visibility = View.VISIBLE
binding.checkboxContainer.visibility = View.GONE
@ -116,33 +116,10 @@ class OutcomingTextMessageViewHolder(itemView: View) :
false,
viewThemeUtils
)
val spansFromString: Array<Any> = processedMessageText!!.getSpans(
0,
processedMessageText.length,
Any::class.java
)
if (spansFromString.isNotEmpty()) {
binding.bubble.layoutParams.apply {
width = FlexboxLayout.LayoutParams.MATCH_PARENT
}
binding.messageText.layoutParams.apply {
width = FlexboxLayout.LayoutParams.MATCH_PARENT
}
} else {
binding.bubble.layoutParams.apply {
width = FlexboxLayout.LayoutParams.WRAP_CONTENT
}
binding.messageText.layoutParams.apply {
width = FlexboxLayout.LayoutParams.WRAP_CONTENT
}
}
processedMessageText = messageUtils.processMessageParameters(
binding.messageText.context,
viewThemeUtils,
processedMessageText,
processedMessageText!!,
message,
itemView
)
@ -172,7 +149,7 @@ class OutcomingTextMessageViewHolder(itemView: View) :
binding.messageEditIndicator.visibility = View.GONE
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
}
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
binding.messageTime.setTextColor(ContextCompat.getColor(context, R.color.no_emphasis_text))
setBubbleOnChatMessage(message)
// parent message handling
if (!message.isDeleted && message.parentMessageId != null) {
@ -185,7 +162,7 @@ class OutcomingTextMessageViewHolder(itemView: View) :
binding.checkMark.visibility = View.INVISIBLE
binding.sendingProgress.visibility = View.GONE
if (message.sendStatus == SendStatus.FAILED) {
if (message.sendingFailed) {
updateStatus(R.drawable.baseline_error_outline_24, context.resources?.getString(R.string.nc_message_failed))
} else if (message.isTemporary) {
updateStatus(R.drawable.baseline_schedule_24, context.resources?.getString(R.string.nc_message_sending))

View File

@ -17,7 +17,6 @@ import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.AddParticipantOverall
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.userAbsence.UserAbsenceOverall
import okhttp3.MultipartBody
@ -179,36 +178,12 @@ interface NcApiCoroutines {
@Url url: String
): GenericOverall
@POST
suspend fun markConversationAsImportant(
@Header("Authorization") authorization: String,
@Url url: String
): GenericOverall
@DELETE
suspend fun markConversationAsUnimportant(
@Header("Authorization") authorization: String,
@Url url: String
): GenericOverall
@DELETE
suspend fun removeConversationFromFavorites(
@Header("Authorization") authorization: String,
@Url url: String
): GenericOverall
@POST
suspend fun markConversationAsSensitive(
@Header("Authorization") authorization: String,
@Url url: String
): GenericOverall
@DELETE
suspend fun markConversationAsInsensitive(
@Header("Authorization") authorization: String,
@Url url: String
): GenericOverall
@FormUrlEncoded
@POST
suspend fun notificationCalls(
@ -279,10 +254,4 @@ interface NcApiCoroutines {
@GET
suspend fun getNoteToSelfRoom(@Header("Authorization") authorization: String, @Url url: String): RoomOverall
@GET
suspend fun getProfile(@Header("Authorization") authorization: String, @Url url: String): ProfileOverall
@DELETE
suspend fun unbindRoom(@Header("Authorization") authorization: String, @Url url: String): GenericOverall
}

View File

@ -33,6 +33,7 @@ import coil.decode.SvgDecoder
import coil.memory.MemoryCache
import coil.util.DebugLogger
import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.filebrowser.webdav.DavUtils
import com.nextcloud.talk.dagger.modules.BusModule
import com.nextcloud.talk.dagger.modules.ContextModule
import com.nextcloud.talk.dagger.modules.DaosModule
@ -42,7 +43,6 @@ import com.nextcloud.talk.dagger.modules.RepositoryModule
import com.nextcloud.talk.dagger.modules.RestModule
import com.nextcloud.talk.dagger.modules.UtilsModule
import com.nextcloud.talk.dagger.modules.ViewModelModule
import com.nextcloud.talk.filebrowser.webdav.DavUtils
import com.nextcloud.talk.jobs.AccountRemovalWorker
import com.nextcloud.talk.jobs.CapabilitiesWorker
import com.nextcloud.talk.jobs.SignalingSettingsWorker

View File

@ -6,7 +6,6 @@
*/
package com.nextcloud.talk.bottomsheet.items
import android.annotation.SuppressLint
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
@ -66,7 +65,6 @@ internal class ListIconDialogAdapter<IT : ListItemWithImage>(
}
}
@SuppressLint("RestrictedApi")
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListItemViewHolder {
val listItemView: View = parent.inflate(dialog.windowContext, R.layout.menu_item_sheet)
val viewHolder = ListItemViewHolder(

View File

@ -12,11 +12,11 @@ package com.nextcloud.talk.call.components
import android.content.res.Configuration
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.PaddingValues
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.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
@ -36,6 +36,7 @@ fun ParticipantGrid(
eglBase: EglBase?,
participantUiStates: List<ParticipantUiState>,
isVoiceOnlyCall: Boolean,
isInPipMode: Boolean,
onClick: () -> Unit
) {
val configuration = LocalConfiguration.current
@ -43,59 +44,63 @@ fun ParticipantGrid(
val minItemHeight = 100.dp
if (participantUiStates.isEmpty()) return
val columns = if (isPortrait) {
when (participantUiStates.size) {
1, 2, 3 -> 1
else -> 2
val columns =
if (isPortrait) {
when (participantUiStates.size) {
1, 2, 3 -> 1
else -> 2
}
} else {
when (participantUiStates.size) {
1 -> 1
2, 4 -> 2
else -> 3
}
}
val rows = ceil(participantUiStates.size / columns.toFloat()).toInt()
val heightForNonGridComponents = if (isVoiceOnlyCall && !isInPipMode) {
// this is a workaround for now. It should ~summarize the height of callInfosLinearLayout and callControls
// Once everything is migrated to jetpack, this workaround should be obsolete or solved in a better way
240.dp
} else {
when (participantUiStates.size) {
1 -> 1
2, 4 -> 2
else -> 3
}
}.coerceAtLeast(1) // Prevent 0
val rows = ceil(participantUiStates.size / columns.toFloat()).toInt().coerceAtLeast(1)
0.dp
}
val gridHeight = LocalConfiguration.current.screenHeightDp.dp - heightForNonGridComponents
val itemSpacing = 8.dp
val edgePadding = 8.dp
val totalVerticalSpacing = itemSpacing * (rows - 1)
val totalVerticalPadding = edgePadding * 2
val availableHeight = gridHeight - totalVerticalSpacing - totalVerticalPadding
BoxWithConstraints(
modifier = modifier
val rawItemHeight = availableHeight / rows
val itemHeight = maxOf(rawItemHeight, minItemHeight)
LazyVerticalGrid(
columns = GridCells.Fixed(columns),
modifier = Modifier
.fillMaxSize()
.clickable { onClick() }
.padding(horizontal = edgePadding)
.clickable { onClick() },
verticalArrangement = Arrangement.spacedBy(itemSpacing),
horizontalArrangement = Arrangement.spacedBy(itemSpacing),
contentPadding = PaddingValues(vertical = edgePadding)
) {
val availableHeight = maxHeight
val gridAvailableHeight = availableHeight - totalVerticalSpacing - totalVerticalPadding
val rawItemHeight = gridAvailableHeight / rows
val itemHeight = maxOf(rawItemHeight, minItemHeight)
LazyVerticalGrid(
columns = GridCells.Fixed(columns),
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(itemSpacing),
horizontalArrangement = Arrangement.spacedBy(itemSpacing),
contentPadding = PaddingValues(vertical = edgePadding, horizontal = edgePadding)
) {
items(
participantUiStates,
key = { it.sessionKey }
) { participant ->
ParticipantTile(
participantUiState = participant,
modifier = Modifier
.height(itemHeight)
.fillMaxWidth(),
eglBase = eglBase,
isVoiceOnlyCall = isVoiceOnlyCall
)
}
items(
participantUiStates,
key = { it.sessionKey }
) { participant ->
ParticipantTile(
participantUiState = participant,
modifier = Modifier
.height(itemHeight)
.fillMaxWidth(),
eglBase = eglBase,
isVoiceOnlyCall = isVoiceOnlyCall
)
}
}
}
@ -106,7 +111,8 @@ fun ParticipantGridPreview() {
ParticipantGrid(
participantUiStates = getTestParticipants(1),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -116,7 +122,8 @@ fun TwoParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(2),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -126,7 +133,8 @@ fun ThreeParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(3),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -136,7 +144,8 @@ fun FourParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(4),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -146,7 +155,8 @@ fun FiveParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(5),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -156,7 +166,8 @@ fun SevenParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(7),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -166,7 +177,8 @@ fun FiftyParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(50),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -180,7 +192,8 @@ fun OneParticipantLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(1),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -194,7 +207,8 @@ fun TwoParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(2),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -208,7 +222,8 @@ fun ThreeParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(3),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -222,7 +237,8 @@ fun FourParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(4),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -236,7 +252,8 @@ fun SevenParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(7),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}
@ -250,7 +267,8 @@ fun FiftyParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(50),
eglBase = null,
isVoiceOnlyCall = false
isVoiceOnlyCall = false,
isInPipMode = false
) {}
}

View File

@ -16,6 +16,7 @@ package com.nextcloud.talk.chat
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
@ -52,7 +53,6 @@ import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.view.ContextThemeWrapper
import androidx.cardview.widget.CardView
@ -85,7 +85,6 @@ import coil.request.CachePolicy
import coil.request.ImageRequest
import coil.target.Target
import coil.transform.CircleCropTransformation
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.nextcloud.android.common.ui.color.ColorUtil
@ -125,7 +124,6 @@ import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel
import com.nextcloud.talk.conversationinfo.ConversationInfoActivity
import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
import com.nextcloud.talk.conversationlist.ConversationsListActivity
import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.data.user.model.User
@ -144,7 +142,6 @@ import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
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.polls.ui.PollCreateDialogFragment
import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
@ -156,7 +153,6 @@ import com.nextcloud.talk.ui.PlaybackSpeed
import com.nextcloud.talk.ui.PlaybackSpeedControl
import com.nextcloud.talk.ui.StatusDrawable
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
import com.nextcloud.talk.ui.dialog.ContextChatCompose
import com.nextcloud.talk.ui.dialog.DateTimeCompose
import com.nextcloud.talk.ui.dialog.FileAttachmentPreviewFragment
import com.nextcloud.talk.ui.dialog.MessageActionsDialog
@ -168,10 +164,6 @@ import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.AudioUtils
import com.nextcloud.talk.utils.CapabilitiesUtil
import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
import com.nextcloud.talk.utils.CapabilitiesUtil.retentionOfEventRooms
import com.nextcloud.talk.utils.CapabilitiesUtil.retentionOfInstantMeetingRoom
import com.nextcloud.talk.utils.CapabilitiesUtil.retentionOfSIPRoom
import com.nextcloud.talk.utils.ContactUtils
import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.DateConstants
@ -216,7 +208,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@ -233,8 +224,10 @@ import java.util.Locale
import java.util.concurrent.ExecutionException
import javax.inject.Inject
import kotlin.math.roundToInt
import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia
import com.nextcloud.talk.conversationinfo.viewmodel.ConversationInfoViewModel
import com.nextcloud.talk.models.json.participants.Participant
@Suppress("TooManyFunctions")
@AutoInjector(NextcloudTalkApplication::class)
class ChatActivity :
BaseActivity(),
@ -275,8 +268,6 @@ class ChatActivity :
lateinit var conversationInfoViewModel: ConversationInfoViewModel
lateinit var messageInputViewModel: MessageInputViewModel
private var chatMenu: Menu? = null
private val startSelectContactForResult = registerForActivityResult(
ActivityResultContracts
.StartActivityForResult()
@ -305,34 +296,10 @@ class ChatActivity :
private val startMessageSearchForResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
executeIfResultOk(it) { intent ->
runBlocking {
val id = intent?.getStringExtra(MessageSearchActivity.RESULT_KEY_MESSAGE_ID)
id?.let {
startContextChatWindowForMessage(id)
}
}
onMessageSearchResult(intent)
}
}
private fun startContextChatWindowForMessage(id: String?) {
binding.genericComposeView.apply {
val shouldDismiss = mutableStateOf(false)
setContent {
val bundle = bundleOf()
bundle.putString(BundleKeys.KEY_CREDENTIALS, credentials!!)
bundle.putString(BundleKeys.KEY_BASE_URL, conversationUser!!.baseUrl)
bundle.putString(KEY_ROOM_TOKEN, roomToken)
bundle.putString(BundleKeys.KEY_MESSAGE_ID, id)
bundle.putString(
KEY_CONVERSATION_NAME,
currentConversation!!.displayName
)
ContextChatCompose(bundle).GetDialogView(shouldDismiss, context)
}
}
Log.d(TAG, "Should open something else")
}
private val startPickCameraIntentForResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
@ -362,7 +329,6 @@ class ChatActivity :
var startCallFromRoomSwitch: Boolean = false
var voiceOnly: Boolean = true
var focusInput: Boolean = false
private lateinit var path: String
var myFirstMessage: CharSequence? = null
@ -546,8 +512,6 @@ class ChatActivity :
startCallFromRoomSwitch = extras?.getBoolean(KEY_START_CALL_AFTER_ROOM_SWITCH, false) == true
voiceOnly = extras?.getBoolean(KEY_CALL_VOICE_ONLY, false) == true
focusInput = extras?.getBoolean(BundleKeys.KEY_FOCUS_INPUT) == true
}
override fun onStart() {
@ -639,17 +603,12 @@ class ChatActivity :
supportFragmentManager.commit {
setReorderingAllowed(true) // optimizes out redundant replace operations
replace(R.id.fragment_container_activity_chat, messageInputFragment)
runOnCommit {
if (focusInput) {
messageInputFragment.binding.fragmentMessageInputView.requestFocus()
}
}
}
joinRoomWithPassword()
if (conversationUser?.userId != "?" &&
hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MENTION_FLAG)
CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MENTION_FLAG)
) {
binding.chatToolbar.setOnClickListener { _ -> showConversationInfoScreen() }
}
@ -673,59 +632,6 @@ class ChatActivity :
}
}
if (currentConversation?.objectType == ConversationEnums.ObjectType.EVENT &&
hasSpreedFeatureCapability(
conversationUser?.capabilities!!.spreedCapability!!,
SpreedFeatures.UNBIND_CONVERSATION
)
) {
val eventEndTimeStamp =
currentConversation?.objectId
?.split("#")
?.getOrNull(1)
?.toLongOrNull()
val currentTimeStamp = (System.currentTimeMillis() / 1000).toLong()
val retentionPeriod = retentionOfEventRooms(spreedCapabilities)
val isPastEvent = eventEndTimeStamp?.let { it < currentTimeStamp }
if (isPastEvent == true && retentionPeriod != 0) {
showConversationDeletionWarning(retentionPeriod)
}
}
if (currentConversation?.objectType == ConversationEnums.ObjectType.PHONE_TEMPORARY &&
hasSpreedFeatureCapability(
conversationUser?.capabilities!!.spreedCapability!!,
SpreedFeatures.UNBIND_CONVERSATION
)
) {
val retentionPeriod = retentionOfSIPRoom(spreedCapabilities)
val systemMessage = currentConversation?.lastMessage?.systemMessageType
if (retentionPeriod != 0 && (
systemMessage == ChatMessage.SystemMessageType.CALL_ENDED ||
systemMessage == ChatMessage.SystemMessageType.CALL_ENDED_EVERYONE
)
) {
showConversationDeletionWarning(retentionPeriod)
}
}
if (currentConversation?.objectType == ConversationEnums.ObjectType.INSTANT_MEETING &&
hasSpreedFeatureCapability(
conversationUser?.capabilities!!.spreedCapability!!,
SpreedFeatures.UNBIND_CONVERSATION
)
) {
val retentionPeriod = retentionOfInstantMeetingRoom(spreedCapabilities)
val systemMessage = currentConversation?.lastMessage?.systemMessageType
if (retentionPeriod != 0 && (
systemMessage == ChatMessage.SystemMessageType.CALL_ENDED ||
systemMessage == ChatMessage.SystemMessageType.CALL_ENDED_EVERYONE
)
) {
showConversationDeletionWarning(retentionPeriod)
}
}
updateRoomTimerHandler(MILLIS_250)
val urlForChatting =
@ -1052,10 +958,8 @@ class ChatActivity :
val newString = state.messageEdited.ocs?.data?.parentMessage?.message ?: "(null)"
val id = state.messageEdited.ocs?.data?.parentMessage?.id.toString()
val index = adapter?.getMessagePositionById(id) ?: 0
val item = adapter?.items?.get(index)?.item
item?.let {
setMessageAsEdited(item as ChatMessage, newString)
}
val message = adapter?.items?.get(index)?.item as ChatMessage
setMessageAsEdited(message, newString)
}
is MessageInputViewModel.EditMessageErrorState -> {
@ -1097,29 +1001,6 @@ class ChatActivity :
binding.voiceRecordingLock.y -= y
}
chatViewModel.unbindRoomResult.observe(this) { uiState ->
when (uiState) {
is ChatViewModel.UnbindRoomUiState.Success -> {
binding.conversationDeleteNotice.visibility = View.GONE
Snackbar.make(
binding.root,
context.getString(R.string.nc_room_retention),
Snackbar.LENGTH_LONG
).show()
chatMenu?.removeItem(R.id.conversation_event)
}
is ChatViewModel.UnbindRoomUiState.Error -> {
Snackbar.make(
binding.root,
context.getString(R.string.nc_common_error_sorry),
Snackbar.LENGTH_LONG
).show()
}
else -> { }
}
}
chatViewModel.outOfOfficeViewState.observe(this) { uiState ->
when (uiState) {
is ChatViewModel.OutOfOfficeUIState.Error -> {
@ -1235,69 +1116,6 @@ class ChatActivity :
}
}
fun showConversationDeletionWarning(retentionPeriod: Int) {
binding.conversationDeleteNotice.visibility = View.VISIBLE
binding.conversationDeleteNotice.apply {
isClickable = false
isFocusable = false
bringToFront()
}
val deleteNoticeText = binding.conversationDeleteNotice.findViewById<TextView>(R.id.deletion_message)
viewThemeUtils.material.themeCardView(binding.conversationDeleteNotice)
deleteNoticeText.text = resources.getQuantityString(
R.plurals.nc_conversation_auto_delete_info,
retentionPeriod,
retentionPeriod
)
viewThemeUtils.material.colorMaterialButtonPrimaryTonal(
binding.conversationDeleteNotice
.findViewById<MaterialButton>(R.id.keep_button)
)
if (ConversationUtils.isParticipantOwnerOrModerator(currentConversation!!)) {
binding.conversationDeleteNotice.findViewById<MaterialButton>(R.id.delete_now_button).visibility =
View.VISIBLE
binding.conversationDeleteNotice.findViewById<MaterialButton>(R.id.keep_button).visibility = View.VISIBLE
} else {
binding.conversationDeleteNotice.findViewById<MaterialButton>(R.id.delete_now_button).visibility =
View.GONE
binding.conversationDeleteNotice.findViewById<MaterialButton>(R.id.keep_button).visibility = View.GONE
}
binding.conversationDeleteNotice.findViewById<MaterialButton>(R.id.delete_now_button).setOnClickListener {
deleteConversationDialog(it.context)
}
binding.conversationDeleteNotice.findViewById<MaterialButton>(R.id.keep_button).setOnClickListener {
chatViewModel.unbindRoom(credentials!!, conversationUser?.baseUrl!!, currentConversation?.token!!)
}
}
fun deleteConversationDialog(context: Context) {
val dialogBuilder = MaterialAlertDialogBuilder(context)
.setIcon(
viewThemeUtils.dialog
.colorMaterialAlertDialogIcon(context, R.drawable.ic_delete_black_24dp)
)
.setTitle(R.string.nc_delete_call)
.setMessage(R.string.nc_delete_conversation_more)
.setPositiveButton(R.string.nc_delete) { _, _ ->
currentConversation?.let { conversation ->
deleteConversation(conversation)
}
}
.setNegativeButton(R.string.nc_cancel) { _, _ ->
}
viewThemeUtils.dialog
.colorMaterialAlertDialogBackground(context, dialogBuilder)
val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
)
}
@Suppress("Detekt.TooGenericExceptionCaught")
override fun onResume() {
super.onResume()
@ -2054,7 +1872,7 @@ class ChatActivity :
private fun shouldShowLobby(): Boolean {
if (currentConversation != null) {
return hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.WEBINARY_LOBBY) &&
return CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.WEBINARY_LOBBY) &&
currentConversation?.lobbyState == ConversationEnums.LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
!ConversationUtils.canModerate(currentConversation!!, spreedCapabilities) &&
!participantPermissions.canIgnoreLobby()
@ -2309,8 +2127,15 @@ class ChatActivity :
}
}
private fun onMessageSearchResult(intent: Intent?) {
val messageId = intent?.getStringExtra(MessageSearchActivity.RESULT_KEY_MESSAGE_ID)
messageId?.let { id ->
scrollToAndCenterMessageWithId(id)
}
}
private fun executeIfResultOk(result: ActivityResult, onResult: (intent: Intent?) -> Unit) {
if (result.resultCode == RESULT_OK) {
if (result.resultCode == Activity.RESULT_OK) {
onResult(result.data)
} else {
Log.e(TAG, "resultCode for received intent was != ok")
@ -2339,8 +2164,8 @@ class ChatActivity :
} else {
Log.d(
TAG,
"message $messageId that should be scrolled " +
"to was not found (scrollToAndCenterMessageWithId)"
"message $messageId that should be scrolled to was not found " +
"(scrollToAndCenterMessageWithId)"
)
}
}
@ -2804,7 +2629,7 @@ class ChatActivity :
}
if (this::spreedCapabilities.isInitialized) {
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MESSAGE_EXPIRATION)) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MESSAGE_EXPIRATION)) {
deleteExpiredMessages()
}
} else {
@ -3051,7 +2876,6 @@ class ChatActivity :
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.menu_conversation, menu)
chatMenu = menu
if (currentConversation?.objectType == ConversationEnums.ObjectType.EVENT) {
eventConversationMenuItem = menu.findItem(R.id.conversation_event)
@ -3065,6 +2889,7 @@ class ChatActivity :
loadAvatarForStatusBar()
setActionBarTitle()
}
return true
}
@ -3072,7 +2897,7 @@ class ChatActivity :
super.onPrepareOptionsMenu(menu)
if (this::spreedCapabilities.isInitialized) {
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.READ_ONLY_ROOMS)) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.READ_ONLY_ROOMS)) {
checkShowCallButtons()
}
@ -3093,7 +2918,7 @@ class ChatActivity :
}.collect()
}
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.SILENT_CALL)) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.SILENT_CALL)) {
Handler().post {
findViewById<View?>(R.id.conversation_voice_call)?.setOnLongClickListener {
showCallButtonMenu(true)
@ -3152,10 +2977,10 @@ class ChatActivity :
else -> super.onOptionsItemSelected(item)
}
@SuppressLint("InflateParams")
private fun showPopupWindow(anchorView: View) {
val popupView = layoutInflater.inflate(R.layout.item_event_schedule, null)
val titleTextView = popupView.findViewById<TextView>(R.id.event_scheduled)
val subtitleTextView = popupView.findViewById<TextView>(R.id.meetingTime)
val popupWindow = PopupWindow(
@ -3184,7 +3009,28 @@ class ChatActivity :
deleteConversation.visibility = View.VISIBLE
deleteConversation.setOnClickListener {
deleteConversationDialog(it.context)
val dialogBuilder = MaterialAlertDialogBuilder(it.context)
.setIcon(
viewThemeUtils.dialog
.colorMaterialAlertDialogIcon(context, R.drawable.ic_delete_black_24dp)
)
.setTitle(R.string.nc_delete_call)
.setMessage(R.string.nc_delete_conversation_more)
.setPositiveButton(R.string.nc_delete) { _, _ ->
currentConversation?.let { conversation ->
deleteConversation(conversation)
}
}
.setNegativeButton(R.string.nc_cancel) { _, _ ->
}
viewThemeUtils.dialog
.colorMaterialAlertDialogBackground(it.context, dialogBuilder)
val dialog = dialogBuilder.show()
viewThemeUtils.platform.colorTextButtons(
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
)
popupWindow.dismiss()
}
} else {
@ -3313,11 +3159,9 @@ class ChatActivity :
context.resources.getString(R.string.nc_tomorrow_meeting),
startDateTime.format(DateTimeFormatter.ofPattern("HH:mm"))
)
else -> startDateTime.format(DateTimeFormatter.ofPattern("MMM d, yyyy, HH:mm"))
}
}
currentTime.isAfter(endDateTime) -> context.resources.getString(R.string.nc_meeting_ended)
else -> context.resources.getString(R.string.nc_ongoing_meeting)
}
@ -3604,7 +3448,7 @@ class ChatActivity :
fun copyMessage(message: IMessage?) {
val clipboardManager =
getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clipData = ClipData.newPlainText(
resources?.getString(R.string.nc_app_product_name),
message?.text
@ -3780,7 +3624,6 @@ class ChatActivity :
)
showSnackBar(roomToken)
}
else -> {}
}
}
@ -3801,7 +3644,6 @@ class ChatActivity :
chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(chatIntent)
}
fun openInFilesApp(message: ChatMessage) {
val keyID = message.selectedIndividualHashMap!![PreviewMessageViewHolder.KEY_ID]
val link = message.selectedIndividualHashMap!!["link"]
@ -3918,7 +3760,7 @@ class ChatActivity :
val isOlderThanSixHours = message
.createdAt
.before(Date(System.currentTimeMillis() - AGE_THRESHOLD_FOR_DELETE_MESSAGE))
val hasDeleteMessagesUnlimitedCapability = hasSpreedFeatureCapability(
val hasDeleteMessagesUnlimitedCapability = CapabilitiesUtil.hasSpreedFeatureCapability(
spreedCapabilities,
SpreedFeatures.DELETE_MESSAGES_UNLIMITED
)
@ -3928,7 +3770,7 @@ class ChatActivity :
!hasDeleteMessagesUnlimitedCapability && isOlderThanSixHours -> false
message.systemMessageType != ChatMessage.SystemMessageType.DUMMY -> false
message.isDeleted -> false
!hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.DELETE_MESSAGES) -> false
!CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.DELETE_MESSAGES) -> false
!participantPermissions.hasChatPermission() -> false
hasDeleteMessagesUnlimitedCapability -> true
else -> true
@ -4085,7 +3927,9 @@ class ChatActivity :
}
if (!foundMessage) {
Log.d(TAG, "quoted message with id " + parentMessage.id + " was not found in adapter")
startContextChatWindowForMessage(parentMessage.id)
// TODO: show better info
// TODO: improve handling how this can be avoided. E.g. loading chat until message is reached...
Snackbar.make(binding.root, "Message was not found", Snackbar.LENGTH_LONG).show()
}
}

View File

@ -200,7 +200,7 @@ class MessageInputFragment : Fragment() {
val connectionGained = (!wasOnline && isOnline)
Log.d(TAG, "isOnline: $isOnline\nwasOnline: $wasOnline\nconnectionGained: $connectionGained")
if (connectionGained) {
chatActivity.messageInputViewModel.sendUnsentMessages(
chatActivity.messageInputViewModel.sendTempMessages(
chatActivity.conversationUser!!.getCredentials(),
ApiUtils.getUrlForChat(
chatActivity.chatApiVersion,

View File

@ -110,7 +110,7 @@ interface ChatMessageRepository : LifecycleAwareManager {
suspend fun editTempChatMessage(message: ChatMessage, editedMessageText: String): Flow<Boolean>
suspend fun sendUnsentChatMessages(credentials: String, url: String)
suspend fun sendTempChatMessages(credentials: String, url: String)
suspend fun deleteTempMessage(chatMessage: ChatMessage)
}

View File

@ -183,26 +183,19 @@ class MediaPlayerManager : LifecycleAwareManager {
continue
}
mediaPlayer?.let { player ->
try {
if (!player.isPlaying) return@let
} catch (e: IllegalStateException) {
Log.e(TAG, "Seekbar updated during an improper state: $e")
return@let
}
val pos = player.currentPosition
if (mediaPlayer != null && mediaPlayer?.isPlaying == true) {
val pos = mediaPlayer!!.currentPosition
mediaPlayerPosition = pos
val progress = (pos.toFloat() / mediaPlayerDuration) * DIVIDER
val progressI = ceil(progress).toInt()
val seconds = (pos / ONE_SEC)
_mediaPlayerSeekBarPosition.emit(progressI)
currentCycledMessage?.let { msg ->
msg.isPlayingVoiceMessage = true
msg.voiceMessageSeekbarProgress = progressI
msg.voiceMessagePlayedSeconds = seconds
if (progressI >= IS_PLAYED_CUTOFF) msg.wasPlayedVoiceMessage = true
_mediaPlayerSeekBarPositionMsg.emit(msg)
currentCycledMessage?.let {
it.isPlayingVoiceMessage = true
it.voiceMessageSeekbarProgress = progressI
it.voiceMessagePlayedSeconds = seconds
if (progressI >= IS_PLAYED_CUTOFF) it.wasPlayedVoiceMessage = true
_mediaPlayerSeekBarPositionMsg.emit(it)
}
}

View File

@ -14,7 +14,6 @@ import android.util.Log
import com.bluelinelabs.logansquare.annotation.JsonIgnore
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.data.database.model.SendStatus
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.chat.ChatUtils.Companion.getParsedMessage
import com.nextcloud.talk.models.json.chat.ReadStatus
@ -120,7 +119,7 @@ data class ChatMessage(
var referenceId: String? = null,
var sendStatus: SendStatus? = null,
var sendingFailed: Boolean = true,
var silent: Boolean = false

View File

@ -76,5 +76,4 @@ interface ChatNetworkDataSource {
limit: Int
): List<ChatMessageJson>
suspend fun getOpenGraph(credentials: String, baseUrl: String, extractedLinkToPreview: String): Reference?
suspend fun unbindRoom(credentials: String, baseUrl: String, roomToken: String): GenericOverall
}

View File

@ -19,7 +19,6 @@ import com.nextcloud.talk.data.database.mappers.asEntity
import com.nextcloud.talk.data.database.mappers.asModel
import com.nextcloud.talk.data.database.model.ChatBlockEntity
import com.nextcloud.talk.data.database.model.ChatMessageEntity
import com.nextcloud.talk.data.database.model.SendStatus
import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.extensions.toIntOrZero
@ -215,8 +214,7 @@ class OfflineFirstChatRepository @Inject constructor(
)
}
// this call could be deleted when we have a worker to send messages..
sendUnsentChatMessages(credentials, urlForChatting)
sendTempChatMessages(credentials, urlForChatting)
// delay is a dirty workaround to make sure messages are added to adapter on initial load before dealing
// with them (otherwise there is a race condition).
@ -367,18 +365,11 @@ class OfflineFirstChatRepository @Inject constructor(
lookIntoFuture: Boolean,
showUnreadMessagesMarker: Boolean
) {
receivedChatMessages.forEach {
Log.d(TAG, "receivedChatMessage: " + it.message)
}
// remove all temp messages from UI
val oldTempMessages = chatDao.getTempMessagesForConversation(internalConversationId)
.first()
.map(ChatMessageEntity::asModel)
oldTempMessages.forEach {
Log.d(TAG, "oldTempMessage to be removed from UI: " + it.message)
_removeMessageFlow.emit(it)
}
oldTempMessages.forEach { _removeMessageFlow.emit(it) }
// add new messages to UI
val tripleChatMessages = Triple(lookIntoFuture, showUnreadMessagesMarker, receivedChatMessages)
@ -387,9 +378,6 @@ class OfflineFirstChatRepository @Inject constructor(
// remove temp messages from DB that are now found in the new messages
val chatMessagesReferenceIds = receivedChatMessages.mapTo(HashSet(receivedChatMessages.size)) { it.referenceId }
val tempChatMessagesThatCanBeReplaced = oldTempMessages.filter { it.referenceId in chatMessagesReferenceIds }
tempChatMessagesThatCanBeReplaced.forEach {
Log.d(TAG, "oldTempMessage that was identified in newMessages: " + it.message)
}
chatDao.deleteTempChatMessages(
internalConversationId,
tempChatMessagesThatCanBeReplaced.map { it.referenceId!! }
@ -401,10 +389,6 @@ class OfflineFirstChatRepository @Inject constructor(
.sortedBy { it.internalId }
.map(ChatMessageEntity::asModel)
remainingTempMessages.forEach {
Log.d(TAG, "remainingTempMessage: " + it.message)
}
val triple = Triple(true, false, remainingTempMessages)
_messageFlow.emit(triple)
}
@ -859,17 +843,6 @@ class OfflineFirstChatRepository @Inject constructor(
val chatMessageModel = response.ocs?.data?.asModel()
val sentMessage = chatDao.getTempMessageForConversation(
internalConversationId,
referenceId
).firstOrNull()
sentMessage?.let {
it.sendStatus = SendStatus.SENT_PENDING_ACK
chatDao.updateChatMessage(it)
}
Log.d(TAG, "sending chat message succeeded: " + message)
emit(Result.success(chatMessageModel))
}
.catch { e ->
@ -880,7 +853,7 @@ class OfflineFirstChatRepository @Inject constructor(
referenceId
).firstOrNull()
failedMessage?.let {
it.sendStatus = SendStatus.FAILED
it.sendingFailed = true
chatDao.updateChatMessage(it)
val failedMessageModel = it.asModel()
@ -901,7 +874,7 @@ class OfflineFirstChatRepository @Inject constructor(
referenceId: String
): Flow<Result<ChatMessage?>> {
val messageToResend = chatDao.getTempMessageForConversation(internalConversationId, referenceId).first()
messageToResend.sendStatus = SendStatus.PENDING
messageToResend.sendingFailed = false
chatDao.updateChatMessage(messageToResend)
val messageToResendModel = messageToResend.asModel()
@ -957,8 +930,8 @@ class OfflineFirstChatRepository @Inject constructor(
}
}
override suspend fun sendUnsentChatMessages(credentials: String, url: String) {
val tempMessages = chatDao.getTempUnsentMessagesForConversation(internalConversationId).first()
override suspend fun sendTempChatMessages(credentials: String, url: String) {
val tempMessages = chatDao.getTempMessagesForConversation(internalConversationId).first()
tempMessages.sortedBy { it.internalId }.onEach {
sendChatMessage(
credentials,
@ -1052,7 +1025,7 @@ class OfflineFirstChatRepository @Inject constructor(
actorDisplayName = currentUser.displayName!!,
referenceId = referenceId,
isTemporary = true,
sendStatus = SendStatus.PENDING,
sendingFailed = false,
silent = sendWithoutNotification
)
return entity

View File

@ -217,9 +217,4 @@ class RetrofitChatNetwork(
extractedLinkToPreview
).blockingFirst().ocs?.data?.references?.entries?.iterator()?.next()?.value
}
override suspend fun unbindRoom(credentials: String, baseUrl: String, roomToken: String): GenericOverall {
val url = ApiUtils.getUrlForUnbindingRoom(baseUrl, roomToken)
return ncApiCoroutines.unbindRoom(credentials, url)
}
}

View File

@ -146,10 +146,6 @@ class ChatViewModel @Inject constructor(
val outOfOfficeViewState: LiveData<OutOfOfficeUIState>
get() = _outOfOfficeViewState
private val _unbindRoomResult = MutableLiveData<UnbindRoomUiState>(UnbindRoomUiState.None)
val unbindRoomResult: LiveData<UnbindRoomUiState>
get() = _unbindRoomResult
private val _voiceMessagePlaybackSpeedPreferences: MutableLiveData<Map<String, PlaybackSpeed>> = MutableLiveData()
val voiceMessagePlaybackSpeedPreferences: LiveData<Map<String, PlaybackSpeed>>
get() = _voiceMessagePlaybackSpeedPreferences
@ -804,18 +800,6 @@ class ChatViewModel @Inject constructor(
}
}
@Suppress("Detekt.TooGenericExceptionCaught")
fun unbindRoom(credentials: String, baseUrl: String, roomToken: String) {
viewModelScope.launch {
try {
val response = chatNetworkDataSource.unbindRoom(credentials, baseUrl, roomToken)
_unbindRoomResult.value = UnbindRoomUiState.Success(response.ocs?.meta?.statusCode!!)
} catch (exception: Exception) {
_unbindRoomResult.value = UnbindRoomUiState.Error(exception.message.toString())
}
}
}
fun resendMessage(credentials: String, urlForChat: String, message: ChatMessage) {
viewModelScope.launch {
chatRepository.resendChatMessage(
@ -867,10 +851,4 @@ class ChatViewModel @Inject constructor(
data class Success(val userAbsence: UserAbsenceData) : OutOfOfficeUIState()
data class Error(val exception: Exception) : OutOfOfficeUIState()
}
sealed class UnbindRoomUiState {
data object None : UnbindRoomUiState()
data class Success(val statusCode: Int) : UnbindRoomUiState()
data class Error(val message: String) : UnbindRoomUiState()
}
}

View File

@ -169,9 +169,9 @@ class MessageInputViewModel @Inject constructor(
}
}
fun sendUnsentMessages(credentials: String, url: String) {
fun sendTempMessages(credentials: String, url: String) {
viewModelScope.launch {
chatRepository.sendUnsentChatMessages(
chatRepository.sendTempChatMessages(
credentials,
url
)

View File

@ -1,7 +1,7 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/

View File

@ -29,7 +29,6 @@ fun ContactsScreen(contactsViewModel: ContactsViewModel, uiState: ContactsUiStat
val isSearchActive by contactsViewModel.isSearchActive.collectAsStateWithLifecycle()
val isAddParticipants by contactsViewModel.isAddParticipantsView.collectAsStateWithLifecycle()
val autocompleteUsers by contactsViewModel.selectedParticipantsList.collectAsStateWithLifecycle()
val enableAddButton by contactsViewModel.enableAddButton.collectAsStateWithLifecycle()
Scaffold(
topBar = {
@ -50,10 +49,6 @@ fun ContactsScreen(contactsViewModel: ContactsViewModel, uiState: ContactsUiStat
},
onUpdateAutocompleteUsers = {
contactsViewModel.getContactsFromSearchParams()
},
enableAddButton = enableAddButton,
clickAddButton = {
contactsViewModel.modifyClickAddButton(it)
}
)
},

View File

@ -36,15 +36,6 @@ class ContactsViewModel @Inject constructor(
private val _isAddParticipantsView = MutableStateFlow(false)
val isAddParticipantsView: StateFlow<Boolean> = _isAddParticipantsView
private val _enableAddButton = MutableStateFlow(false)
val enableAddButton: StateFlow<Boolean> = _enableAddButton
@Suppress("PropertyName")
private val _selectedContacts = MutableStateFlow<List<AutocompleteUser>>(emptyList())
@Suppress("PropertyName")
private val _clickAddButton = MutableStateFlow(false)
private var hideAlreadyAddedParticipants: Boolean = false
init {
@ -55,28 +46,14 @@ class ContactsViewModel @Inject constructor(
_searchQuery.value = query
}
fun modifyClickAddButton(value: Boolean) {
_clickAddButton.value = value
}
fun selectContact(contact: AutocompleteUser) {
val updatedParticipants = selectedParticipants.value + contact
selectedParticipants.value = updatedParticipants
_selectedContacts.value = _selectedContacts.value + contact
}
fun updateAddButtonState() {
if (_selectedContacts.value.isEmpty()) {
_enableAddButton.value = false
} else {
_enableAddButton.value = true
}
}
fun deselectContact(contact: AutocompleteUser) {
val updatedParticipants = selectedParticipants.value - contact
selectedParticipants.value = updatedParticipants
_selectedContacts.value = _selectedContacts.value - contact
}
fun updateSelectedParticipants(participants: List<AutocompleteUser>) {
@ -99,23 +76,20 @@ class ContactsViewModel @Inject constructor(
}
@Suppress("Detekt.TooGenericExceptionCaught")
fun getContactsFromSearchParams(query: String = "") {
fun getContactsFromSearchParams() {
_contactsViewState.value = ContactsUiState.Loading
viewModelScope.launch {
try {
val contacts = repository.getContacts(
if (query != "") query else searchQuery.value,
searchQuery.value,
shareTypeList
)
val contactsList: MutableList<AutocompleteUser>? = contacts.ocs!!.data?.toMutableList()
if (hideAlreadyAddedParticipants && !_clickAddButton.value) {
if (hideAlreadyAddedParticipants) {
contactsList?.removeAll(selectedParticipants.value)
}
if (_clickAddButton.value) {
contactsList?.removeAll(selectedParticipants.value)
contactsList?.addAll(_selectedContacts.value)
}
_contactsViewState.value = ContactsUiState.Success(contactsList)
} catch (exception: Exception) {
_contactsViewState.value = ContactsUiState.Error(exception.message ?: "")

View File

@ -13,8 +13,6 @@ import android.app.Activity
import android.content.Intent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Search
@ -22,10 +20,8 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@ -34,7 +30,6 @@ import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Suppress("LongParameterList", "LongMethod")
@Composable
fun AppBar(
title: String,
@ -45,18 +40,12 @@ fun AppBar(
onEnableSearch: () -> Unit,
onDisableSearch: () -> Unit,
onUpdateSearchQuery: (String) -> Unit,
onUpdateAutocompleteUsers: () -> Unit,
enableAddButton: Boolean,
clickAddButton: (Boolean) -> Unit
onUpdateAutocompleteUsers: () -> Unit
) {
val context = LocalContext.current
val appTitle = if (!isSearchActive) {
title
} else {
""
}
TopAppBar(
title = { Text(text = appTitle) },
title = { Text(text = title) },
navigationIcon = {
IconButton(onClick = {
(context as? Activity)?.finish()
@ -65,53 +54,36 @@ fun AppBar(
}
},
actions = {
if (!isSearchActive) {
IconButton(onClick = onEnableSearch) {
Icon(Icons.Filled.Search, contentDescription = stringResource(R.string.search_icon))
}
if (isAddParticipants) {
Text(
text = stringResource(id = R.string.nc_contacts_done),
modifier = Modifier.clickable {
val resultIntent = Intent().apply {
putParcelableArrayListExtra(
"selectedParticipants",
ArrayList(autocompleteUsers)
)
}
(context as? Activity)?.setResult(Activity.RESULT_OK, resultIntent)
(context as? Activity)?.finish()
IconButton(onClick = onEnableSearch) {
Icon(Icons.Filled.Search, contentDescription = stringResource(R.string.search_icon))
}
if (isAddParticipants) {
Text(
text = stringResource(id = R.string.nc_contacts_done),
modifier = Modifier.clickable {
val resultIntent = Intent().apply {
putParcelableArrayListExtra(
"selectedParticipants",
ArrayList(autocompleteUsers)
)
}
)
}
(context as? Activity)?.setResult(Activity.RESULT_OK, resultIntent)
(context as? Activity)?.finish()
}
)
}
}
)
if (isSearchActive) {
Row(modifier = Modifier.fillMaxWidth()) {
Row {
SearchComponent(
text = searchQuery,
onTextChange = { searchQuery ->
onUpdateSearchQuery(searchQuery)
onUpdateAutocompleteUsers()
},
onDisableSearch = onDisableSearch,
modifier = Modifier.weight(1f)
onDisableSearch = onDisableSearch
)
if (isAddParticipants) {
TextButton(
modifier = Modifier.align(Alignment.CenterVertically).wrapContentWidth(),
onClick = {
onDisableSearch()
onUpdateSearchQuery("")
clickAddButton(true)
onUpdateAutocompleteUsers()
},
enabled = enableAddButton
) {
Text(text = context.getString(R.string.add_participants))
}
}
}
}
}

View File

@ -65,10 +65,8 @@ fun ContactItemRow(contact: AutocompleteUser, contactsViewModel: ContactsViewMod
isSelected = !isSelected
if (isSelected) {
contactsViewModel.selectContact(contact)
contactsViewModel.updateAddButtonState()
} else {
contactsViewModel.deselectContact(contact)
contactsViewModel.updateAddButtonState()
}
}
}

View File

@ -1,13 +1,14 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.contacts.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
@ -33,19 +34,16 @@ import androidx.compose.ui.unit.sp
import com.nextcloud.talk.R
@Composable
fun SearchComponent(
text: String,
onTextChange: (String) -> Unit,
onDisableSearch: () -> Unit,
modifier: Modifier = Modifier
) {
fun SearchComponent(text: String, onTextChange: (String) -> Unit, onDisableSearch: () -> Unit) {
val keyboardController = LocalSoftwareKeyboardController.current
TextField(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxWidth()
.height(60.dp),
value = text,
onValueChange = { onTextChange(it) },
modifier = modifier
.background(MaterialTheme.colorScheme.background)
.height(60.dp),
placeholder = { Text(text = stringResource(R.string.nc_search)) },
textStyle = TextStyle(fontSize = 16.sp),
singleLine = true,

View File

@ -103,11 +103,6 @@ import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.time.Instant
import java.time.ZoneOffset
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.Calendar
import java.util.Collections
import java.util.Locale
@ -155,7 +150,7 @@ class ConversationInfoActivity :
get() {
if (!TextUtils.isEmpty(conversationToken)) {
val data = Data.Builder()
data.putString(KEY_ROOM_TOKEN, conversationToken)
data.putString(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
data.putLong(BundleKeys.KEY_INTERNAL_USER_ID, conversationUser.id!!)
return data.build()
}
@ -197,7 +192,7 @@ class ConversationInfoActivity :
conversationUser = currentUserProvider.currentUser.blockingGet()
conversationToken = intent.getStringExtra(KEY_ROOM_TOKEN)!!
conversationToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!!
hasAvatarSpacing = intent.getBooleanExtra(BundleKeys.KEY_ROOM_ONE_TO_ONE, false)
credentials = ApiUtils.getCredentials(conversationUser.username, conversationUser.token)!!
}
@ -250,51 +245,8 @@ class ConversationInfoActivity :
initBanActorObserver()
initConversationReadOnlyObserver()
initClearChatHistoryObserver()
initMarkConversationAsSensitiveObserver()
initMarkConversationAsInsensitiveObserver()
initMarkConversationAsImportantObserver()
initMarkConversationAsUnimportantObserver()
}
private fun initMarkConversationAsSensitiveObserver() {
viewModel.markAsSensitiveResult.observe(this) { uiState ->
when (uiState) {
is ConversationInfoViewModel.MarkConversationAsSensitiveViewState.Success -> {
Snackbar.make(
binding.root,
context.getString(R.string.nc_mark_conversation_as_sensitive),
Snackbar.LENGTH_LONG
).show()
}
is ConversationInfoViewModel.MarkConversationAsSensitiveViewState.Error -> {
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
Log.e(TAG, "failed to mark conversation as insensitive", uiState.exception)
}
else -> {
}
}
}
}
private fun initMarkConversationAsInsensitiveObserver() {
viewModel.markAsInsensitiveResult.observe(this) { uiState ->
when (uiState) {
is ConversationInfoViewModel.MarkConversationAsInsensitiveViewState.Success -> {
Snackbar.make(
binding.root,
context.getString(R.string.nc_mark_conversation_as_insensitive),
Snackbar.LENGTH_LONG
).show()
}
is ConversationInfoViewModel.MarkConversationAsInsensitiveViewState.Error -> {
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
Log.e(TAG, "failed to mark conversation as sensitive", uiState.exception)
}
else -> {
}
}
}
}
private fun initClearChatHistoryObserver() {
viewModel.clearChatHistoryViewState.observe(this) { uiState ->
when (uiState) {
@ -383,47 +335,6 @@ class ConversationInfoActivity :
}
}
private fun initMarkConversationAsImportantObserver() {
viewModel.markAsImportantResult.observe(this) { uiState ->
when (uiState) {
is ConversationInfoViewModel.MarkConversationAsImportantViewState.Success -> {
Snackbar.make(
binding.root,
context.getString(R.string.nc_mark_conversation_as_important),
Snackbar.LENGTH_LONG
).show()
}
is ConversationInfoViewModel.MarkConversationAsImportantViewState.Error -> {
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
Log.e(TAG, "failed to mark conversation as important", uiState.exception)
}
else -> {
}
}
}
}
private fun initMarkConversationAsUnimportantObserver() {
viewModel.markAsUnimportantResult.observe(this) { uiState ->
when (uiState) {
is ConversationInfoViewModel.MarkConversationAsUnimportantViewState.Success -> {
Snackbar.make(
binding.root,
context.getString(R.string.nc_mark_conversation_as_unimportant),
Snackbar.LENGTH_LONG
).show()
}
is ConversationInfoViewModel.MarkConversationAsUnimportantViewState.Error -> {
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
Log.e(TAG, "failed to mark conversation as unimportant", uiState.exception)
}
else -> {
}
}
}
}
@Suppress("Detekt.TooGenericExceptionCaught")
private fun initViewStateObserver() {
viewModel.viewState.observe(this) { state ->
when (state) {
@ -443,12 +354,6 @@ class ConversationInfoActivity :
canGeneratePrettyURL
)
}
conversation?.let {
if (it.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
viewModel.getProfileData(conversationUser, it.name)
}
}
}
is ConversationInfoViewModel.GetRoomErrorState -> {
@ -458,57 +363,6 @@ class ConversationInfoActivity :
else -> {}
}
}
viewModel.getProfileViewState.observe(this) { state ->
when (state) {
is ConversationInfoViewModel.GetProfileSuccessState -> {
try {
// Pronouns
val profile = state.profile
val pronouns = profile.pronouns ?: ""
binding.pronouns.text = pronouns
// Role @ Organization
val concat1 = if (profile.role != null && profile.company != null) " @ " else ""
val role = profile.role ?: ""
val company = profile.company ?: ""
val professionCompanyText = "$role$concat1$company"
binding.professionCompany.text = professionCompanyText
// Local Time: xX:xX · Address
val profileZoneOffset = ZoneOffset.ofTotalSeconds(0)
val secondsToAdd = profile.timezoneOffset?.toLong() ?: 0
val localTime = ZonedDateTime.ofInstant(
Instant.now().plusSeconds(secondsToAdd),
profileZoneOffset
)
val localTimeString = localTime.format(
DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.getDefault())
)
val concat2 = if (profile.address != null) " · " else ""
val address = profile.address ?: ""
val localTimeLocation = "$localTimeString$concat2$address"
binding.locationTime.text = resources.getString(R.string.local_time, localTimeLocation)
binding.pronouns.visibility = VISIBLE
binding.professionCompany.visibility = if (professionCompanyText.isNotEmpty()) VISIBLE else GONE
binding.locationTime.visibility = VISIBLE
} catch (e: Exception) {
Log.e(TAG, "Exception getting profile information", e)
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
}
}
is ConversationInfoViewModel.GetProfileErrorState -> {
Log.e(TAG, "Network error occurred getting profile information")
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
}
else -> {}
}
}
}
private fun setupActionBar() {
@ -550,7 +404,7 @@ class ConversationInfoActivity :
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.edit) {
val bundle = Bundle()
bundle.putString(KEY_ROOM_TOKEN, conversationToken)
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
val intent = Intent(this, ConversationInfoEditActivity::class.java)
intent.putExtras(bundle)
@ -567,9 +421,7 @@ class ConversationInfoActivity :
binding.notificationSettingsView.importantConversationSwitch,
binding.guestAccessView.allowGuestsSwitch,
binding.guestAccessView.passwordProtectionSwitch,
binding.recordingConsentView.recordingConsentForConversationSwitch,
binding.lockConversationSwitch,
binding.notificationSettingsView.sensitiveConversationSwitch
binding.recordingConsentView.recordingConsentForConversationSwitch
).forEach(viewThemeUtils.talk::colorSwitch)
}
}
@ -593,7 +445,7 @@ class ConversationInfoActivity :
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
intent.putExtra(BundleKeys.KEY_CONVERSATION_NAME, conversation?.displayName)
intent.putExtra(KEY_ROOM_TOKEN, conversationToken)
intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, conversationToken)
intent.putExtra(
SharedItemsActivity.KEY_USER_IS_OWNER_OR_MODERATOR,
ConversationUtils.isParticipantOwnerOrModerator(conversation!!)
@ -686,7 +538,7 @@ class ConversationInfoActivity :
) {
binding.webinarInfoView.startTimeButtonSummary.text = (
dateUtils.getLocalDateTimeStringFromTimestamp(
conversation!!.lobbyTimer * DateConstants.SECOND_DIVIDER
conversation!!.lobbyTimer!! * DateConstants.SECOND_DIVIDER
)
)
} else {
@ -882,13 +734,16 @@ class ConversationInfoActivity :
private fun selectParticipantsToAdd() {
val bundle = Bundle()
val existingParticipants = ArrayList<AutocompleteUser>()
for (userItem in userItems) {
val user = AutocompleteUser(
userItem.model.calculatedActorId!!,
userItem.model.displayName,
userItem.model.calculatedActorType.name.lowercase()
)
existingParticipants.add(user)
if (userItem.model.calculatedActorType == USERS) {
val user = AutocompleteUser(
userItem.model.calculatedActorId!!,
userItem.model.displayName,
userItem.model.calculatedActorType.name.lowercase()
)
existingParticipants.add(user)
}
}
bundle.putBoolean(BundleKeys.KEY_ADD_PARTICIPANTS, true)
@ -1087,31 +942,6 @@ class ConversationInfoActivity :
}
}
binding.notificationSettingsView.importantConversationSwitch.isChecked = conversation!!.hasImportant
binding.notificationSettingsView.notificationSettingsImportantConversation.setOnClickListener {
val isChecked = binding.notificationSettingsView.importantConversationSwitch.isChecked
binding.notificationSettingsView.importantConversationSwitch.isChecked = !isChecked
if (!isChecked) {
viewModel.markConversationAsImportant(
credentials,
conversationUser.baseUrl!!,
conversation?.token!!
)
} else {
viewModel.markConversationAsUnimportant(
credentials,
conversationUser.baseUrl!!,
conversation?.token!!
)
}
}
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.IMPORTANT_CONVERSATIONS)) {
binding.notificationSettingsView.notificationSettingsImportantConversation.visibility = VISIBLE
} else {
binding.notificationSettingsView.notificationSettingsImportantConversation.visibility = GONE
}
if (!hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.ARCHIVE_CONVERSATIONS)) {
binding.archiveConversationBtn.visibility = GONE
binding.archiveConversationTextHint.visibility = GONE
@ -1147,31 +977,6 @@ class ConversationInfoActivity :
binding.archiveConversationText.text = resources.getString(R.string.archive_conversation)
binding.archiveConversationTextHint.text = resources.getString(R.string.archive_hint)
}
binding.notificationSettingsView.sensitiveConversationSwitch.isChecked = conversation!!.hasSensitive
binding.notificationSettingsView.notificationSettingsSensitiveConversation.setOnClickListener {
val isChecked = !binding.notificationSettingsView.sensitiveConversationSwitch.isChecked
binding.notificationSettingsView.sensitiveConversationSwitch.isChecked = isChecked
if (isChecked) {
viewModel.markConversationAsSensitive(
credentials,
conversationUser.baseUrl!!,
conversation?.token!!
)
} else {
viewModel.markConversationAsInsensitive(
credentials,
conversationUser.baseUrl!!,
conversation?.token!!
)
}
}
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.SENSITIVE_CONVERSATIONS)) {
binding.notificationSettingsView.notificationSettingsSensitiveConversation.visibility = VISIBLE
} else {
binding.notificationSettingsView.notificationSettingsSensitiveConversation.visibility = GONE
}
if (ConversationUtils.isConversationReadOnlyAvailable(conversationCopy, spreedCapabilities)) {
binding.lockConversation.visibility = VISIBLE
binding.lockConversationSwitch.isChecked = databaseStorageModule!!.getBoolean("lock_switch", false)
@ -1234,7 +1039,7 @@ class ConversationInfoActivity :
binding.displayNameText.text = conversation!!.displayName
if (conversation!!.description != null && conversation!!.description.isNotEmpty()) {
if (conversation!!.description != null && conversation!!.description!!.isNotEmpty()) {
binding.descriptionText.text = conversation!!.description
binding.conversationDescription.visibility = VISIBLE
}
@ -1380,7 +1185,7 @@ class ConversationInfoActivity :
val stringValue: String =
when (
DomainEnumNotificationLevelConverter()
.convertToInt(conversation!!.notificationLevel)
.convertToInt(conversation!!.notificationLevel!!)
) {
NOTIFICATION_LEVEL_ALWAYS -> resources.getString(R.string.nc_notify_me_always)
NOTIFICATION_LEVEL_MENTION -> resources.getString(R.string.nc_notify_me_mention)
@ -1429,7 +1234,7 @@ class ConversationInfoActivity :
conversation!!.name
)
) {
conversation!!.name.let {
conversation!!.name?.let {
binding.avatarImage.loadUserAvatar(
conversationUser,
it,
@ -1843,6 +1648,13 @@ class ConversationInfoActivity :
}
private fun setUpNotificationSettings(module: DatabaseStorageModule) {
binding.notificationSettingsView.notificationSettingsImportantConversation.setOnClickListener {
val isChecked = binding.notificationSettingsView.importantConversationSwitch.isChecked
binding.notificationSettingsView.importantConversationSwitch.isChecked = !isChecked
lifecycleScope.launch {
module.saveBoolean("important_conversation_switch", !isChecked)
}
}
binding.notificationSettingsView.notificationSettingsCallNotifications.setOnClickListener {
val isChecked = binding.notificationSettingsView.callNotificationsSwitch.isChecked
binding.notificationSettingsView.callNotificationsSwitch.isChecked = !isChecked
@ -1850,7 +1662,6 @@ class ConversationInfoActivity :
module.saveBoolean("call_notifications_switch", !isChecked)
}
}
binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown
.setOnItemClickListener { _, _, position, _ ->
val value = resources.getStringArray(R.array.message_notification_levels_entry_values)[position]
@ -1860,6 +1671,9 @@ class ConversationInfoActivity :
}
}
binding.notificationSettingsView.importantConversationSwitch.isChecked = module
.getBoolean("important_conversation_switch", false)
if (conversation!!.remoteServer.isNullOrEmpty()) {
binding.notificationSettingsView.notificationSettingsCallNotifications.visibility = VISIBLE
binding.notificationSettingsView.callNotificationsSwitch.isChecked = module

View File

@ -28,7 +28,6 @@ import com.nextcloud.talk.models.json.participants.Participant.ActorType.EMAILS
import com.nextcloud.talk.models.json.participants.Participant.ActorType.FEDERATED
import com.nextcloud.talk.models.json.participants.Participant.ActorType.GROUPS
import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.models.json.profile.Profile
import com.nextcloud.talk.repositories.conversations.ConversationsRepository
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.ApiUtils.getUrlForRooms
@ -124,40 +123,10 @@ class ConversationInfoViewModel @Inject constructor(
val getConversationReadOnlyState: LiveData<SetConversationReadOnlyViewState>
get() = _getConversationReadOnlyState
@Suppress("PropertyName")
private val _markConversationAsImportantResult =
MutableLiveData<MarkConversationAsImportantViewState>(MarkConversationAsImportantViewState.None)
val markAsImportantResult: LiveData<MarkConversationAsImportantViewState>
get() = _markConversationAsImportantResult
@Suppress("PropertyName")
private val _markConversationAsUnimportantResult =
MutableLiveData<MarkConversationAsUnimportantViewState>(MarkConversationAsUnimportantViewState.None)
val markAsUnimportantResult: LiveData<MarkConversationAsUnimportantViewState>
get() = _markConversationAsUnimportantResult
private val _createRoomViewState = MutableLiveData<CreateRoomUIState>(CreateRoomUIState.None)
val createRoomViewState: LiveData<CreateRoomUIState>
get() = _createRoomViewState
object GetProfileErrorState : ViewState
class GetProfileSuccessState(val profile: Profile) : ViewState
private val _getProfileViewState = MutableLiveData<ViewState>()
val getProfileViewState: LiveData<ViewState>
get() = _getProfileViewState
@Suppress("PropertyName")
private val _markConversationAsSensitiveResult =
MutableLiveData<MarkConversationAsSensitiveViewState>(MarkConversationAsSensitiveViewState.None)
val markAsSensitiveResult: LiveData<MarkConversationAsSensitiveViewState>
get() = _markConversationAsSensitiveResult
@Suppress("PropertyName")
private val _markConversationAsInsensitiveResult =
MutableLiveData<MarkConversationAsInsensitiveViewState>(MarkConversationAsInsensitiveViewState.None)
val markAsInsensitiveResult: LiveData<MarkConversationAsInsensitiveViewState>
get() = _markConversationAsInsensitiveResult
fun getRoom(user: User, token: String) {
_viewState.value = GetRoomStartState
chatNetworkDataSource.getRoom(user, token)
@ -319,23 +288,6 @@ class ConversationInfoViewModel @Inject constructor(
}
}
@Suppress("Detekt.TooGenericExceptionCaught")
fun getProfileData(user: User, userId: String) {
val url = ApiUtils.getUrlForProfile(user.baseUrl!!, userId)
viewModelScope.launch {
try {
val profile = conversationsRepository.getProfile(user.getCredentials(), url)
if (profile != null) {
_getProfileViewState.value = GetProfileSuccessState(profile)
} else {
_getProfileViewState.value = GetProfileErrorState
}
} catch (e: Exception) {
Log.w(TAG, "Failed to get profile data (if not supported there wil be http405)", e)
}
}
}
@Suppress("Detekt.TooGenericExceptionCaught")
fun allowGuests(token: String, allow: Boolean) {
viewModelScope.launch {
@ -373,34 +325,6 @@ class ConversationInfoViewModel @Inject constructor(
conversationsRepository.unarchiveConversation(user.getCredentials(), url)
}
@Suppress("Detekt.TooGenericExceptionCaught")
fun markConversationAsImportant(credentials: String, baseUrl: String, roomToken: String) {
viewModelScope.launch {
try {
val response = conversationsRepository.markConversationAsImportant(credentials, baseUrl, roomToken)
_markConversationAsImportantResult.value =
MarkConversationAsImportantViewState.Success(response.ocs?.meta?.statusCode!!)
} catch (exception: Exception) {
_markConversationAsImportantResult.value =
MarkConversationAsImportantViewState.Error(exception)
}
}
}
@Suppress("Detekt.TooGenericExceptionCaught")
fun markConversationAsUnimportant(credentials: String, baseUrl: String, roomToken: String) {
viewModelScope.launch {
try {
val response = conversationsRepository.markConversationAsUnImportant(credentials, baseUrl, roomToken)
_markConversationAsUnimportantResult.value =
MarkConversationAsUnimportantViewState.Success(response.ocs?.meta?.statusCode!!)
} catch (exception: Exception) {
_markConversationAsUnimportantResult.value =
MarkConversationAsUnimportantViewState.Error(exception)
}
}
}
@Suppress("Detekt.TooGenericExceptionCaught")
fun clearChatHistory(apiVersion: Int, roomToken: String) {
viewModelScope.launch {
@ -413,34 +337,6 @@ class ConversationInfoViewModel @Inject constructor(
}
}
@Suppress("Detekt.TooGenericExceptionCaught")
fun markConversationAsSensitive(credentials: String, baseUrl: String, roomToken: String) {
viewModelScope.launch {
try {
val response = conversationsRepository.markConversationAsSensitive(credentials, baseUrl, roomToken)
_markConversationAsSensitiveResult.value =
MarkConversationAsSensitiveViewState.Success(response.ocs?.meta?.statusCode!!)
} catch (exception: Exception) {
_markConversationAsSensitiveResult.value =
MarkConversationAsSensitiveViewState.Error(exception)
}
}
}
@Suppress("Detekt.TooGenericExceptionCaught")
fun markConversationAsInsensitive(credentials: String, baseUrl: String, roomToken: String) {
viewModelScope.launch {
try {
val response = conversationsRepository.markConversationAsInsensitive(credentials, baseUrl, roomToken)
_markConversationAsInsensitiveResult.value =
MarkConversationAsInsensitiveViewState.Success(response.ocs?.meta?.statusCode!!)
} catch (exception: Exception) {
_markConversationAsInsensitiveResult.value =
MarkConversationAsInsensitiveViewState.Error(exception)
}
}
}
inner class GetRoomObserver : Observer<ConversationModel> {
override fun onSubscribe(d: Disposable) {
// unused atm
@ -490,18 +386,6 @@ class ConversationInfoViewModel @Inject constructor(
data class Error(val exception: Exception) : ClearChatHistoryViewState()
}
sealed class MarkConversationAsSensitiveViewState {
data object None : MarkConversationAsSensitiveViewState()
data class Success(val statusCode: Int) : MarkConversationAsSensitiveViewState()
data class Error(val exception: Exception) : MarkConversationAsSensitiveViewState()
}
sealed class MarkConversationAsInsensitiveViewState {
data object None : MarkConversationAsInsensitiveViewState()
data class Success(val statusCode: Int) : MarkConversationAsInsensitiveViewState()
data class Error(val exception: Exception) : MarkConversationAsInsensitiveViewState()
}
sealed class SetConversationReadOnlyViewState {
data object None : SetConversationReadOnlyViewState()
data object Success : SetConversationReadOnlyViewState()
@ -525,16 +409,4 @@ class ConversationInfoViewModel @Inject constructor(
data object Success : PasswordUiState()
data class Error(val exception: Exception) : PasswordUiState()
}
sealed class MarkConversationAsImportantViewState {
data object None : MarkConversationAsImportantViewState()
data class Success(val statusCode: Int) : MarkConversationAsImportantViewState()
data class Error(val exception: Exception) : MarkConversationAsImportantViewState()
}
sealed class MarkConversationAsUnimportantViewState {
data object None : MarkConversationAsUnimportantViewState()
data class Success(val statusCode: Int) : MarkConversationAsUnimportantViewState()
data class Error(val exception: Exception) : MarkConversationAsUnimportantViewState()
}
}

View File

@ -16,6 +16,7 @@ import android.animation.AnimatorInflater
import android.annotation.SuppressLint
import android.app.SearchManager
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
@ -40,12 +41,9 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SearchView
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.core.os.bundleOf
import androidx.core.graphics.drawable.toDrawable
import androidx.core.net.toUri
import androidx.core.os.bundleOf
import androidx.core.view.MenuItemCompat
import androidx.core.view.isVisible
import androidx.fragment.app.DialogFragment
@ -116,9 +114,6 @@ import com.nextcloud.talk.ui.dialog.ChooseAccountShareToDialogFragment
import com.nextcloud.talk.ui.dialog.ContextChatCompose
import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog
import com.nextcloud.talk.ui.dialog.FilterConversationFragment
import com.nextcloud.talk.ui.dialog.FilterConversationFragment.Companion.ARCHIVE
import com.nextcloud.talk.ui.dialog.FilterConversationFragment.Companion.MENTION
import com.nextcloud.talk.ui.dialog.FilterConversationFragment.Companion.UNREAD
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.BrandingUtils
@ -213,7 +208,7 @@ class ConversationsListActivity :
private var adapter: FlexibleAdapter<AbstractFlexibleItem<*>>? = null
private var conversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private var conversationItemsWithHeader: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private var searchableConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private val searchableConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private var filterableConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private var nearFutureEventConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private var searchItem: MenuItem? = null
@ -237,9 +232,9 @@ class ConversationsListActivity :
private var searchViewDisposable: Disposable? = null
private var filterState =
mutableMapOf(
MENTION to false,
UNREAD to false,
ARCHIVE to false,
FilterConversationFragment.MENTION to false,
FilterConversationFragment.UNREAD to false,
FilterConversationFragment.ARCHIVE to false,
FilterConversationFragment.DEFAULT to true
)
val searchBehaviorSubject = BehaviorSubject.createDefault(false)
@ -411,10 +406,6 @@ class ConversationsListActivity :
conversationsListViewModel.getRoomsFlow
.onEach { list ->
setConversationList(list)
val noteToSelf = list
.firstOrNull { ConversationUtils.isNoteToSelfConversation(it) }
val isNoteToSelfAvailable = noteToSelf != null
handleNoteToSelfShortcut(isNoteToSelfAvailable, noteToSelf?.token ?: "")
}.collect()
}
@ -467,13 +458,7 @@ class ConversationsListActivity :
userItems.add(contactItem)
}
val list = searchableConversationItems.filter {
it !is ContactItem
}.toMutableList()
list.addAll(userItems)
searchableConversationItems = list
searchableConversationItems.addAll(userItems)
}
else -> {}
@ -531,29 +516,6 @@ class ConversationsListActivity :
}
}
private fun handleNoteToSelfShortcut(noteToSelfAvailable: Boolean, noteToSelfToken: String) {
if (noteToSelfAvailable) {
val bundle = Bundle()
bundle.putString(KEY_ROOM_TOKEN, noteToSelfToken)
bundle.putBoolean(BundleKeys.KEY_FOCUS_INPUT, true)
val intent = Intent(context, ChatActivity::class.java)
intent.putExtras(bundle)
intent.action = Intent.ACTION_VIEW
val openNotesString = resources.getString(R.string.open_notes)
val shortcut = ShortcutInfoCompat.Builder(context, NOTE_TO_SELF_SHORTCUT_ID)
.setShortLabel(openNotesString)
.setLongLabel(openNotesString)
.setIcon(IconCompat.createWithResource(context, R.drawable.ic_pencil_grey600_24dp))
.setIntent(intent)
.build()
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut)
} else {
ShortcutManagerCompat.removeDynamicShortcuts(context, listOf(NOTE_TO_SELF_SHORTCUT_ID))
}
}
private fun setConversationList(list: List<ConversationModel>) {
// Update Conversations
conversationItems.clear()
@ -561,29 +523,26 @@ class ConversationsListActivity :
nearFutureEventConversationItems.clear()
for (conversation in list) {
if (!isFutureEvent(conversation) && !conversation.hasArchived) {
if (!futureEvent(conversation)) {
addToNearFutureEventConversationItems(conversation)
}
addToConversationItems(conversation)
}
getFilterStates()
val noFiltersActive = !(
filterState[MENTION] == true ||
filterState[UNREAD] == true ||
filterState[ARCHIVE] == true
)
sortConversations(conversationItems)
sortConversations(conversationItemsWithHeader)
sortConversations(nearFutureEventConversationItems)
if (noFiltersActive && searchBehaviorSubject.value == false) {
if (!hasFilterEnabled() && searchBehaviorSubject.value == false) {
adapter?.updateDataSet(nearFutureEventConversationItems, false)
} else {
applyFilter()
// Filter Conversations
if (!hasFilterEnabled()) {
filterableConversationItems = conversationItems
}
filterConversation()
adapter?.updateDataSet(filterableConversationItems, false)
}
Handler().postDelayed({ checkToShowUnreadBubble() }, UNREAD_BUBBLE_DELAY.toLong())
// Fetch Open Conversations
@ -592,14 +551,9 @@ class ConversationsListActivity :
intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1)
)
fetchOpenConversations(apiVersion)
}
fun applyFilter() {
if (!hasFilterEnabled()) {
filterableConversationItems = conversationItems
}
filterConversation()
adapter?.updateDataSet(filterableConversationItems, false)
// Get users
fetchUsers()
}
private fun hasFilterEnabled(): Boolean {
@ -610,14 +564,13 @@ class ConversationsListActivity :
return false
}
private fun isFutureEvent(conversation: ConversationModel): Boolean {
private fun futureEvent(conversation: ConversationModel): Boolean {
if (!conversation.objectId.contains("#")) {
return false
}
val eventTimeStart = conversation.objectId.split("#")[0].toLong()
val currentTimeStampInSeconds = System.currentTimeMillis() / LONG_1000
val sixteenHoursAfterTimeStamp = (eventTimeStart - currentTimeStampInSeconds) > SIXTEEN_HOURS_IN_SECONDS
return conversation.objectType == ConversationEnums.ObjectType.EVENT && sixteenHoursAfterTimeStamp
return conversation.objectType == ConversationEnums.ObjectType.EVENT &&
(conversation.objectId.split("#")[0].toLong() - (System.currentTimeMillis() / LONG_1000)) >
AGE_THRESHOLD_FOR_EVENT_CONVERSATIONS
}
fun showOnlyNearFutureEvents() {
@ -631,35 +584,32 @@ class ConversationsListActivity :
nearFutureEventConversationItems.add(conversationItem)
}
fun getFilterStates() {
val accountId = UserIdUtils.getIdForUser(currentUser)
filterState[UNREAD] = (
arbitraryStorageManager.getStorageSetting(
accountId,
UNREAD,
""
).blockingGet()?.value ?: ""
) == "true"
filterState[MENTION] = (
arbitraryStorageManager.getStorageSetting(
accountId,
MENTION,
""
).blockingGet()?.value ?: ""
) == "true"
filterState[ARCHIVE] = (
arbitraryStorageManager.getStorageSetting(
accountId,
ARCHIVE,
""
).blockingGet()?.value ?: ""
) == "true"
}
fun filterConversation() {
getFilterStates()
val accountId = UserIdUtils.getIdForUser(currentUser)
filterState[FilterConversationFragment.UNREAD] = (
arbitraryStorageManager.getStorageSetting(
accountId,
FilterConversationFragment.UNREAD,
""
).blockingGet()?.value ?: ""
) == "true"
filterState[FilterConversationFragment.MENTION] = (
arbitraryStorageManager.getStorageSetting(
accountId,
FilterConversationFragment.MENTION,
""
).blockingGet()?.value ?: ""
) == "true"
filterState[FilterConversationFragment.ARCHIVE] = (
arbitraryStorageManager.getStorageSetting(
accountId,
FilterConversationFragment.ARCHIVE,
""
).blockingGet()?.value ?: ""
) == "true"
val newItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
val items = conversationItems
for (i in items) {
@ -669,7 +619,7 @@ class ConversationsListActivity :
}
}
val archiveFilterOn = filterState[ARCHIVE] == true
val archiveFilterOn = filterState[FilterConversationFragment.ARCHIVE] ?: false
if (archiveFilterOn && newItems.isEmpty()) {
binding.noArchivedConversationLayout.visibility = View.VISIBLE
} else {
@ -691,7 +641,7 @@ class ConversationsListActivity :
for ((k, v) in filterState) {
if (v) {
when (k) {
MENTION -> result = (result && conversation.unreadMention) ||
FilterConversationFragment.MENTION -> result = (result && conversation.unreadMention) ||
(
result &&
(
@ -701,10 +651,10 @@ class ConversationsListActivity :
(conversation.unreadMessages > 0)
)
UNREAD -> result = result && (conversation.unreadMessages > 0)
FilterConversationFragment.UNREAD -> result = result && (conversation.unreadMessages > 0)
FilterConversationFragment.DEFAULT -> {
result = if (filterState[ARCHIVE] == true) {
result = if (filterState[FilterConversationFragment.ARCHIVE] == true) {
result && conversation.hasArchived
} else {
result && !conversation.hasArchived
@ -784,7 +734,7 @@ class ConversationsListActivity :
}
private fun initSearchView() {
val searchManager = getSystemService(SEARCH_SERVICE) as SearchManager?
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager?
if (searchItem != null) {
searchView = MenuItemCompat.getActionView(searchItem) as SearchView
viewThemeUtils.talk.themeSearchView(searchView!!)
@ -1200,8 +1150,8 @@ class ConversationsListActivity :
}
}
private fun fetchUsers(query: String = "") {
contactsViewModel.getContactsFromSearchParams(query)
private fun fetchUsers() {
contactsViewModel.getContactsFromSearchParams()
}
private fun handleHttpExceptions(throwable: Throwable) {
@ -1246,7 +1196,7 @@ class ConversationsListActivity :
})
binding.recyclerView.setOnTouchListener { v: View, _: MotionEvent? ->
if (!isDestroyed) {
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
}
false
@ -1399,9 +1349,6 @@ class ConversationsListActivity :
private fun performFilterAndSearch(filter: String?) {
if (filter!!.length >= SEARCH_MIN_CHARS) {
clearMessageSearchResults()
binding.noArchivedConversationLayout.visibility = View.GONE
fetchUsers(filter)
if (hasFilterEnabled()) {
adapter?.updateDataSet(conversationItems)
@ -1409,7 +1356,6 @@ class ConversationsListActivity :
adapter?.filterItems()
adapter?.updateDataSet(filterableConversationItems)
} else {
adapter?.updateDataSet(searchableConversationItems)
adapter?.setFilter(filter)
adapter?.filterItems()
}
@ -1424,15 +1370,8 @@ class ConversationsListActivity :
private fun resetSearchResults() {
clearMessageSearchResults()
adapter?.updateDataSet(conversationItems)
adapter?.setFilter("")
adapter?.filterItems()
val archiveFilterOn = filterState[ARCHIVE] == true
if (archiveFilterOn && adapter!!.isEmpty) {
binding.noArchivedConversationLayout.visibility = View.VISIBLE
} else {
binding.noArchivedConversationLayout.visibility = View.GONE
}
}
private fun clearMessageSearchResults() {
@ -1441,7 +1380,6 @@ class ConversationsListActivity :
adapter?.removeSection(firstHeader)
} else {
adapter?.removeItemsOfType(MessageResultItem.VIEW_TYPE)
adapter?.removeItemsOfType(MessagesTextHeaderItem.VIEW_TYPE)
}
adapter?.removeItemsOfType(LoadMoreResultsItem.VIEW_TYPE)
}
@ -1838,7 +1776,7 @@ class ConversationsListActivity :
val callsChannelNotEnabled = !NotificationUtils.isCallsNotificationChannelEnabled(this)
val serverNotificationAppInstalled =
currentUser?.capabilities?.notificationsCapability?.features?.isNotEmpty() == true
currentUser?.capabilities?.notificationsCapability?.features?.isNotEmpty() ?: false
val settingsOfUserAreWrong = notificationPermissionNotGranted ||
batteryOptimizationNotIgnored ||
@ -2139,25 +2077,29 @@ class ConversationsListActivity :
val entries = results.messages
if (entries.isNotEmpty()) {
val adapterItems: MutableList<AbstractFlexibleItem<*>> = ArrayList(entries.size + 1)
for (i in entries.indices) {
val showHeader = i == 0
adapterItems.add(
MessageResultItem(
context,
currentUser!!,
entries[i],
showHeader,
viewThemeUtils = viewThemeUtils
)
)
}
if (results.hasMore) {
adapterItems.add(LoadMoreResultsItem)
}
adapter?.addItems(Int.MAX_VALUE, adapterItems)
val pos = adapter?.currentItems?.indexOfFirst {
it is MessageResultItem
}
val item = (adapter?.currentItems?.get(pos!!) as MessageResultItem).apply { showHeader = true }
adapter?.addItem(pos!!, item)
adapter?.notifyItemInserted(pos!!)
adapter?.removeItem(pos!! - 1)
adapter?.notifyItemRemoved(pos!! - 1)
binding.recyclerView.scrollToPosition(0)
}
}
@ -2170,8 +2112,8 @@ class ConversationsListActivity :
}
fun updateFilterState(mention: Boolean, unread: Boolean) {
filterState[MENTION] = mention
filterState[UNREAD] = unread
filterState[FilterConversationFragment.MENTION] = mention
filterState[FilterConversationFragment.UNREAD] = unread
}
fun setFilterableItems(items: MutableList<AbstractFlexibleItem<*>>) {
@ -2210,8 +2152,7 @@ class ConversationsListActivity :
const val NOTIFICATION_WARNING_DATE_NOT_SET = 0L
const val OFFSET_HEIGHT_DIVIDER: Int = 3
const val ROOM_TYPE_ONE_ONE = "1"
private const val SIXTEEN_HOURS_IN_SECONDS: Long = 57600
private const val AGE_THRESHOLD_FOR_EVENT_CONVERSATIONS: Long = 57600
const val LONG_1000: Long = 1000
private const val NOTE_TO_SELF_SHORTCUT_ID = "NOTE_TO_SELF_SHORTCUT_ID"
}
}

View File

@ -50,18 +50,6 @@ interface ChatMessagesDao {
)
fun getTempMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>>
@Query(
"""
SELECT *
FROM ChatMessages
WHERE internalConversationId = :internalConversationId
AND isTemporary = 1
AND sendStatus != 'SENT_PENDING_ACK'
ORDER BY timestamp DESC, id DESC
"""
)
fun getTempUnsentMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>>
@Query(
"""
SELECT *

View File

@ -68,7 +68,7 @@ fun ChatMessageEntity.asModel() =
isDeleted = deleted,
referenceId = referenceId,
isTemporary = isTemporary,
sendStatus = sendStatus,
sendingFailed = sendingFailed,
readStatus = ReadStatus.NONE,
silent = silent
)

View File

@ -61,9 +61,7 @@ fun ConversationModel.asEntity() =
recordingConsentRequired = recordingConsentRequired,
remoteServer = remoteServer,
remoteToken = remoteToken,
hasArchived = hasArchived,
hasSensitive = hasSensitive,
hasImportant = hasImportant
hasArchived = hasArchived
)
fun ConversationEntity.asModel() =
@ -115,9 +113,7 @@ fun ConversationEntity.asModel() =
recordingConsentRequired = recordingConsentRequired,
remoteServer = remoteServer,
remoteToken = remoteToken,
hasArchived = hasArchived,
hasSensitive = hasSensitive,
hasImportant = hasImportant
hasArchived = hasArchived
)
fun Conversation.asEntity(accountId: Long) =
@ -168,7 +164,5 @@ fun Conversation.asEntity(accountId: Long) =
recordingConsentRequired = recordingConsentRequired,
remoteServer = remoteServer,
remoteToken = remoteToken,
hasArchived = hasArchived,
hasSensitive = hasSensitive,
hasImportant = hasImportant
hasArchived = hasArchived
)

View File

@ -64,7 +64,7 @@ data class ChatMessageEntity(
@ColumnInfo(name = "reactions") var reactions: LinkedHashMap<String, Int>? = null,
@ColumnInfo(name = "reactionsSelf") var reactionsSelf: ArrayList<String>? = null,
@ColumnInfo(name = "referenceId") var referenceId: String? = null,
@ColumnInfo(name = "sendStatus") var sendStatus: SendStatus? = null,
@ColumnInfo(name = "sendingFailed") var sendingFailed: Boolean = false,
@ColumnInfo(name = "silent") var silent: Boolean = false,
@ColumnInfo(name = "systemMessage") var systemMessageType: ChatMessage.SystemMessageType,
@ColumnInfo(name = "timestamp") var timestamp: Long = 0

View File

@ -94,9 +94,7 @@ data class ConversationEntity(
@ColumnInfo(name = "unreadMention") var unreadMention: Boolean = false,
@ColumnInfo(name = "unreadMentionDirect") var unreadMentionDirect: Boolean,
@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 = "hasArchived") var hasArchived: Boolean = false
// missing/not needed: attendeeId
// missing/not needed: attendeePin
// missing/not needed: attendeePermissions

View File

@ -1,14 +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.data.database.model
enum class SendStatus {
PENDING,
SENT_PENDING_ACK,
FAILED
}

View File

@ -1,31 +1,18 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024-2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-FileCopyrightText: 2022 Andy Scherzinger <info@andy-scherzinger.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.data.source.local
import android.util.Log
import androidx.room.DeleteColumn
import androidx.room.migration.AutoMigrationSpec
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import java.sql.SQLException
@Suppress("MagicNumber")
object Migrations {
//region Auto migrations
@DeleteColumn(tableName = "ChatMessages", columnName = "sendingFailed")
class AutoMigration16To17 : AutoMigrationSpec
//endregion
//region Manual migrations
val MIGRATION_6_8 = object : Migration(6, 8) {
override fun migrate(db: SupportSQLiteDatabase) {
Log.i("Migrations", "Migrating 6 to 8")
@ -75,22 +62,6 @@ object Migrations {
}
}
val MIGRATION_14_15 = object : Migration(14, 15) {
override fun migrate(db: SupportSQLiteDatabase) {
Log.i("Migrations", "Migrating 14 to 15")
addIsSensitive(db)
}
}
val MIGRATION_15_16 = object : Migration(15, 16) {
override fun migrate(db: SupportSQLiteDatabase) {
Log.i("Migrations", "Migrating 15 to 16")
addIsImportant(db)
}
}
//endregion
fun migrateToRoom(db: SupportSQLiteDatabase) {
db.execSQL(
"CREATE TABLE User_new (" +
@ -312,28 +283,6 @@ object Migrations {
}
}
fun addIsSensitive(db: SupportSQLiteDatabase) {
try {
db.execSQL(
"ALTER TABLE Conversations " +
"ADD COLUMN hasSensitive INTEGER NOT NULL DEFAULT 0;"
)
} catch (e: SQLException) {
Log.i("Migrations", "Something went wrong when adding column hasSensitive to table Conversations")
}
}
fun addIsImportant(db: SupportSQLiteDatabase) {
try {
db.execSQL(
"ALTER TABLE Conversations " +
"ADD COLUMN hasImportant INTEGER NOT NULL DEFAULT 0;"
)
} catch (e: SQLException) {
Log.i("Migrations", "Something went wrong when adding column hasImportant to table Conversations")
}
}
fun addTempMessagesSupport(db: SupportSQLiteDatabase) {
try {
db.execSQL(

View File

@ -1,7 +1,7 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023-2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-FileCopyrightText: 2023-2024 Marcel Hibbe <dev@mhibbe.de>
* SPDX-FileCopyrightText: 2022 Andy Scherzinger <info@andy-scherzinger.de>
* SPDX-FileCopyrightText: 2017-2020 Mario Danic <mario@lovelyhq.com>
* SPDX-License-Identifier: GPL-3.0-or-later
@ -23,14 +23,12 @@ import com.nextcloud.talk.data.database.dao.ConversationsDao
import com.nextcloud.talk.data.database.model.ChatBlockEntity
import com.nextcloud.talk.data.database.model.ChatMessageEntity
import com.nextcloud.talk.data.database.model.ConversationEntity
import com.nextcloud.talk.data.source.local.Migrations.AutoMigration16To17
import com.nextcloud.talk.data.source.local.converters.ArrayListConverter
import com.nextcloud.talk.data.source.local.converters.CapabilitiesConverter
import com.nextcloud.talk.data.source.local.converters.ExternalSignalingServerConverter
import com.nextcloud.talk.data.source.local.converters.HashMapHashMapConverter
import com.nextcloud.talk.data.source.local.converters.LinkedHashMapConverter
import com.nextcloud.talk.data.source.local.converters.PushConfigurationConverter
import com.nextcloud.talk.data.source.local.converters.SendStatusConverter
import com.nextcloud.talk.data.source.local.converters.ServerVersionConverter
import com.nextcloud.talk.data.source.local.converters.SignalingSettingsConverter
import com.nextcloud.talk.data.storage.ArbitraryStoragesDao
@ -51,10 +49,9 @@ import java.util.Locale
ChatMessageEntity::class,
ChatBlockEntity::class
],
version = 17,
version = 14,
autoMigrations = [
AutoMigration(from = 9, to = 10),
AutoMigration(from = 16, to = 17, spec = AutoMigration16To17::class)
AutoMigration(from = 9, to = 10)
],
exportSchema = true
)
@ -66,8 +63,7 @@ import java.util.Locale
SignalingSettingsConverter::class,
HashMapHashMapConverter::class,
LinkedHashMapConverter::class,
ArrayListConverter::class,
SendStatusConverter::class
ArrayListConverter::class
)
abstract class TalkDatabase : RoomDatabase() {
@ -120,9 +116,7 @@ abstract class TalkDatabase : RoomDatabase() {
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_13_14
)
.allowMainThreadQueries()
.addCallback(

View File

@ -1,23 +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.data.source.local.converters
import androidx.room.TypeConverter
import com.nextcloud.talk.data.database.model.SendStatus
class SendStatusConverter {
@TypeConverter
fun fromStatus(value: SendStatus): String {
return value.name
}
@TypeConverter
fun toStatus(value: String): SendStatus {
return SendStatus.valueOf(value)
}
}

View File

@ -190,7 +190,6 @@ class FullScreenMediaActivity : AppCompatActivity() {
supportActionBar?.show()
}
@OptIn(UnstableApi::class)
private fun applyWindowInsets() {
val playerView = binding.playerView
val exoControls = playerView.findViewById<FrameLayout>(R.id.exo_bottom_bar)

View File

@ -61,8 +61,6 @@ class ConversationModel(
var remoteServer: String? = null,
var remoteToken: String? = null,
var hasArchived: Boolean = false,
var hasSensitive: Boolean = false,
var hasImportant: Boolean = false,
// attributes that don't come from API. This should be changed?!
var password: String? = null
@ -127,9 +125,7 @@ class ConversationModel(
recordingConsentRequired = conversation.recordingConsentRequired,
remoteServer = conversation.remoteServer,
remoteToken = conversation.remoteToken,
hasArchived = conversation.hasArchived,
hasSensitive = conversation.hasSensitive,
hasImportant = conversation.hasImportant
hasArchived = conversation.hasArchived
)
}
}

View File

@ -21,7 +21,7 @@ class ChatUtils {
return message
}
@Suppress("Detekt.ComplexMethod", "Detekt.ComplexCondition")
@Suppress("Detekt.ComplexMethod")
private fun parse(messageParameters: HashMap<String?, HashMap<String?, String?>>, message: String?): String? {
var resultMessage = message
for (key in messageParameters.keys) {
@ -29,9 +29,7 @@ class ChatUtils {
if (individualHashMap != null) {
val type = individualHashMap["type"]
resultMessage = if (type == "user" || type == "guest" || type == "call" || type == "email" ||
type == "user-group" || type == "circle"
) {
resultMessage = if (type == "user" || type == "guest" || type == "call" || type == "email") {
resultMessage?.replace("{$key}", "@" + individualHashMap["name"])
} else if (type == "geo-location") {
individualHashMap["name"]

View File

@ -165,11 +165,5 @@ data class Conversation(
var remoteToken: String? = "",
@JsonField(name = ["isArchived"])
var hasArchived: Boolean = false,
@JsonField(name = ["isSensitive"])
var hasSensitive: Boolean = false,
@JsonField(name = ["isImportant"])
var hasImportant: Boolean = false
var hasArchived: Boolean = false
) : Parcelable

View File

@ -44,9 +44,6 @@ class ConversationEnums {
SHARE_PASSWORD,
FILE,
ROOM,
EVENT,
PHONE_TEMPORARY,
PHONE_PERSIST,
INSTANT_MEETING
EVENT
}
}

View File

@ -16,9 +16,6 @@ class ConversationObjectTypeConverter : StringBasedTypeConverter<ConversationEnu
"room" -> ConversationEnums.ObjectType.ROOM
"file" -> ConversationEnums.ObjectType.FILE
"event" -> ConversationEnums.ObjectType.EVENT
"phone_persist" -> ConversationEnums.ObjectType.PHONE_PERSIST
"phone_temporary" -> ConversationEnums.ObjectType.PHONE_TEMPORARY
"instant_meeting" -> ConversationEnums.ObjectType.INSTANT_MEETING
else -> ConversationEnums.ObjectType.DEFAULT
}
}
@ -33,9 +30,6 @@ class ConversationObjectTypeConverter : StringBasedTypeConverter<ConversationEnu
ConversationEnums.ObjectType.ROOM -> "room"
ConversationEnums.ObjectType.FILE -> "file"
ConversationEnums.ObjectType.EVENT -> "event"
ConversationEnums.ObjectType.PHONE_PERSIST -> "phone_persist"
ConversationEnums.ObjectType.PHONE_TEMPORARY -> "phone_temporary"
ConversationEnums.ObjectType.INSTANT_MEETING -> "instant_meeting"
else -> ""
}
}

View File

@ -1,29 +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.json.profile
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class CoreProfileAction(
@JsonField(name = ["id"])
var id: String? = null,
@JsonField(name = ["icon"])
var icon: String? = null,
@JsonField(name = ["title"])
var title: String? = null,
@JsonField(name = ["target"])
var target: String? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null, null, null)
}

View File

@ -1,30 +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.json.profile
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class Profile(
@JsonField(name = ["userId"]) var userId: String? = null,
@JsonField(name = ["address"]) var address: String? = null,
@JsonField(name = ["biography"]) var biography: Int? = null,
@JsonField(name = ["displayname"]) var displayName: Int? = null,
@JsonField(name = ["headline"]) var headline: String? = null,
// @JsonField(name = ["isUserAvatarVisible"]) var isUserAvatarVisible: Boolean = false,
@JsonField(name = ["organisation"]) var company: String? = null,
@JsonField(name = ["pronouns"]) var pronouns: String? = null,
@JsonField(name = ["role"]) var role: String? = null,
@JsonField(name = ["actions"]) var actions: List<CoreProfileAction>? = null,
@JsonField(name = ["timezone"]) var timezone: String? = null,
@JsonField(name = ["timezoneOffset"]) var timezoneOffset: Int? = null
) : Parcelable

View File

@ -1,26 +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.json.profile
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 ProfileOCS(
@JsonField(name = ["meta"])
var meta: GenericMeta? = null,
@JsonField(name = ["data"])
var data: Profile? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
}

View File

@ -1,23 +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.json.profile
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class ProfileOverall(
@JsonField(name = ["ocs"])
var ocs: ProfileOCS? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null)
}

View File

@ -6,17 +6,14 @@
*/
package com.nextcloud.talk.receivers
import android.Manifest
import android.app.Notification
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.Person
@ -165,15 +162,9 @@ class DirectReplyReceiver : BroadcastReceiver() {
// Set the updated style
previousBuilder.setStyle(previousStyle)
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
) {
// Check if notification still exists
if (findActiveNotification(systemNotificationId!!) != null) {
NotificationManagerCompat.from(context).notify(systemNotificationId!!, previousBuilder.build())
}
// Check if notification still exists
if (findActiveNotification(systemNotificationId!!) != null) {
NotificationManagerCompat.from(context).notify(systemNotificationId!!, previousBuilder.build())
}
}
.subscribeOn(Schedulers.io())

View File

@ -11,7 +11,6 @@ import com.nextcloud.talk.conversationinfo.CreateRoomRequest
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.models.json.profile.Profile
import io.reactivex.Observable
interface ConversationsRepository {
@ -47,14 +46,4 @@ interface ConversationsRepository {
suspend fun clearChatHistory(apiVersion: Int, roomToken: String): GenericOverall
suspend fun createRoom(credentials: String, url: String, body: CreateRoomRequest): RoomOverall
suspend fun getProfile(credentials: String, url: String): Profile?
suspend fun markConversationAsSensitive(credentials: String, baseUrl: String, roomToken: String): GenericOverall
suspend fun markConversationAsInsensitive(credentials: String, baseUrl: String, roomToken: String): GenericOverall
suspend fun markConversationAsImportant(credentials: String, baseUrl: String, roomToken: String): GenericOverall
suspend fun markConversationAsUnImportant(credentials: String, baseUrl: String, roomToken: String): GenericOverall
}

View File

@ -14,7 +14,6 @@ import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.TalkBan
import com.nextcloud.talk.models.json.profile.Profile
import com.nextcloud.talk.repositories.conversations.ConversationsRepository.ResendInvitationsResult
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
@ -117,46 +116,6 @@ class ConversationsRepositoryImpl(
return response
}
override suspend fun getProfile(credentials: String, url: String): Profile? {
return coroutineApi.getProfile(credentials, url).ocs?.data
}
override suspend fun markConversationAsSensitive(
credentials: String,
baseUrl: String,
roomToken: String
): GenericOverall {
val url = ApiUtils.getUrlForSensitiveConversation(baseUrl, roomToken)
return coroutineApi.markConversationAsSensitive(credentials, url)
}
override suspend fun markConversationAsInsensitive(
credentials: String,
baseUrl: String,
roomToken: String
): GenericOverall {
val url = ApiUtils.getUrlForSensitiveConversation(baseUrl, roomToken)
return coroutineApi.markConversationAsInsensitive(credentials, url)
}
override suspend fun markConversationAsImportant(
credentials: String,
baseUrl: String,
roomToken: String
): GenericOverall {
val url = ApiUtils.getUrlForImportantConversation(baseUrl, roomToken)
return coroutineApi.markConversationAsImportant(credentials, url)
}
override suspend fun markConversationAsUnImportant(
credentials: String,
baseUrl: String,
roomToken: String
): GenericOverall {
val url = ApiUtils.getUrlForImportantConversation(baseUrl, roomToken)
return coroutineApi.markConversationAsUnimportant(credentials, url)
}
override suspend fun banActor(
credentials: String,
url: String,

View File

@ -8,7 +8,6 @@
package com.nextcloud.talk.ui
import android.content.Context
import android.content.ContextWrapper
import android.util.Log
import android.view.View.TEXT_ALIGNMENT_VIEW_START
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
@ -24,7 +23,6 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
@ -48,7 +46,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@ -71,18 +68,15 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.graphics.ColorUtils
import androidx.emoji2.widget.EmojiTextView
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asFlow
import autodagger.AutoInjector
@ -90,8 +84,6 @@ import coil.compose.AsyncImage
import com.elyeproj.loaderviewlibrary.LoaderImageView
import com.elyeproj.loaderviewlibrary.LoaderTextView
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.adapters.messages.PreviewMessageViewHolder.Companion.KEY_MIMETYPE
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.chat.data.model.ChatMessage
@ -107,9 +99,7 @@ import com.nextcloud.talk.models.json.opengraph.Reference
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.DrawableUtils.getDrawableResourceIdForMimeType
import com.nextcloud.talk.utils.message.MessageUtils
import com.nextcloud.talk.utils.preview.ComposePreviewUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import org.osmdroid.config.Configuration
@ -126,53 +116,40 @@ import kotlin.random.Random
@Suppress("FunctionNaming", "TooManyFunctions", "LongMethod", "StaticFieldLeak", "LargeClass")
class ComposeChatAdapter(
private var messagesJson: List<ChatMessageJson>? = null,
private var messageId: String? = null,
private val utils: ComposePreviewUtils? = null
private var messageId: String? = null
) {
interface PreviewAble {
val viewThemeUtils: ViewThemeUtils
val messageUtils: MessageUtils
val contactsViewModel: ContactsViewModel
val chatViewModel: ChatViewModel
val context: Context
val userManager: UserManager
}
@AutoInjector(NextcloudTalkApplication::class)
inner class ComposeChatAdapterViewModel : ViewModel(), PreviewAble {
inner class ComposeChatAdapterViewModel : ViewModel() {
@Inject
override lateinit var viewThemeUtils: ViewThemeUtils
lateinit var viewThemeUtils: ViewThemeUtils
@Inject
override lateinit var messageUtils: MessageUtils
lateinit var messageUtils: MessageUtils
@Inject
override lateinit var contactsViewModel: ContactsViewModel
lateinit var contactsViewModel: ContactsViewModel
@Inject
override lateinit var chatViewModel: ChatViewModel
lateinit var chatViewModel: ChatViewModel
@Inject
override lateinit var context: Context
lateinit var context: Context
@Inject
override lateinit var userManager: UserManager
lateinit var userManager: UserManager
val items = mutableStateListOf<ChatMessage>()
init {
sharedApplication?.componentApplication?.inject(this)
sharedApplication!!.componentApplication.inject(this)
}
}
inner class ComposeChatAdapterPreviewViewModel(
override val viewThemeUtils: ViewThemeUtils,
override val messageUtils: MessageUtils,
override val contactsViewModel: ContactsViewModel,
override val chatViewModel: ChatViewModel,
override val context: Context,
override val userManager: UserManager
) : ViewModel(), PreviewAble
val currentUser: User = userManager.currentUser.blockingGet()
val colorScheme = viewThemeUtils.getColorScheme(context)
val highEmphasisColorInt = context.resources.getColor(R.color.high_emphasis_text, null)
}
companion object {
val TAG: String = ComposeChatAdapter::class.java.simpleName
@ -196,48 +173,21 @@ class ComposeChatAdapter(
private var incomingShape: RoundedCornerShape = RoundedCornerShape(2.dp, 20.dp, 20.dp, 20.dp)
private var outgoingShape: RoundedCornerShape = RoundedCornerShape(20.dp, 2.dp, 20.dp, 20.dp)
val viewModel: PreviewAble =
if (utils != null) {
ComposeChatAdapterPreviewViewModel(
utils.viewThemeUtils,
utils.messageUtils,
utils.contactsViewModel,
utils.chatViewModel,
utils.context,
utils.userManager
)
} else {
ComposeChatAdapterViewModel()
}
val items = mutableStateListOf<ChatMessage>()
val currentUser: User = viewModel.userManager.currentUser.blockingGet()
val colorScheme = viewModel.viewThemeUtils.getColorScheme(viewModel.context)
val highEmphasisColorInt = viewModel.context.resources.getColor(R.color.high_emphasis_text, null)
fun Context.findMainActivityOrNull(): MainActivity? {
var context = this
while (context is ContextWrapper) {
if (context is MainActivity) return context
context = context.baseContext
}
return null
}
private val viewModel = ComposeChatAdapterViewModel()
fun addMessages(messages: MutableList<ChatMessage>, append: Boolean) {
if (messages.isEmpty()) return
val processedMessages = messages.toMutableList()
if (items.isNotEmpty()) {
if (viewModel.items.isNotEmpty()) {
if (append) {
processedMessages.add(items.first())
processedMessages.add(viewModel.items.first())
} else {
processedMessages.add(items.last())
processedMessages.add(viewModel.items.last())
}
}
if (append) items.addAll(processedMessages) else items.addAll(0, processedMessages)
if (append) viewModel.items.addAll(processedMessages) else viewModel.items.addAll(0, processedMessages)
}
@OptIn(ExperimentalFoundationApi::class)
@ -252,7 +202,7 @@ class ComposeChatAdapter(
modifier = Modifier.padding(16.dp)
) {
stickyHeader {
if (items.size == 0) {
if (viewModel.items.size == 0) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
@ -261,11 +211,11 @@ class ComposeChatAdapter(
ShimmerGroup()
}
} else {
val timestamp = items[listState.firstVisibleItemIndex].timestamp
val timestamp = viewModel.items[listState.firstVisibleItemIndex].timestamp
val dateString = formatTime(timestamp * LONG_1000)
val color = Color(highEmphasisColorInt)
val color = Color(viewModel.highEmphasisColorInt)
val backgroundColor =
LocalContext.current.resources.getColor(R.color.bg_message_list_incoming_bubble, null)
viewModel.context.resources.getColor(R.color.bg_message_list_incoming_bubble, null)
Row(
horizontalArrangement = Arrangement.Absolute.Center,
verticalAlignment = Alignment.CenterVertically
@ -279,8 +229,8 @@ class ComposeChatAdapter(
.padding(8.dp)
.shadow(
16.dp,
spotColor = colorScheme.primary,
ambientColor = colorScheme.primary
spotColor = viewModel.colorScheme.primary,
ambientColor = viewModel.colorScheme.primary
)
.background(color = Color(backgroundColor), shape = RoundedCornerShape(8.dp))
.padding(8.dp)
@ -290,8 +240,8 @@ class ComposeChatAdapter(
}
}
items(items) { message ->
message.activeUser = currentUser
items(viewModel.items) { message ->
message.activeUser = viewModel.currentUser
when (val type = message.getCalculateMessageType()) {
ChatMessage.MessageType.SYSTEM_MESSAGE -> {
if (!message.shouldFilter()) {
@ -334,7 +284,7 @@ class ComposeChatAdapter(
}
}
if (messageId != null && items.size > 0) {
if (messageId != null && viewModel.items.size > 0) {
LaunchedEffect(Dispatchers.Main) {
delay(SCROLL_DELAY)
val pos = searchMessages(messageId!!)
@ -376,7 +326,7 @@ class ComposeChatAdapter(
}
private fun searchMessages(searchId: String): Int {
items.forEachIndexed { index, message ->
viewModel.items.forEachIndexed { index, message ->
if (message.id == searchId) return index
}
return -1
@ -430,18 +380,18 @@ class ComposeChatAdapter(
@Composable
(RowScope.() -> Unit)
) {
val incoming = message.actorId != currentUser.userId
val incoming = message.actorId != viewModel.currentUser.userId
val color = if (incoming) {
if (message.isDeleted) {
LocalContext.current.resources.getColor(R.color.bg_message_list_incoming_bubble_deleted, null)
viewModel.context.resources.getColor(R.color.bg_message_list_incoming_bubble_deleted, null)
} else {
LocalContext.current.resources.getColor(R.color.bg_message_list_incoming_bubble, null)
viewModel.context.resources.getColor(R.color.bg_message_list_incoming_bubble, null)
}
} else {
if (message.isDeleted) {
ColorUtils.setAlphaComponent(colorScheme.surfaceVariant.toArgb(), HALF_OPACITY)
ColorUtils.setAlphaComponent(viewModel.colorScheme.surfaceVariant.toArgb(), HALF_OPACITY)
} else {
colorScheme.surfaceVariant.toArgb()
viewModel.colorScheme.surfaceVariant.toArgb()
}
}
val shape = if (incoming) incomingShape else outgoingShape
@ -455,7 +405,7 @@ class ComposeChatAdapter(
if (incoming) {
val imageUri = message.actorId?.let { viewModel.contactsViewModel.getImageUri(it, true) }
val errorPlaceholderImage: Int = R.drawable.account_circle_96dp
val loadedImage = loadImage(imageUri, LocalContext.current, errorPlaceholderImage)
val loadedImage = loadImage(imageUri, viewModel.context, errorPlaceholderImage)
AsyncImage(
model = loadedImage,
contentDescription = stringResource(R.string.user_avatar),
@ -477,13 +427,13 @@ class ComposeChatAdapter(
color = Color(color),
shape = shape
) {
val timeString = DateUtils(LocalContext.current).getLocalTimeStringFromTimestamp(message.timestamp)
val timeString = DateUtils(viewModel.context).getLocalTimeStringFromTimestamp(message.timestamp)
val modifier = if (includePadding) Modifier.padding(8.dp, 4.dp, 8.dp, 4.dp) else Modifier
Column(modifier = modifier) {
if (message.parentMessageId != null && !message.isDeleted && messagesJson != null) {
messagesJson!!
.find { it.parentMessage?.id == message.parentMessageId }
?.parentMessage!!.asModel().let { CommonMessageQuote(LocalContext.current, it) }
?.parentMessage!!.asModel().let { CommonMessageQuote(viewModel.context, it) }
}
if (incoming) {
@ -520,8 +470,8 @@ class ComposeChatAdapter(
private fun Modifier.withCustomAnimation(incoming: Boolean): Modifier {
val infiniteTransition = rememberInfiniteTransition()
val borderColor by infiniteTransition.animateColor(
initialValue = colorScheme.primary,
targetValue = colorScheme.background,
initialValue = viewModel.colorScheme.primary,
targetValue = viewModel.colorScheme.background,
animationSpec = infiniteRepeatable(
animation = tween(ANIMATED_BLINK, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
@ -592,7 +542,7 @@ class ComposeChatAdapter(
LoaderTextView(ctx).apply {
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
val color = if (outgoing) {
colorScheme.primary.toArgb()
viewModel.colorScheme.primary.toArgb()
} else {
resources.getColor(R.color.nc_shimmer_default_color, null)
}
@ -612,7 +562,7 @@ class ComposeChatAdapter(
@Composable
private fun EnrichedText(message: ChatMessage) {
AndroidView(factory = { ctx ->
val incoming = message.actorId != currentUser.userId
val incoming = message.actorId != viewModel.currentUser.userId
var processedMessageText = viewModel.messageUtils.enrichChatMessageText(
ctx,
message,
@ -624,7 +574,7 @@ class ComposeChatAdapter(
ctx, viewModel.viewThemeUtils, processedMessageText!!, message, null
)
EmojiTextView(ctx).apply {
androidx.emoji2.widget.EmojiTextView(ctx).apply {
layoutParams = LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
setLineSpacing(0F, LINE_SPACING)
textAlignment = TEXT_ALIGNMENT_VIEW_START
@ -642,14 +592,14 @@ class ComposeChatAdapter(
}
@Composable
fun SystemMessage(message: ChatMessage) {
private fun SystemMessage(message: ChatMessage) {
val similarMessages = sharedApplication!!.resources.getQuantityString(
R.plurals.see_similar_system_messages,
message.expandableChildrenAmount,
message.expandableChildrenAmount
)
Column(horizontalAlignment = Alignment.CenterHorizontally) {
val timeString = DateUtils(LocalContext.current).getLocalTimeStringFromTimestamp(message.timestamp)
val timeString = DateUtils(viewModel.context).getLocalTimeStringFromTimestamp(message.timestamp)
Row(horizontalArrangement = Arrangement.Absolute.Center, verticalAlignment = Alignment.CenterVertically) {
Spacer(modifier = Modifier.weight(1f))
Text(
@ -682,7 +632,7 @@ class ComposeChatAdapter(
Text(
text,
fontSize = AUTHOR_TEXT_SIZE,
color = Color(highEmphasisColorInt)
color = Color(viewModel.highEmphasisColorInt)
)
}
}
@ -690,15 +640,14 @@ class ComposeChatAdapter(
@Composable
private fun ImageMessage(message: ChatMessage, state: MutableState<Boolean>) {
val hasCaption = (message.message != "{file}")
val incoming = message.actorId != currentUser.userId
val timeString = DateUtils(LocalContext.current).getLocalTimeStringFromTimestamp(message.timestamp)
val incoming = message.actorId != viewModel.currentUser.userId
val timeString = DateUtils(viewModel.context).getLocalTimeStringFromTimestamp(message.timestamp)
CommonMessageBody(message, includePadding = false, playAnimation = state.value) {
Column {
message.activeUser = currentUser
message.activeUser = viewModel.currentUser
val imageUri = message.imageUrl
val mimetype = message.selectedIndividualHashMap!![KEY_MIMETYPE]
val drawableResourceId = getDrawableResourceIdForMimeType(mimetype)
val loadedImage = load(imageUri, LocalContext.current, drawableResourceId)
val errorPlaceholderImage: Int = R.drawable.ic_mimetype_image
val loadedImage = load(imageUri, viewModel.context, errorPlaceholderImage)
AsyncImage(
model = loadedImage,
@ -768,8 +717,8 @@ class ComposeChatAdapter(
WaveformSeekBar(ctx).apply {
setWaveData(FloatArray(DEFAULT_WAVE_SIZE) { Random.nextFloat() }) // READ ONLY for now
setColors(
colorScheme.inversePrimary.toArgb(),
colorScheme.onPrimaryContainer.toArgb()
viewModel.colorScheme.inversePrimary.toArgb(),
viewModel.colorScheme.onPrimaryContainer.toArgb()
)
}
},
@ -844,8 +793,8 @@ class ComposeChatAdapter(
private fun LinkMessage(message: ChatMessage, state: MutableState<Boolean>) {
val color = colorResource(R.color.high_emphasis_text)
viewModel.chatViewModel.getOpenGraph(
currentUser.getCredentials(),
currentUser.baseUrl!!,
viewModel.currentUser.getCredentials(),
viewModel.currentUser.baseUrl!!,
message.extractedUrlToPreview!!
)
CommonMessageBody(message, playAnimation = state.value) {
@ -879,7 +828,7 @@ class ComposeChatAdapter(
it.link?.let { Text(it, fontSize = TIME_TEXT_SIZE) }
it.thumb?.let {
val errorPlaceholderImage: Int = R.drawable.ic_mimetype_image
val loadedImage = loadImage(it, LocalContext.current, errorPlaceholderImage)
val loadedImage = loadImage(it, viewModel.context, errorPlaceholderImage)
AsyncImage(
model = loadedImage,
contentDescription = stringResource(R.string.nc_sent_an_image),
@ -933,7 +882,7 @@ class ComposeChatAdapter(
if (cardName?.isNotEmpty() == true) {
val cardDescription = String.format(
LocalContext.current.resources.getString(R.string.deck_card_description),
viewModel.context.resources.getString(R.string.deck_card_description),
stackName,
boardName
)
@ -950,44 +899,3 @@ class ComposeChatAdapter(
}
}
}
@Preview(showBackground = true, widthDp = 380, heightDp = 800)
@Composable
fun AllMessageTypesPreview() {
val previewUtils = ComposePreviewUtils.getInstance(LocalContext.current)
val adapter = remember { ComposeChatAdapter(messagesJson = null, messageId = null, previewUtils) }
val sampleMessages = remember {
listOf(
// Text Messages
ChatMessage().apply {
jsonMessageId = 1
actorId = "user1"
message = "I love Nextcloud"
timestamp = System.currentTimeMillis()
actorDisplayName = "User1"
messageType = ChatMessage.MessageType.REGULAR_TEXT_MESSAGE.name
},
ChatMessage().apply {
jsonMessageId = 2
actorId = "user1_id"
message = "I love Nextcloud"
timestamp = System.currentTimeMillis()
actorDisplayName = "User2"
messageType = ChatMessage.MessageType.REGULAR_TEXT_MESSAGE.name
}
)
}
LaunchedEffect(sampleMessages) { // Use LaunchedEffect or similar to update state once
if (adapter.items.isEmpty()) { // Prevent adding multiple times on recomposition
adapter.addMessages(sampleMessages.toMutableList(), append = false) // Add messages
}
}
MaterialTheme(colorScheme = adapter.colorScheme) { // Use the (potentially faked) color scheme
Box(modifier = Modifier.fillMaxSize()) { // Provide a container
adapter.GetView() // Call the main Composable
}
}
}

View File

@ -7,16 +7,14 @@
package com.nextcloud.talk.ui.dialog
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.content.pm.ActivityInfo
import android.os.Bundle
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.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@ -44,7 +42,9 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@ -94,15 +94,6 @@ class ContextChatCompose(val bundle: Bundle) {
}
}
private fun Context.requireActivity(): Activity {
var context = this
while (context is ContextWrapper) {
if (context is Activity) return context
context = context.baseContext
}
throw IllegalStateException("No activity was present but it is required.")
}
@Composable
fun GetDialogView(
shouldDismiss: MutableState<Boolean>,
@ -110,11 +101,9 @@ class ContextChatCompose(val bundle: Bundle) {
contextViewModel: ContextChatComposeViewModel = ContextChatComposeViewModel()
) {
if (shouldDismiss.value) {
context.requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
return
}
context.requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
val colorScheme = contextViewModel.viewThemeUtils.getColorScheme(context)
MaterialTheme(colorScheme) {
Dialog(
@ -155,28 +144,28 @@ class ContextChatCompose(val bundle: Bundle) {
val name = bundle.getString(BundleKeys.KEY_CONVERSATION_NAME)!!
Text(name, fontSize = 24.sp)
}
// Spacer(modifier = Modifier.weight(1f))
// val cInt = context.resources.getColor(R.color.high_emphasis_text, null)
// Icon(
// painterResource(R.drawable.ic_call_black_24dp),
// "",
// tint = Color(cInt),
// modifier = Modifier
// .padding()
// .padding(end = 16.dp)
// .alpha(HALF_ALPHA)
// )
//
// Icon(
// painterResource(R.drawable.ic_baseline_videocam_24),
// "",
// tint = Color(cInt),
// modifier = Modifier
// .padding()
// .alpha(HALF_ALPHA)
// )
//
// ComposeChatMenu(colorScheme.background, false)
Spacer(modifier = Modifier.weight(1f))
val cInt = context.resources.getColor(R.color.high_emphasis_text, null)
Icon(
painterResource(R.drawable.ic_call_black_24dp),
"",
tint = Color(cInt),
modifier = Modifier
.padding()
.padding(end = 16.dp)
.alpha(HALF_ALPHA)
)
Icon(
painterResource(R.drawable.ic_baseline_videocam_24),
"",
tint = Color(cInt),
modifier = Modifier
.padding()
.alpha(HALF_ALPHA)
)
ComposeChatMenu(colorScheme.background, false)
}
if (shouldShow) {
Icon(

View File

@ -118,6 +118,7 @@ class ConversationsListBottomDialog(
currentUser.capabilities?.spreedCapability!!,
SpreedFeatures.FAVORITES
)
val canModerate = ConversationUtils.canModerate(conversation, currentUser.capabilities?.spreedCapability!!)
binding.conversationRemoveFromFavorites.visibility = setVisibleIf(
hasFavoritesCapability && conversation.favorite
@ -148,11 +149,14 @@ class ConversationsListBottomDialog(
)
binding.conversationOperationDelete.visibility = setVisibleIf(
conversation.canDeleteConversation
canModerate
)
binding.conversationOperationLeave.visibility = setVisibleIf(
conversation.canLeaveConversation
conversation.canLeaveConversation &&
// leaving is by api not possible for the last user with moderator permissions.
// for now, hide this option for all moderators.
!ConversationUtils.canModerate(conversation, currentUser.capabilities!!.spreedCapability!!)
)
}

View File

@ -18,7 +18,6 @@ import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.data.database.model.SendStatus
import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.databinding.DialogTempMessageActionsBinding
import com.nextcloud.talk.ui.theme.ViewThemeUtils
@ -59,10 +58,9 @@ class TempMessageActionsDialog(
private fun initMenuItems() {
this.lifecycleScope.launch {
val sendingFailed = message.sendStatus == SendStatus.FAILED
initResendMessage(sendingFailed && networkMonitor.isOnline.value)
initMenuEditMessage(sendingFailed || !networkMonitor.isOnline.value)
initMenuDeleteMessage(sendingFailed || !networkMonitor.isOnline.value)
initResendMessage(message.sendingFailed && networkMonitor.isOnline.value)
initMenuEditMessage(message.sendingFailed || !networkMonitor.isOnline.value)
initMenuDeleteMessage(message.sendingFailed || !networkMonitor.isOnline.value)
initMenuItemCopy()
}
}

View File

@ -205,10 +205,6 @@ object ApiUtils {
return getUrlForParticipants(version, baseUrl, token) + "/active"
}
fun getUrlForImportantConversation(baseUrl: String, roomToken: String): String {
return "$baseUrl$OCS_API_VERSION/apps/spreed/api/v4/room/$roomToken/important"
}
@JvmStatic
fun getUrlForParticipantsSelf(version: Int, baseUrl: String?, token: String?): String {
return getUrlForParticipants(version, baseUrl, token) + "/self"
@ -453,10 +449,6 @@ object ApiUtils {
return "$baseUrl$OCS_API_VERSION/cloud/users/search/by-phone"
}
fun getUrlForUnbindingRoom(baseUrl: String, roomToken: String): String {
return "$baseUrl/ocs/v2.php/apps/spreed/api/v4/room/$roomToken/object"
}
fun getUrlForFileUpload(baseUrl: String, user: String, remotePath: String): String {
return "$baseUrl/remote.php/dav/files/$user$remotePath"
}
@ -481,10 +473,6 @@ object ApiUtils {
return "$baseUrl$OCS_API_VERSION/apps/spreed/temp-user-avatar"
}
fun getUrlForSensitiveConversation(baseUrl: String, roomToken: String): String {
return "$baseUrl$OCS_API_VERSION/apps/spreed/api/v4/room/$roomToken/sensitive"
}
fun getUrlForUserFields(baseUrl: String): String {
return "$baseUrl$OCS_API_VERSION/cloud/user/fields"
}
@ -637,8 +625,4 @@ object ApiUtils {
fun getUrlForChatMessageContext(baseUrl: String, token: String, messageId: String): String {
return "$baseUrl$OCS_API_VERSION$SPREED_API_VERSION/chat/$token/$messageId/context"
}
fun getUrlForProfile(baseUrl: String, userId: String): String {
return "$baseUrl$OCS_API_VERSION/profile/$userId"
}
}

View File

@ -57,10 +57,7 @@ enum class SpreedFeatures(val value: String) {
BAN_V1("ban-v1"),
EDIT_MESSAGES_NOTE_TO_SELF("edit-messages-note-to-self"),
ARCHIVE_CONVERSATIONS("archived-conversations-v2"),
CONVERSATION_CREATION_ALL("conversation-creation-all"),
UNBIND_CONVERSATION("unbind-conversation"),
SENSITIVE_CONVERSATIONS("sensitive-conversations"),
IMPORTANT_CONVERSATIONS("important-conversations")
CONVERSATION_CREATION_ALL("conversation-creation-all")
}
@Suppress("TooManyFunctions")
@ -143,36 +140,6 @@ object CapabilitiesUtil {
return false
}
fun retentionOfEventRooms(spreedCapabilities: SpreedCapability): Int {
if (spreedCapabilities.config?.containsKey("conversations") == true) {
val map = spreedCapabilities.config!!["conversations"]
if (map?.containsKey("retention-event") == true) {
return map["retention-event"].toString().toInt()
}
}
return 0
}
fun retentionOfSIPRoom(spreedCapabilities: SpreedCapability): Int {
if (spreedCapabilities.config?.containsKey("conversations") == true) {
val map = spreedCapabilities.config!!["conversations"]
if (map?.containsKey("retention-phone") == true) {
return map["retention-phone"].toString().toInt()
}
}
return 0
}
fun retentionOfInstantMeetingRoom(spreedCapabilities: SpreedCapability): Int {
if (spreedCapabilities.config?.containsKey("conversations") == true) {
val map = spreedCapabilities.config!!["conversations"]
if (map?.containsKey("retention-instant-meetings") == true) {
return map["retention-instant-meetings"].toString().toInt()
}
}
return 0
}
@JvmStatic
fun isCallRecordingAvailable(spreedCapabilities: SpreedCapability): Boolean {
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.RECORDING_V1) &&

View File

@ -36,7 +36,6 @@ import com.nextcloud.talk.utils.Mimetype.AUDIO_MPEG
import com.nextcloud.talk.utils.Mimetype.AUDIO_OGG
import com.nextcloud.talk.utils.Mimetype.AUDIO_WAV
import com.nextcloud.talk.utils.Mimetype.IMAGE_GIF
import com.nextcloud.talk.utils.Mimetype.IMAGE_HEIC
import com.nextcloud.talk.utils.Mimetype.IMAGE_JPEG
import com.nextcloud.talk.utils.Mimetype.IMAGE_PNG
import com.nextcloud.talk.utils.Mimetype.TEXT_MARKDOWN
@ -156,8 +155,7 @@ class FileViewerUtils(private val context: Context, private val user: User) {
-> openMediaView(filename, mimetype)
IMAGE_PNG,
IMAGE_JPEG,
IMAGE_GIF,
IMAGE_HEIC
IMAGE_GIF
-> openImageView(filename, mimetype)
TEXT_MARKDOWN,
TEXT_PLAIN
@ -250,7 +248,6 @@ class FileViewerUtils(private val context: Context, private val user: User) {
return when (mimetype) {
IMAGE_PNG,
IMAGE_JPEG,
IMAGE_HEIC,
IMAGE_GIF,
AUDIO_MPEG,
AUDIO_WAV,

View File

@ -22,7 +22,6 @@ object Mimetype {
const val IMAGE_JPEG = "image/jpeg"
const val IMAGE_JPG = "image/jpg"
const val IMAGE_GIF = "image/gif"
const val IMAGE_HEIC = "image/heic"
const val VIDEO_MP4 = "video/mp4"
const val VIDEO_QUICKTIME = "video/quicktime"

View File

@ -81,5 +81,4 @@ object BundleKeys {
const val KEY_FIELD_MAP: String = "KEY_FIELD_MAP"
const val KEY_CHAT_URL: String = "KEY_CHAT_URL"
const val KEY_SCROLL_TO_NOTIFICATION_CATEGORY: String = "KEY_SCROLL_TO_NOTIFICATION_CATEGORY"
const val KEY_FOCUS_INPUT: String = "KEY_FOCUS_INPUT"
}

View File

@ -25,7 +25,6 @@ import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration
import io.noties.markwon.core.MarkwonTheme
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
import io.noties.markwon.ext.tables.TablePlugin
import io.noties.markwon.ext.tasklist.TaskListDrawable
import io.noties.markwon.ext.tasklist.TaskListPlugin
@ -196,7 +195,6 @@ class MessageUtils(val context: Context) {
}
})
.usePlugin(TaskListPlugin.create(drawable))
.usePlugin(TablePlugin.create { _ -> })
.usePlugin(StrikethroughPlugin.create()).build()
return markwon.toMarkdown(markdown)
}

View File

@ -1,188 +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.utils.preview
import android.content.Context
import com.github.aurae.retrofit2.LoganSquareConverterFactory
import com.nextcloud.android.common.ui.color.ColorUtil
import com.nextcloud.android.common.ui.theme.MaterialSchemes
import com.nextcloud.android.common.ui.theme.utils.AndroidViewThemeUtils
import com.nextcloud.android.common.ui.theme.utils.AndroidXViewThemeUtils
import com.nextcloud.android.common.ui.theme.utils.DialogViewThemeUtils
import com.nextcloud.android.common.ui.theme.utils.MaterialViewThemeUtils
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.api.NcApiCoroutines
import com.nextcloud.talk.chat.data.ChatMessageRepository
import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager
import com.nextcloud.talk.chat.data.io.MediaRecorderManager
import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository
import com.nextcloud.talk.chat.data.network.RetrofitChatNetwork
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
import com.nextcloud.talk.contacts.ContactsRepository
import com.nextcloud.talk.contacts.ContactsRepositoryImpl
import com.nextcloud.talk.contacts.ContactsViewModel
import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository
import com.nextcloud.talk.conversationlist.data.network.ConversationsNetworkDataSource
import com.nextcloud.talk.conversationlist.data.network.OfflineFirstConversationsRepository
import com.nextcloud.talk.conversationlist.data.network.RetrofitConversationsNetwork
import com.nextcloud.talk.data.database.dao.ChatBlocksDao
import com.nextcloud.talk.data.database.dao.ChatMessagesDao
import com.nextcloud.talk.data.database.dao.ConversationsDao
import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.data.network.NetworkMonitorImpl
import com.nextcloud.talk.data.user.UsersDao
import com.nextcloud.talk.data.user.UsersRepository
import com.nextcloud.talk.data.user.UsersRepositoryImpl
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
import com.nextcloud.talk.repositories.reactions.ReactionsRepositoryImpl
import com.nextcloud.talk.ui.theme.MaterialSchemesProviderImpl
import com.nextcloud.talk.ui.theme.TalkSpecificViewThemeUtils
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.database.user.CurrentUserProviderImpl
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.message.MessageUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.nextcloud.talk.utils.preferences.AppPreferencesImpl
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
/**
* TODO - basically a reimplementation of common dependencies for use in Previewing Advanced Compose Views
* It's a hard coded Dependency Injector
*
*/
class ComposePreviewUtils private constructor(context: Context) {
private val mContext = context
companion object {
fun getInstance(context: Context) = ComposePreviewUtils(context)
val TAG: String = ComposePreviewUtils::class.java.simpleName
}
@OptIn(ExperimentalCoroutinesApi::class)
val appPreferences: AppPreferences
get() = AppPreferencesImpl(mContext)
val context: Context = mContext
val userRepository: UsersRepository
get() = UsersRepositoryImpl(usersDao)
val userManager: UserManager
get() = UserManager(userRepository)
val userProvider: CurrentUserProviderNew
get() = CurrentUserProviderImpl(userManager)
val colorUtil: ColorUtil
get() = ColorUtil(mContext)
val materialScheme: MaterialSchemes
get() = MaterialSchemesProviderImpl(userProvider, colorUtil).getMaterialSchemesForCurrentUser()
val viewThemeUtils: ViewThemeUtils
get() {
val android = AndroidViewThemeUtils(materialScheme, colorUtil)
val material = MaterialViewThemeUtils(materialScheme, colorUtil)
val androidx = AndroidXViewThemeUtils(materialScheme, android)
val talk = TalkSpecificViewThemeUtils(materialScheme, androidx)
val dialog = DialogViewThemeUtils(materialScheme)
return ViewThemeUtils(materialScheme, android, material, androidx, talk, dialog)
}
val messageUtils: MessageUtils
get() = MessageUtils(mContext)
val retrofit: Retrofit
get() {
val retrofitBuilder = Retrofit.Builder()
.client(OkHttpClient.Builder().build())
.baseUrl("https://nextcloud.com")
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.addConverterFactory(LoganSquareConverterFactory.create())
return retrofitBuilder.build()
}
val ncApi: NcApi
get() = retrofit.create(NcApi::class.java)
val ncApiCoroutines: NcApiCoroutines
get() = retrofit.create(NcApiCoroutines::class.java)
val chatNetworkDataSource: ChatNetworkDataSource
get() = RetrofitChatNetwork(ncApi, ncApiCoroutines)
val usersDao: UsersDao
get() = DummyUserDaoImpl()
val chatMessagesDao: ChatMessagesDao
get() = DummyChatMessagesDaoImpl()
val chatBlocksDao: ChatBlocksDao
get() = DummyChatBlocksDaoImpl()
val conversationsDao: ConversationsDao
get() = DummyConversationDaoImpl()
val networkMonitor: NetworkMonitor
get() = NetworkMonitorImpl(mContext)
val chatRepository: ChatMessageRepository
get() = OfflineFirstChatRepository(
chatMessagesDao,
chatBlocksDao,
chatNetworkDataSource,
networkMonitor,
userProvider
)
val conversationNetworkDataSource: ConversationsNetworkDataSource
get() = RetrofitConversationsNetwork(ncApi)
val conversationRepository: OfflineConversationsRepository
get() = OfflineFirstConversationsRepository(
conversationsDao,
conversationNetworkDataSource,
chatNetworkDataSource,
networkMonitor,
userProvider
)
val reactionsRepository: ReactionsRepository
get() = ReactionsRepositoryImpl(ncApi, userProvider, chatMessagesDao)
val mediaRecorderManager: MediaRecorderManager
get() = MediaRecorderManager()
val audioFocusRequestManager: AudioFocusRequestManager
get() = AudioFocusRequestManager(mContext)
val chatViewModel: ChatViewModel
get() = ChatViewModel(
appPreferences,
chatNetworkDataSource,
chatRepository,
conversationRepository,
reactionsRepository,
mediaRecorderManager,
audioFocusRequestManager,
userProvider
)
val contactsRepository: ContactsRepository
get() = ContactsRepositoryImpl(ncApiCoroutines, userProvider)
val contactsViewModel: ContactsViewModel
get() = ContactsViewModel(contactsRepository)
}

View File

@ -1,213 +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.utils.preview
import com.nextcloud.talk.data.database.dao.ChatBlocksDao
import com.nextcloud.talk.data.database.dao.ChatMessagesDao
import com.nextcloud.talk.data.database.dao.ConversationsDao
import com.nextcloud.talk.data.database.model.ChatBlockEntity
import com.nextcloud.talk.data.database.model.ChatMessageEntity
import com.nextcloud.talk.data.database.model.ConversationEntity
import com.nextcloud.talk.data.user.UsersDao
import com.nextcloud.talk.data.user.model.UserEntity
import com.nextcloud.talk.models.json.push.PushConfigurationState
import io.reactivex.Maybe
import io.reactivex.Observable
import io.reactivex.Single
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
class DummyChatMessagesDaoImpl : ChatMessagesDao {
override fun getNewestMessageId(internalConversationId: String): Long = 0L
override fun getMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>> = flowOf()
override fun getTempMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>> =
flowOf()
override fun getTempUnsentMessagesForConversation(internalConversationId: String): Flow<List<ChatMessageEntity>> {
// nothing to return here as long this class is only used for the Search window
return flowOf()
}
override fun getTempMessageForConversation(
internalConversationId: String,
referenceId: String
): Flow<ChatMessageEntity> = flowOf()
override suspend fun upsertChatMessages(chatMessages: List<ChatMessageEntity>) { /* */ }
override suspend fun upsertChatMessage(chatMessage: ChatMessageEntity) { /* */ }
override fun getChatMessageForConversation(
internalConversationId: String,
messageId: Long
): Flow<ChatMessageEntity> = flowOf()
override fun deleteChatMessages(internalIds: List<String>) { /* */ }
override fun deleteTempChatMessages(internalConversationId: String, referenceIds: List<String>) { /* */ }
override fun updateChatMessage(message: ChatMessageEntity) { /* */ }
override fun getMessagesFromIds(messageIds: List<Long>): Flow<List<ChatMessageEntity>> = flowOf()
override fun getMessagesForConversationSince(
internalConversationId: String,
messageId: Long
): Flow<List<ChatMessageEntity>> = flowOf()
override fun getMessagesForConversationBefore(
internalConversationId: String,
messageId: Long,
limit: Int
): Flow<List<ChatMessageEntity>> = flowOf()
override fun getMessagesForConversationBeforeAndEqual(
internalConversationId: String,
messageId: Long,
limit: Int
): Flow<List<ChatMessageEntity>> = flowOf()
override fun getCountBetweenMessageIds(
internalConversationId: String,
oldestMessageId: Long,
newestMessageId: Long
): Int = 0
override fun clearAllMessagesForUser(pattern: String) { /* */ }
override fun deleteMessagesOlderThan(internalConversationId: String, messageId: Long) { /* */ }
}
class DummyUserDaoImpl : UsersDao() {
private val dummyUsers = mutableListOf(
UserEntity(1L, "user1_id", "user1", "server1", "1"),
UserEntity(2L, "user2_id", "user2", "server1", "2"),
UserEntity(0L, "user3_id", "user3", "server2", "3")
)
private var activeUserId: Long? = 1L
override fun getActiveUser(): Maybe<UserEntity> {
return Maybe.fromCallable { dummyUsers.find { it.id == activeUserId && !it.scheduledForDeletion } }
}
override fun getActiveUserObservable(): Observable<UserEntity> {
return Observable.fromCallable { dummyUsers.find { it.id == activeUserId && !it.scheduledForDeletion } }
}
override fun getActiveUserSynchronously(): UserEntity? {
return dummyUsers.find { it.id == activeUserId && !it.scheduledForDeletion }
}
override fun deleteUser(user: UserEntity): Int {
val initialSize = dummyUsers.size
dummyUsers.removeIf { it.id == user.id }
return initialSize - dummyUsers.size
}
override fun updateUser(user: UserEntity): Int {
val index = dummyUsers.indexOfFirst { it.id == user.id }
return if (index != -1) {
dummyUsers[index] = user
1
} else {
0
}
}
override fun saveUser(user: UserEntity): Long {
val newUser = user.copy(id = dummyUsers.size + 1L)
dummyUsers.add(newUser)
return newUser.id
}
override fun saveUsers(vararg users: UserEntity): List<Long> {
return users.map { saveUser(it) }
}
override fun getUsers(): Single<List<UserEntity>> {
return Single.just(dummyUsers.filter { !it.scheduledForDeletion })
}
override fun getUserWithId(id: Long): Maybe<UserEntity> {
return Maybe.fromCallable { dummyUsers.find { it.id == id } }
}
override fun getUserWithIdNotScheduledForDeletion(id: Long): Maybe<UserEntity> {
return Maybe.fromCallable { dummyUsers.find { it.id == id && !it.scheduledForDeletion } }
}
override fun getUserWithUserId(userId: String): Maybe<UserEntity> {
return Maybe.fromCallable { dummyUsers.find { it.userId == userId } }
}
override fun getUsersScheduledForDeletion(): Single<List<UserEntity>> {
return Single.just(dummyUsers.filter { it.scheduledForDeletion })
}
override fun getUsersNotScheduledForDeletion(): Single<List<UserEntity>> {
return Single.just(dummyUsers.filter { !it.scheduledForDeletion })
}
override fun getUserWithUsernameAndServer(username: String, server: String): Maybe<UserEntity> {
return Maybe.fromCallable { dummyUsers.find { it.username == username } }
}
override fun setUserAsActiveWithId(id: Long): Int {
activeUserId = id
return 1
}
override fun updatePushState(id: Long, state: PushConfigurationState): Single<Int> {
val index = dummyUsers.indexOfFirst { it.id == id }
return if (index != -1) {
dummyUsers[index] = dummyUsers[index]
Single.just(1)
} else {
Single.just(0)
}
}
}
class DummyConversationDaoImpl : ConversationsDao {
override fun getConversationsForUser(accountId: Long): Flow<List<ConversationEntity>> = flowOf()
override fun getConversationForUser(accountId: Long, token: String): Flow<ConversationEntity?> = flowOf()
override fun upsertConversations(conversationEntities: List<ConversationEntity>) { /* */ }
override fun deleteConversations(conversationIds: List<String>) { /* */ }
override fun updateConversation(conversationEntity: ConversationEntity) { /* */ }
override fun clearAllConversationsForUser(accountId: Long) { /* */ }
}
class DummyChatBlocksDaoImpl : ChatBlocksDao {
override fun deleteChatBlocks(blocks: List<ChatBlockEntity>) { /* */ }
override fun getChatBlocks(internalConversationId: String): Flow<List<ChatBlockEntity>> = flowOf()
override fun getChatBlocksContainingMessageId(
internalConversationId: String,
messageId: Long
): Flow<List<ChatBlockEntity?>> = flowOf()
override fun getConnectedChatBlocks(
internalConversationId: String,
oldestMessageId: Long,
newestMessageId: Long
): Flow<List<ChatBlockEntity>> = flowOf()
override suspend fun upsertChatBlock(chatBlock: ChatBlockEntity) { /* */ }
override fun clearChatBlocksForUser(pattern: String) { /* */ }
override fun deleteChatBlocksOlderThan(internalConversationId: String, messageId: Long) { /* */ }
}

View File

@ -1,7 +1,7 @@
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2021-2025 Google LLC
~ SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"

View File

@ -139,19 +139,6 @@
<include layout="@layout/out_of_office_view" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/conversation_delete_notice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible"
android:layout_margin="8dp"
app:cardCornerRadius="12dp">
<include layout="@layout/remainder_to_delete_conversation" />
</com.google.android.material.card.MaterialCardView>
<com.stfalcon.chatkit.messages.MessagesList
android:id="@+id/messagesListView"
android:layout_width="match_parent"

View File

@ -86,49 +86,8 @@
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/margin_between_elements"
android:textSize="@dimen/headline_text_size"
android:textStyle="bold"
tools:text="Jane Doe" />
<androidx.emoji2.widget.EmojiTextView
android:id="@+id/pronouns"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/avatar_image"
android:paddingBottom="4dp"
android:paddingTop="2dp"
android:layout_marginStart="@dimen/margin_between_elements"
android:layout_marginTop="@dimen/margin_between_elements"
android:layout_toEndOf="@id/display_name_text"
android:textSize="@dimen/supporting_text_text_size"
android:visibility="gone"
tools:visibility="visible"
tools:text="She/Her"
/>
<androidx.emoji2.widget.EmojiTextView
android:id="@+id/profession_company"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/display_name_text"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/margin_between_elements"
android:textSize="@dimen/headline_text_size"
android:visibility="gone"
tools:visibility="visible"
tools:text="Marketing Manager @ Nextcloud GmbH" />
<androidx.emoji2.widget.EmojiTextView
android:id="@+id/location_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/profession_company"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/margin_between_elements"
android:textSize="@dimen/headline_text_size"
android:visibility="gone"
tools:visibility="visible"
tools:text="10:03 PM · London" />
</RelativeLayout>
<LinearLayout
@ -310,34 +269,34 @@
android:textSize="@dimen/supporting_text_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/share_conversation_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_margin"
android:paddingTop="@dimen/standard_half_margin"
android:paddingEnd="@dimen/standard_margin"
android:paddingBottom="@dimen/standard_half_margin">
<LinearLayout
android:id="@+id/share_conversation_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/standard_margin"
android:paddingTop="@dimen/standard_half_margin"
android:paddingEnd="@dimen/standard_margin"
android:paddingBottom="@dimen/standard_half_margin"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:layout_width="24dp"
android:layout_height="40dp"
android:layout_marginEnd="@dimen/standard_margin"
android:contentDescription="@null"
android:src="@drawable/ic_share_variant"
app:tint="@color/grey_600" />
<ImageView
android:layout_width="24dp"
android:layout_height="40dp"
android:layout_marginEnd="@dimen/standard_margin"
android:contentDescription="@null"
android:src="@drawable/ic_share_variant"
app:tint="@color/grey_600" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="@string/nc_guest_access_share_link"
android:textSize="@dimen/headline_text_size" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="@string/nc_guest_access_share_link"
android:textSize="@dimen/headline_text_size" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
@ -345,18 +304,18 @@
android:id="@+id/lock_conversation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_margin"
android:paddingEnd="@dimen/standard_margin">
android:paddingEnd="@dimen/standard_margin"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="horizontal"
android:paddingTop="@dimen/standard_half_margin"
android:paddingBottom="@dimen/standard_half_margin">
android:paddingBottom="@dimen/standard_half_margin"
android:layout_weight="1"
android:orientation="horizontal">
<ImageView
android:layout_width="24dp"
@ -472,12 +431,12 @@
android:id="@+id/list_bans_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_margin"
android:paddingTop="@dimen/standard_half_margin"
android:paddingEnd="@dimen/standard_margin"
android:paddingBottom="@dimen/standard_half_margin">
android:paddingBottom="@dimen/standard_half_margin"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground">
<ImageView
android:layout_width="24dp"

View File

@ -195,36 +195,6 @@
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_rename"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_padding"
android:paddingEnd="@dimen/standard_padding"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_pencil_grey600_24dp"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_rename"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_archive"
android:layout_width="match_parent"
@ -257,7 +227,7 @@
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_leave"
android:id="@+id/conversation_operation_rename"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:background="?android:attr/selectableItemBackground"
@ -271,7 +241,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_exit_to_app_black_24dp"
android:src="@drawable/ic_pencil_grey600_24dp"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.appcompat.widget.AppCompatTextView
@ -280,7 +250,7 @@
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_leave"
android:text="@string/nc_rename"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
@ -316,6 +286,36 @@
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
<LinearLayout
android:id="@+id/conversation_operation_leave"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_sheet_item_height"
android:background="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="@dimen/standard_padding"
android:paddingEnd="@dimen/standard_padding"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
android:src="@drawable/ic_exit_to_app_black_24dp"
app:tint="@color/high_emphasis_menu_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:paddingStart="40dp"
android:paddingEnd="@dimen/zero"
android:text="@string/nc_leave"
android:textAlignment="viewStart"
android:textColor="@color/high_emphasis_text"
android:textSize="@dimen/bottom_sheet_text_size" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -12,7 +12,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:padding="16dp"
android:background="@color/bg_bottom_sheet">
android:background="@color/popup_menu_color">
<TextView
android:id="@+id/event_scheduled"
@ -26,6 +26,7 @@
android:id="@+id/meetingTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary"
android:textSize="16sp"
tools:text="Meeting at 8:00 pm"/>
@ -45,7 +46,6 @@
android:layout_height="wrap_content"
android:text="@string/archive_conversation"
android:visibility = "gone"
android:textSize="18sp"
android:paddingTop="24dp"/>

View File

@ -62,8 +62,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/standard_margin"
android:checked="false"
android:clickable="false"/>
android:clickable="false" />
</LinearLayout>
@ -112,47 +111,6 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/standard_margin"
android:checked="true"
android:clickable="false"/>
android:clickable="false" />
</LinearLayout>
<LinearLayout
android:id="@+id/notification_settings_sensitive_conversation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="@dimen/standard_half_padding"
android:paddingBottom="@dimen/standard_half_padding"
android:paddingStart="@dimen/standard_margin"
android:paddingEnd="@dimen/standard_margin"
android:background="?android:attr/selectableItemBackground">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nc_sensitive_conversation"
android:textSize="@dimen/headline_text_size" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/nc_sensitive_conversation_hint"
android:textSize="@dimen/supporting_text_text_size"
android:textColor="?android:textColorSecondary" />
</LinearLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/sensitive_conversation_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/standard_margin"
android:checked="false"
android:clickable="false" />
</LinearLayout>
</LinearLayout>

View File

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2025 Sowjanya Kota <sowjanya.kch@gmail.com>
~ SPDX-License-Identifier: GPL-3.0-or-later
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/deletion_warning_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal"
android:padding="12dp">
<TextView
android:id="@+id/deletion_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:lineSpacingExtra="4dp"
android:gravity="center"
android:layout_marginBottom="12dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/delete_now_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nc_delete_now"
android:textColor="@color/white"
app:icon="@drawable/ic_delete"
app:iconPadding="8dp"
app:iconTint="@color/white"
app:iconGravity="textStart"
android:backgroundTint="@color/nc_darkRed"
android:layout_marginEnd="16dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/keep_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nc_keep"
app:icon="@drawable/ic_check"
app:iconPadding="8dp"
app:iconGravity="textStart" />
</LinearLayout>
</LinearLayout>

View File

@ -64,7 +64,6 @@
android:layout_height="wrap_content"
android:layout_below="@id/dialogName"
android:layout_marginTop="6dp"
android:id="@+id/relativeLayout"
android:layout_toEndOf="@id/dialogAvatarFrameLayout">
<androidx.emoji2.widget.EmojiTextView
@ -122,8 +121,8 @@
android:layout_height="wrap_content"
android:layout_alignTop="@id/dialogAvatarFrameLayout"
android:layout_marginTop="2dp"
android:layout_toEndOf="@id/dialogAvatarFrameLayout"
android:layout_toStartOf="@id/dialogDate"
android:layout_toEndOf="@id/dialogAvatarFrameLayout"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">تحرير</string>
<string name="add_participants">إضافة </string>
<string name="add_to_notes">إضافة إلى الملاحظات</string>
<string name="added_to_favorites">إضافة المحادثة %1$sإلى المُفضّلة</string>
<string name="appbar_search_in">بحث في %s</string>
@ -275,9 +274,9 @@
<string name="nc_ignore_battery_optimization_dialog_text">تحسين البطارية لا يتم تجاهله. يجب تغيير هذا للتأكد من أن الإشعارات تعمل في الخلفية! الرجاء النقر فوق \"موافق OK\" ثم تحديد \"جميع التطبيقات All apps\" -> %1$s -> \"لا تقم بالتحسين Do not optimize\"</string>
<string name="nc_ignore_battery_optimization_dialog_title">تجاهل توفير البطارية</string>
<string name="nc_important_conversation">محادثة مهمة</string>
<string name="nc_important_conversation_desc">الاشعارات في هذه المحادثة ستتجاوز إعداد الحالة \"يُرجى عدم الإزعاج\"</string>
<string name="nc_invitations">دعوات</string>
<string name="nc_join_open_conversations">الانضمام إلى محادثات جارية</string>
<string name="nc_keep">حفظ</string>
<string name="nc_last_moderator_leaving_room_warning">تحتاج إلى إنشاء ميسر جديد قبل أن تتمكن من مغادرة المحادثة</string>
<string name="nc_last_modified">%1$s| آخر تعديل: %2$s</string>
<string name="nc_leave">غادر المحادثة</string>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Editar</string>
<string name="add_participants">Amestar</string>
<string name="add_to_notes">Amestar a Notes</string>
<string name="added_to_favorites">La conversación «%1$s» metióse en Favoritos</string>
<string name="appbar_search_in">Buscar en: %s</string>
@ -163,7 +162,6 @@
<string name="nc_guest_access_share_link">Compartir l\'enllaz de la converación</string>
<string name="nc_important_conversation">Converación importante</string>
<string name="nc_invitations">Invitaciones</string>
<string name="nc_keep">Caltener</string>
<string name="nc_leave">Colar de la conversación</string>
<string name="nc_leaving_call">Colando de la llamada…</string>
<string name="nc_license_title">Llicencia</string>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Edit</string>
<string name="add_participants">Add</string>
<string name="add_to_notes">Add to Notes</string>
<string name="added_to_favorites">Added conversation %1$s to favourites</string>
<string name="appbar_search_in">Search in %s</string>
@ -72,7 +71,6 @@
<string name="leave_call">Leave call</string>
<string name="left_conversation">You left the conversation %1$s</string>
<string name="load_more_results">Load more results</string>
<string name="local_time">Local time: %1$s</string>
<string name="lock_conversation">Lock conversation</string>
<string name="lock_symbol">Lock symbol</string>
<string name="lower_hand">Lower hand</string>
@ -179,7 +177,6 @@
<string name="nc_delete_conversation_more">If you delete the conversation, it will also be deleted for all other participants.</string>
<string name="nc_delete_message">Delete</string>
<string name="nc_delete_message_leaked_to_matterbridge">Message deleted successfully, but it might have been leaked to other services</string>
<string name="nc_delete_now">Delete now</string>
<string name="nc_deleted_user">User %1$s was removed</string>
<string name="nc_demote">Demote from moderator</string>
<string name="nc_description_record_voice">Record voice message</string>
@ -285,11 +282,10 @@
<string name="nc_ignore_battery_optimization_dialog_text">Battery optimization is not ignored. This should be changed to make sure that notifications work in the background! Please click OK and select \"All apps\" -> %1$s -> Do not optimize</string>
<string name="nc_ignore_battery_optimization_dialog_title">Ignore battery optimization</string>
<string name="nc_important_conversation">Important conversation</string>
<string name="nc_important_conversation_desc">\"Do not disturb\" user status is ignored for important conversations</string>
<string name="nc_important_conversation_desc">Notifications in this conversation will override Do Not Disturb settings</string>
<string name="nc_invalid_time">Invalid time</string>
<string name="nc_invitations">Invitations</string>
<string name="nc_join_open_conversations">Join open conversations</string>
<string name="nc_keep">Keep</string>
<string name="nc_last_moderator_leaving_room_warning">You need to promote a new moderator before you can leave the conversation</string>
<string name="nc_last_modified">%1$s | Last modified: %2$s</string>
<string name="nc_leave">Leave conversation</string>
@ -309,10 +305,6 @@
<string name="nc_manual">Not set</string>
<string name="nc_mark_as_read">Mark as read</string>
<string name="nc_mark_as_unread">Mark as unread</string>
<string name="nc_mark_conversation_as_important">Conversation marked as important</string>
<string name="nc_mark_conversation_as_insensitive">Conversation unmarked as sensitive</string>
<string name="nc_mark_conversation_as_sensitive">Conversation marked as sensitive</string>
<string name="nc_mark_conversation_as_unimportant">Conversation unmarked as important</string>
<string name="nc_meeting_ended">Meeting ended</string>
<string name="nc_message_added_to_notes">Message added to notes</string>
<string name="nc_message_failed">Failed</string>
@ -391,7 +383,6 @@
<string name="nc_rename_confirm">Rename</string>
<string name="nc_reply">Reply</string>
<string name="nc_reply_privately">Reply privately</string>
<string name="nc_room_retention">Room is retained successfully</string>
<string name="nc_save_message">Save</string>
<string name="nc_save_success">Saved successfully</string>
<string name="nc_screen_lock_timeout_30">30 seconds</string>
@ -406,8 +397,6 @@
<string name="nc_search">Search</string>
<string name="nc_select_an_account">Select an account</string>
<string name="nc_send_edit_message">Update message</string>
<string name="nc_sensitive_conversation">Sensitive conversation</string>
<string name="nc_sensitive_conversation_hint">Message preview will be disabled in conversation list and notifications</string>
<string name="nc_sent_a_gif" formatted="true">%1$s sent a GIF.</string>
<string name="nc_sent_a_gif_you">You sent a GIF.</string>
<string name="nc_sent_a_video" formatted="true">%1$s sent a video.</string>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Редактиране</string>
<string name="add_participants">Добавяне</string>
<string name="appbar_search_in">Търсене в %s</string>
<string name="archived">Архивирано</string>
<string name="audio_output_bluetooth">Bluetooth /Блутут/</string>
@ -17,7 +16,7 @@
<string name="call_without_notification">Обаждане без известие</string>
<string name="camera_permission_granted">Дадено е право на камера. Моля, изберете камера отново. </string>
<string name="choose_avatar_from_cloud">Избор на аватар от облака</string>
<string name="clear_status_message">Изчисти състоянието</string>
<string name="clear_status_message">Изчистване на съобщението за състоянието</string>
<string name="clear_status_message_after">Изчистване на съобщение за състоянието след</string>
<string name="close">Затваряне</string>
<string name="connection_established">Осъществена е връзка</string>
@ -38,7 +37,7 @@
<string name="file_list_folder">папка</string>
<string name="file_list_loading">Зареждане …</string>
<string name="filename_progress">%1$s (%2$d)</string>
<string name="fourHours">4 чàса</string>
<string name="fourHours">4 часа</string>
<string name="invisible">Невидим</string>
<string name="leave_call">Напускане на обаждането</string>
<string name="load_more_results">Зареждане на още резултати</string>
@ -65,7 +64,7 @@
<string name="nc_action_open_main_menu">Отворяне на главното меню</string>
<string name="nc_add_attachment">Добавяне на прикачен файл</string>
<string name="nc_add_emojis">Добавяне на емотикони</string>
<string name="nc_add_file">Добави към разговор</string>
<string name="nc_add_file">Добавяне към разговор</string>
<string name="nc_add_participants">Добавяне на участници</string>
<string name="nc_add_to_favorites">Добави към любимите</string>
<string name="nc_all_ok_operation">Добре, всичко е готово!</string>
@ -113,7 +112,6 @@
<string name="nc_common_disabled">Изключено</string>
<string name="nc_common_dismiss">Отхвърляне</string>
<string name="nc_common_error_sorry">Съжалявам нещо се обърка!</string>
<string name="nc_common_more_options">Още опции</string>
<string name="nc_common_set">Настройка</string>
<string name="nc_common_skip">Пропусни</string>
<string name="nc_common_unknown">Неизвестен</string>
@ -198,7 +196,7 @@
<string name="nc_guest_access_share_link">Споделяне на връзка за разговор</string>
<string name="nc_hint_enter_a_message">Въвеждане на съобщение …</string>
<string name="nc_important_conversation">Важен разговор</string>
<string name="nc_keep">Запази</string>
<string name="nc_important_conversation_desc">Известията в този разговор ще отменят настройките „Не безпокойте“</string>
<string name="nc_last_moderator_leaving_room_warning">Трябва да повишите нов модератор, преди да можете да напуснете разговора</string>
<string name="nc_last_modified">%1$s Последна промяна: %2$s</string>
<string name="nc_leave">Напускане на разговор</string>
@ -226,7 +224,6 @@
<string name="nc_missed_call">Пропуснахте обаждане от %s</string>
<string name="nc_moderator">Модератор</string>
<string name="nc_new_conversation">Нов разговор</string>
<string name="nc_new_conversation_visibility">Видимост</string>
<string name="nc_new_mention">Непрочетени споменавания</string>
<string name="nc_new_messages">Непрочетени съобщения.</string>
<string name="nc_nextcloud_talk_app_not_installed">%1$s не е наличен (не е инсталиран или е ограничен от администратора)</string>
@ -395,7 +392,7 @@
<string name="no_phone_book_integration_due_to_permissions">Няма интеграция на телефонен номер поради липсващи права</string>
<string name="oneHour">1 час</string>
<string name="online">На линия</string>
<string name="online_status">Състояние</string>
<string name="online_status">Състояние на линия</string>
<string name="openConversations">Отворени разговори</string>
<string name="open_in_files_app">Отворяне в приложението Файлове</string>
<string name="play_pause_voice_message">Възпроизвеждане/пауза на гласово съобщение</string>
@ -431,10 +428,10 @@
<string name="save">Записване</string>
<string name="scope_federated_description">Синхронизиране само с доверени сървъри</string>
<string name="scope_federated_title">Федериран</string>
<string name="scope_local_description">Видимо за потребители на тази инстанция на сървъра, както и гости.</string>
<string name="scope_local_description">Видим само за хора от този случай и гости</string>
<string name="scope_local_title">Локално</string>
<string name="scope_private_description">Видим само за хора, открити по телефонен номер, който е зададен в \"Talk\".</string>
<string name="scope_private_title">Лично</string>
<string name="scope_private_description">Видим само за хора, съчетани чрез интегриране на телефонен номер чрез Talk на мобилен телефон</string>
<string name="scope_private_title">Частен</string>
<string name="scope_published_description">Синхронизиране с доверени сървъри и с глобалната и публичната адресна книга</string>
<string name="scope_published_title">Публикувано</string>
<string name="scope_toggle">Превключване на обхват</string>
@ -450,7 +447,7 @@
<string name="set">Да се зададе</string>
<string name="set_avatar_from_camera">Задаване на аватар от камерата</string>
<string name="set_status">Задаване на състояние</string>
<string name="set_status_message">Задай състояние</string>
<string name="set_status_message">Задаване на съобщение за състояние</string>
<string name="share">Споделяне</string>
<string name="shared_items_audio">Аудио</string>
<string name="shared_items_file">Файл</string>
@ -498,7 +495,7 @@
<string name="userinfo_no_info_headline">Няма зададена лична информация</string>
<string name="userinfo_no_info_text">Добавяне на име, снимка и подробности за контакт към страницата на вашия профил.</string>
<string name="video_call">Видео разговор</string>
<string name="whats_your_status">Какво е вашето състояние?</string>
<string name="whats_your_status">Какъв е вашият статус?</string>
<plurals name="polls_amount_voters">
<item quantity="one">%dгласувания </item>
<item quantity="other">%d гласувания</item>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Edició</string>
<string name="add_participants">Afegeix</string>
<string name="add_to_notes">Afegeix-ho a les notes</string>
<string name="added_to_favorites">S\'ha afegit la conversa %1$s als preferits</string>
<string name="appbar_search_in">Cerca a %s</string>
@ -272,9 +271,9 @@
<string name="nc_ignore_battery_optimization_dialog_text">L\'optimització de la bateria no s\'ignora. Això hauria de canviar-se per a garantir que les notificacions funcionin en segon pla. Feu clic a D\'acord i seleccioneu \"Totes les aplicacions\" -> %1$s -> No optimitzis</string>
<string name="nc_ignore_battery_optimization_dialog_title">Ignora l\'optimització de la bateria</string>
<string name="nc_important_conversation">Conversa important</string>
<string name="nc_important_conversation_desc">Les notificacions d\'aquesta conversa anul·laran els paràmetres de no destorbar.</string>
<string name="nc_invitations">Invitacions</string>
<string name="nc_join_open_conversations">Uneix-te a converses obertes</string>
<string name="nc_keep">Mantén</string>
<string name="nc_last_moderator_leaving_room_warning">Heu de promocionar un nou moderador abans de deixar la conversa</string>
<string name="nc_last_modified">%1$s | Darrera modificació: %2$s</string>
<string name="nc_leave">Surt de la conversa</string>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Upravit</string>
<string name="add_participants">Přidat</string>
<string name="add_to_notes">Přidat do Poznámek</string>
<string name="added_to_favorites">Konverzace %1$s přidána do oblíbených</string>
<string name="appbar_search_in">Hledat v %s</string>
@ -72,7 +71,6 @@
<string name="leave_call">Opustit hovor</string>
<string name="left_conversation">Opustili jste konverzaci %1$s</string>
<string name="load_more_results">Načíst další výsledky</string>
<string name="local_time">Místní čas: %1$s</string>
<string name="lock_conversation">Uzamknout konverzaci</string>
<string name="lock_symbol">Symbol zámku</string>
<string name="lower_hand">Přestat se hlásit</string>
@ -179,7 +177,6 @@
<string name="nc_delete_conversation_more">Pokud konverzaci smažete, bude smazána také pro všechny její ostatní účastníky.</string>
<string name="nc_delete_message">Smazat</string>
<string name="nc_delete_message_leaked_to_matterbridge">Zpráva úspěšně smazána, ale možná unikla do jiných služeb</string>
<string name="nc_delete_now">Smazat nyní</string>
<string name="nc_deleted_user">Uživatel %1$s byl odebrán</string>
<string name="nc_demote">Odebrat oprávnění moderátora</string>
<string name="nc_description_record_voice">Nahrát hlasovou zprávu</string>
@ -285,11 +282,10 @@
<string name="nc_ignore_battery_optimization_dialog_text">Úspora energie z akumulátoru není ignorována. Pokud chcete, aby upozorňování na pozadí fungovalo správně, mělo by toto být změněno! Klikněte na OK a vyberte „Všechny aplikace“ -> %1$s -> neoptimalizovat</string>
<string name="nc_ignore_battery_optimization_dialog_title">Ignorovat úsporu energie z akumulátoru</string>
<string name="nc_important_conversation">Důležitá konverzace</string>
<string name="nc_important_conversation_desc">Stav uživatele „Nevyrušovat“ bude v případě důležitých konverzací ignorován</string>
<string name="nc_important_conversation_desc">Upozornění v této konverzaci budou ignorovat režim Nerušit</string>
<string name="nc_invalid_time">Neplatný čas</string>
<string name="nc_invitations">Pozvání</string>
<string name="nc_join_open_conversations">Přidat se do všem přístupných konverzací</string>
<string name="nc_keep">Ponechat</string>
<string name="nc_last_moderator_leaving_room_warning">Než budete moci konverzaci opustit, je třeba předat někomu roli moderátora</string>
<string name="nc_last_modified">%1$s | Naposledy upraveno: %2$s</string>
<string name="nc_leave">Opustit konverzaci</string>
@ -309,12 +305,7 @@
<string name="nc_manual">Nenastaveno</string>
<string name="nc_mark_as_read">Označit jako přečtené</string>
<string name="nc_mark_as_unread">Označit jako nepřečtené</string>
<string name="nc_mark_conversation_as_important">Konverzace označena jako důležitá</string>
<string name="nc_mark_conversation_as_insensitive">Zrušeno značení konverzace coby citlivé</string>
<string name="nc_mark_conversation_as_sensitive">Konverzace označena jako citlivá</string>
<string name="nc_mark_conversation_as_unimportant">Zrušeno značení konverzace coby důležité</string>
<string name="nc_meeting_ended">Schůzka skončila</string>
<string name="nc_message_added_to_notes">Zpráva přidána do poznámek</string>
<string name="nc_message_failed">Nezdařilo se</string>
<string name="nc_message_failed_to_send">Odeslání zprávy se nezdařilo:</string>
<string name="nc_message_offline">Bez připojení</string>
@ -391,7 +382,6 @@
<string name="nc_rename_confirm">Přejmenovat</string>
<string name="nc_reply">Odpovědět</string>
<string name="nc_reply_privately">Odpovědět soukromě</string>
<string name="nc_room_retention">Místnost je úspěšně ponechána</string>
<string name="nc_save_message">Uložit</string>
<string name="nc_save_success">Úspěšně uloženo</string>
<string name="nc_screen_lock_timeout_30">30 sekund</string>
@ -406,8 +396,6 @@
<string name="nc_search">Hledat</string>
<string name="nc_select_an_account">Vyberte účet</string>
<string name="nc_send_edit_message">Aktualizovat zprávu</string>
<string name="nc_sensitive_conversation">Citlivá konverzace</string>
<string name="nc_sensitive_conversation_hint">Náhled zprávy bude vypnut pro seznam konverzací a notifikace</string>
<string name="nc_sent_a_gif" formatted="true">%1$s poslal(a) GIF animaci.</string>
<string name="nc_sent_a_gif_you">Odeslali jste GIF animaci.</string>
<string name="nc_sent_a_video" formatted="true">%1$s poslal(a) video.</string>
@ -543,7 +531,6 @@
<string name="online_status">Stav online</string>
<string name="openConversations">Otevřít konverzace</string>
<string name="open_in_files_app">Otevřít v aplikaci Soubory</string>
<string name="open_notes">Otevřít poznámky</string>
<string name="play_pause_voice_message">Přehrát/pozastavit hlasovou zprávu</string>
<string name="playback_speed_control">Ovládání rychlosti přehrávání</string>
<string name="polls_add_option">Přidat volbu</string>
@ -683,12 +670,6 @@
<item quantity="many">Viz %d podobných zpráv</item>
<item quantity="other">Viz %d podobné zprávy</item>
</plurals>
<plurals name="nc_conversation_auto_delete_info">
<item quantity="one">Tato konverzace bude automaticky smazána pro kohokoli po %1$d dni bez jakékoli aktivity.</item>
<item quantity="few">Tato konverzace bude automaticky smazána pro kohokoli po %1$d dnech bez jakékoli aktivity.</item>
<item quantity="many">Tato konverzace bude automaticky smazána pro kohokoli po %1$d dnech bez jakékoli aktivity.</item>
<item quantity="other">Tato konverzace bude automaticky smazána pro kohokoli po %1$d dnech bez jakékoli aktivity.</item>
</plurals>
<plurals name="polls_amount_voters">
<item quantity="one">%d hlas</item>
<item quantity="few">%d hlasy</item>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Rediger</string>
<string name="add_participants">Tilføj</string>
<string name="add_to_notes">Tilføj til Noter</string>
<string name="added_to_favorites">Tilføjede samtalen %1$s til favoritter</string>
<string name="appbar_search_in">Søg i %s</string>
@ -270,6 +269,7 @@
<string name="nc_ignore_battery_optimization_dialog_text">Batterioptimering bliver ikke ignoreret. Dette bør ændres for at være sikker på at notifikationer virker i baggrunden! Klik venligst på OK og vælg \"Alle apps\" -> %1$s -> Optimer ikke</string>
<string name="nc_ignore_battery_optimization_dialog_title">Ignorer batterioptimering</string>
<string name="nc_important_conversation">Vigtig samtale</string>
<string name="nc_important_conversation_desc">notifikationer under denne samtale vil ignorere Forstyr ikke indstillinger</string>
<string name="nc_invitations">Invitationer</string>
<string name="nc_join_open_conversations">Deltag i åbne samtaler</string>
<string name="nc_last_moderator_leaving_room_warning">Du skal udnævne en ny moderator inden du kan forlade samtalen.</string>
@ -390,9 +390,9 @@
<string name="nc_server_failed_to_import_account">Den valgte konto kunne ikke importeres</string>
<string name="nc_server_helper_text">Linket til dit %1$s web interface når du åbner den i browseren.</string>
<string name="nc_server_import_account">Importer konto fra %1$s appen</string>
<string name="nc_server_import_account_plain">Importér konto</string>
<string name="nc_server_import_account_plain">Importer konto</string>
<string name="nc_server_import_accounts">Importer konti fra %1$s appen</string>
<string name="nc_server_import_accounts_plain">Importér konti</string>
<string name="nc_server_import_accounts_plain">Importer konti</string>
<string name="nc_server_maintenance">Få venligst %1$s ud fra vedligeholdelse </string>
<string name="nc_server_not_installed">Afslut venligst din %1$s installation</string>
<string name="nc_server_testing_connection">Tester forbindelsen</string>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Bearbeiten</string>
<string name="add_participants">Hinzufügen</string>
<string name="add_to_notes">Zu Notizen hinzufügen</string>
<string name="added_to_favorites">Unterhaltung %1$s zu Favoriten hinzugefügt</string>
<string name="appbar_search_in">Suche in %s</string>
@ -72,7 +71,6 @@
<string name="leave_call">Anruf verlassen</string>
<string name="left_conversation">Sie haben die Unterhaltung %1$s verlassen</string>
<string name="load_more_results">Weitere Ergebnisse laden</string>
<string name="local_time">Ortszeit: %1$s</string>
<string name="lock_conversation">Unterhaltung sperren</string>
<string name="lock_symbol">Schloss-Symbol</string>
<string name="lower_hand">Hand herunternehmen</string>
@ -179,7 +177,6 @@
<string name="nc_delete_conversation_more">Wenn Sie diese Unterhaltung löschen, dann wird diese auch für alle anderen Teilnehmer gelöscht.</string>
<string name="nc_delete_message">Löschen</string>
<string name="nc_delete_message_leaked_to_matterbridge">Nachricht gelöscht, sie wurde aber möglicherweise an andere Dienste weitergegeben.</string>
<string name="nc_delete_now">Jetzt löschen</string>
<string name="nc_deleted_user">Benutzer %1$s wurde entfernt</string>
<string name="nc_demote">Moderator absetzen</string>
<string name="nc_description_record_voice">Sprachnachricht aufnehmen</string>
@ -285,11 +282,10 @@
<string name="nc_ignore_battery_optimization_dialog_text">Die Batterieoptimierung ist aktiviert. Dies sollte geändert werden, um sicherzustellen, dass Benachrichtigungen im Hintergrund funktionieren! Bitte klicken Sie auf OK und wählen Sie \"Alle Apps\" -> %1$s -> Nicht optimieren</string>
<string name="nc_ignore_battery_optimization_dialog_title">Batterieoptimierung ignorieren</string>
<string name="nc_important_conversation">Wichtige Unterhaltung</string>
<string name="nc_important_conversation_desc">\"Nicht stören\"-Benutzerstatus wird für wichtige Unterhaltungen ignoriert</string>
<string name="nc_important_conversation_desc">Benachrichtigungen in dieser Unterhaltung überschreiben Nicht-Stören-Einstellungen</string>
<string name="nc_invalid_time">Ungültige Zeit</string>
<string name="nc_invitations">Einladungen</string>
<string name="nc_join_open_conversations">Offenen Unterhaltungen beitreten</string>
<string name="nc_keep">Behalten</string>
<string name="nc_last_moderator_leaving_room_warning">Sie müssen einen neuen Moderator bestimmen, bevor Sie die Unterhaltung verlassen können.</string>
<string name="nc_last_modified">%1$s | Zuletzt geändert: %2$s</string>
<string name="nc_leave">Unterhaltung verlassen</string>
@ -309,10 +305,6 @@
<string name="nc_manual">Nicht eingestellt</string>
<string name="nc_mark_as_read">Als gelesen markieren</string>
<string name="nc_mark_as_unread">Als ungelesen markieren</string>
<string name="nc_mark_conversation_as_important">Unterhaltung als wichtig markiert</string>
<string name="nc_mark_conversation_as_insensitive">Unterhaltung nicht als sensibel markiert</string>
<string name="nc_mark_conversation_as_sensitive">Unterhaltung als sensibel markiert</string>
<string name="nc_mark_conversation_as_unimportant">Unterhaltung nicht als wichtig markiert</string>
<string name="nc_meeting_ended">Meeting beendet</string>
<string name="nc_message_added_to_notes">Nachricht zu den Notizen hinzugefügt</string>
<string name="nc_message_failed">Fehlgeschlagen</string>
@ -391,7 +383,6 @@
<string name="nc_rename_confirm">Umbenennen</string>
<string name="nc_reply">Antworten</string>
<string name="nc_reply_privately">Privat antworten</string>
<string name="nc_room_retention">Raum wird beibehalten</string>
<string name="nc_save_message">Speichern</string>
<string name="nc_save_success">Gespeichert</string>
<string name="nc_screen_lock_timeout_30">30 Sekunden</string>
@ -406,8 +397,6 @@
<string name="nc_search">Suchen</string>
<string name="nc_select_an_account">Konto auswählen</string>
<string name="nc_send_edit_message">Nachricht aktualisieren</string>
<string name="nc_sensitive_conversation">Sensible Unterhaltung</string>
<string name="nc_sensitive_conversation_hint">Die Nachrichtenvorschau wird in der Unterhaltungsliste und den Benachrichtigungen deaktiviert</string>
<string name="nc_sent_a_gif" formatted="true">%1$s hat ein GIF gesendet.</string>
<string name="nc_sent_a_gif_you">Sie haben ein GIF gesendet.</string>
<string name="nc_sent_a_video" formatted="true">%1$s hat ein Video gesendet.</string>
@ -505,7 +494,7 @@
<string name="nc_shared_location">Geteilter Ort</string>
<string name="nc_show_notification_warning_description">Wenn Benachrichtigungen nicht korrekt konfiguriert sind, zeige regelmäßig eine Warnung</string>
<string name="nc_show_notification_warning_title">Zeige regelmäßige Benachrichtigungswarnung</string>
<string name="nc_sort_by">Sortieren nach</string>
<string name="nc_sort_by">Sortiere nach</string>
<string name="nc_start_group_chat">Gruppenchat starten</string>
<string name="nc_start_time">Startzeit</string>
<string name="nc_switch_account">Konto wechseln</string>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Επεξεργασία</string>
<string name="add_participants">Προσθήκη</string>
<string name="appbar_search_in">Αναζήτηση στο %s</string>
<string name="archived">Αρχειοθετήθηκε</string>
<string name="audio_output_dialog_headline">Έξοδος ήχου</string>
@ -107,7 +106,6 @@
<string name="nc_conversations_empty">Συμμετέχετε σε συνομιλία ή ξεκινήστε μια νέα</string>
<string name="nc_conversations_empty_details">Πείτε γειά σε φίλους και συνεργάτες!</string>
<string name="nc_copy_message">Αντιγραφή</string>
<string name="nc_create_new_conversation">Δημιουργία νέας συνομιλίας</string>
<string name="nc_create_poll">Δημιουργία ψηφοφορίας</string>
<string name="nc_date_header_today">Σήμερα</string>
<string name="nc_date_header_yesterday">Χθές</string>
@ -167,8 +165,8 @@
<string name="nc_guest_access_share_link">Κοινή χρήση συνδέσμου συνομιλίας</string>
<string name="nc_hint_enter_a_message">Εισάγετε ένα μήνυμα ...</string>
<string name="nc_important_conversation">Σημαντική συνομιλία</string>
<string name="nc_important_conversation_desc">Οι Ειδοποιήσεις σε αυτή την συνομιλία θα παρακάμψουν τις ρυθμίσεις Μην ενοχλείτε</string>
<string name="nc_invitations">Προσκλήσεις</string>
<string name="nc_join_open_conversations">Δημιουργία νέας συνομιλίας</string>
<string name="nc_last_modified">%1$s Τελευταία τροποποίηση %2$s</string>
<string name="nc_leave">Εγκατάλειψη συνομιλίας</string>
<string name="nc_leaving_call">Αποχώρηση από την κλήση ...</string>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Editar</string>
<string name="add_participants">Guardar</string>
<string name="appbar_search_in">Compartir en %s</string>
<string name="archived">Archivado</string>
<string name="audio_output_bluetooth">Bluetooth</string>
@ -201,7 +200,7 @@
<string name="nc_guest_access_share_link">Compartir enlace de la conversación</string>
<string name="nc_hint_enter_a_message">Escribe un mensaje...</string>
<string name="nc_important_conversation">Conversación importante</string>
<string name="nc_keep">Mantén</string>
<string name="nc_important_conversation_desc">Las notificaciones en esta conversación anularán la configuración de No molestar</string>
<string name="nc_last_moderator_leaving_room_warning">Necesitas promover un nuevo moderador antes de poder abandonar la conversación</string>
<string name="nc_last_modified">%1$s | Última modificación: %2$s</string>
<string name="nc_leave">Dejar la conversación</string>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Editar</string>
<string name="add_participants">Añadir</string>
<string name="add_to_notes">Añadir a Notas</string>
<string name="added_to_favorites">Se añadió la conversación %1$s a los favoritos</string>
<string name="appbar_search_in">Buscar en %s</string>
@ -276,6 +275,7 @@
<string name="nc_ignore_battery_optimization_dialog_text">La optimización de batería no se está ignorando. ¡Esto debería ser cambiado para garantizar que las notificaciones funcionen en segundo plano!. Por favor, haga clic en OK y seleccione \"Todas las apps\" -> %1$s -> No optimizar</string>
<string name="nc_ignore_battery_optimization_dialog_title">Ignorar la optimización de batería</string>
<string name="nc_important_conversation">Conversación importante</string>
<string name="nc_important_conversation_desc">Las notificaciones de esta conversación anularán el ajuste de No Molestar</string>
<string name="nc_invitations">Invitaciones</string>
<string name="nc_join_open_conversations">Unirse a conversaciones abiertas</string>
<string name="nc_last_moderator_leaving_room_warning">Debe escoger a un nuevo moderador antes de que pueda abandonar la conversación</string>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Muuda</string>
<string name="add_participants">Lisa</string>
<string name="add_to_notes">Lisa Märkmetesse</string>
<string name="added_to_favorites">„%1$s“ vestlus on märgitud lemmikuks</string>
<string name="appbar_search_in">Otsi siin: %s</string>
@ -28,15 +27,15 @@
<string name="call_without_notification">Kõne ilma teavituseta</string>
<string name="camera_permission_granted">Õigused kaamera kasutamiseks on nüüd olemas. Palun vali kaamera uuesti.</string>
<string name="choose_avatar_from_cloud">Vali tunnuspilt pilvest</string>
<string name="clear_status_message">Eemalda olekuteade</string>
<string name="clear_status_message_after">Eemalda olekuteade pärast</string>
<string name="clear_status_message">Tühjenda staatuseteade</string>
<string name="clear_status_message_after">Tühjenda staatuseteade pärast</string>
<string name="close">Sulge</string>
<string name="close_icon">Sulgemise ikoon</string>
<string name="connection_established">Saadi ühendus</string>
<string name="connection_lost">Puudub ühendus serveriga</string>
<string name="connection_lost_sent_messages_are_queued">Ühendus on katkenud - saadetud sõnumid on edasisaatmise ootejärjekorras</string>
<string name="continuous_voice_message_recording">Häälsõnumi pidevaks salvestamiseks lukusta salvestamine</string>
<string name="conversation_archived">Vestlus on arhiveeritud</string>
<string name="conversation_archived">Vestlust on arhiveeritud</string>
<string name="conversation_is_read_only">Vestlus on vaid loetav</string>
<string name="conversation_read_only_failed">Ei õnnestunud muuta vestlust ainult loetavaks</string>
<string name="conversations">Vestlused</string>
@ -72,7 +71,6 @@
<string name="leave_call">Lahku kõnest</string>
<string name="left_conversation">Sa lahkusid %1$s vestlusest</string>
<string name="load_more_results">Laadi veel tulemusi</string>
<string name="local_time">Kohalik aeg: %1$s</string>
<string name="lock_conversation">Lukusta vestlus</string>
<string name="lock_symbol">Lukuikoon</string>
<string name="lower_hand">Lase käsi alla</string>
@ -179,7 +177,6 @@
<string name="nc_delete_conversation_more">Kui kustutad selle vestluse, siis kustub see ka kõikide osalejate jaoks.</string>
<string name="nc_delete_message">Kustuta</string>
<string name="nc_delete_message_leaked_to_matterbridge">Sõnumi kustutamine õnnestus, kuid võis juhtuda, et ta oli juba teistesse sõnumiteenustesse edastatud</string>
<string name="nc_delete_now">Kustuta kohe</string>
<string name="nc_deleted_user">Kasutaja „%1$s“ on eemaldatud</string>
<string name="nc_demote">Võta ära moderaatori õigused</string>
<string name="nc_description_record_voice">Salvesta häälsõnum</string>
@ -285,11 +282,10 @@
<string name="nc_ignore_battery_optimization_dialog_text">Akukasutuse optimeerimine pole eiratud. Et teavituste saatmine toimiks taustal, siis peaksid seda muutma. Palun klõpsi „Sobib“ ja vali „Kõik rakendused“ → „%1$s“ → „Ära optimeeri“</string>
<string name="nc_ignore_battery_optimization_dialog_title">Eira akukasutuse optimeerimist</string>
<string name="nc_important_conversation">Oluline vestlus</string>
<string name="nc_important_conversation_desc">„Ära sega“ olek on oluliste vestluste puhul eiratud</string>
<string name="nc_important_conversation_desc">Selle vestluse teavituste seadistused on ülemuslikud „Ära sega“ seadistuste suhtes</string>
<string name="nc_invalid_time">Vigane aeg</string>
<string name="nc_invitations">Kutsed</string>
<string name="nc_join_open_conversations">Liitu avalike vestlustega</string>
<string name="nc_keep">Hoia alles</string>
<string name="nc_last_moderator_leaving_room_warning">Pead määrtama uue moderaatori enne, kui saad siis vestlusest lahkuda</string>
<string name="nc_last_modified">%1$s | Viimati muudetud: %2$s</string>
<string name="nc_leave">Lahku vestlusest</string>
@ -309,17 +305,13 @@
<string name="nc_manual">Pole määratud</string>
<string name="nc_mark_as_read">Märgi loetuks</string>
<string name="nc_mark_as_unread">Märgi mitteloetuks</string>
<string name="nc_mark_conversation_as_important">Vestlus on märgitud oluliseks</string>
<string name="nc_mark_conversation_as_insensitive">Vestlus on märgitud mittedelikaatseks</string>
<string name="nc_mark_conversation_as_sensitive">Vestlus on märgitud delikaatseks</string>
<string name="nc_mark_conversation_as_unimportant">Vestlus on märgitud mitteoluliseks</string>
<string name="nc_meeting_ended">Kohtumine on lõppenud</string>
<string name="nc_message_added_to_notes">Teade on lisatud märkmetesse</string>
<string name="nc_message_failed">Ebaõnnestus</string>
<string name="nc_message_failed_to_send">Sõnumi saatmine ei õnnestunud:</string>
<string name="nc_message_offline">Pole võrgus</string>
<string name="nc_message_quote_cancel_reply">Katkesta vastamine</string>
<string name="nc_message_read">Sõnum on loetud</string>
<string name="nc_message_read">Sõnim on loetud</string>
<string name="nc_message_sending">Saatmisel</string>
<string name="nc_message_sent">Sõnum on saadetud</string>
<string name="nc_microphone_permission_hint">Et saaksid suhtlemisel kasutada heliedastust, palun luba rakendusel kasutada mikrofoni.</string>
@ -391,7 +383,6 @@
<string name="nc_rename_confirm">Muuda nime</string>
<string name="nc_reply">Vasta</string>
<string name="nc_reply_privately">Vasta privaatselt</string>
<string name="nc_room_retention">Jututoa allesjätmine õnnestus</string>
<string name="nc_save_message">Salvesta</string>
<string name="nc_save_success">Salvestamine õnnestus</string>
<string name="nc_screen_lock_timeout_30">30 sekundit</string>
@ -406,8 +397,6 @@
<string name="nc_search">Otsi</string>
<string name="nc_select_an_account">Otsi kasutajakontot</string>
<string name="nc_send_edit_message">Uuenda sõnumit</string>
<string name="nc_sensitive_conversation">Vestlus tundlikul teemal</string>
<string name="nc_sensitive_conversation_hint">Sõnumite eelvaated saava vestluste loendis ja teavitustes olema peidetud</string>
<string name="nc_sent_a_gif" formatted="true">%1$s saatis gif-faili.</string>
<string name="nc_sent_a_gif_you">Sina saatsid gif-faili.</string>
<string name="nc_sent_a_video" formatted="true">%1$s saatis video.</string>
@ -543,7 +532,6 @@
<string name="online_status">Võrgus staatus</string>
<string name="openConversations">Ava vestlused</string>
<string name="open_in_files_app">Ava failirakenduses</string>
<string name="open_notes">Ava märkmik</string>
<string name="play_pause_voice_message">Esita häälsõnumit või peata esitus</string>
<string name="playback_speed_control">Taasesituse kiiruse juhtimine</string>
<string name="polls_add_option">Lisa valik</string>
@ -681,10 +669,6 @@
<item quantity="one">Vaata %d sarnast sõnumit</item>
<item quantity="other">Vaata %d sarnast sõnumit</item>
</plurals>
<plurals name="nc_conversation_auto_delete_info">
<item quantity="one">See vestlus kustub automaatselt kõigi osalejate jaoks, kui siin pole olnud tegevust %1$d päeva jooksul.</item>
<item quantity="other">See vestlus kustub automaatselt kõigi osalejate jaoks, kui siin pole olnud tegevust %1$d päeva jooksul.</item>
</plurals>
<plurals name="polls_amount_voters">
<item quantity="one">%d hääl</item>
<item quantity="other">%d häält</item>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Aldatu</string>
<string name="add_participants">Gehitu</string>
<string name="add_to_notes">Gehitu oharretara</string>
<string name="added_to_favorites">%1$s elkarrizketa gogokoetara gehitu da</string>
<string name="appbar_search_in">Bilatu %s(e)n</string>
@ -236,6 +235,7 @@
<string name="nc_guest_access_share_link">Partekatu elkarrizketa esteka</string>
<string name="nc_hint_enter_a_message">Idatzi mezu bat …</string>
<string name="nc_important_conversation">Elkarrizketa garrantzitsua</string>
<string name="nc_important_conversation_desc">Ez Molestatu ezarpenek sahiestuko dituzte elkarrizketa honen jakinarazpenak </string>
<string name="nc_invitations">Gonbidapenak</string>
<string name="nc_join_open_conversations">Sartu elkarrizketa irekietara</string>
<string name="nc_last_moderator_leaving_room_warning">Moderatzaile berri bat sustatu behar duzu elkarrizketatik irten aurretik</string>

View File

@ -43,7 +43,6 @@
<string name="later_today">تا آخر وقت همین امروز</string>
<string name="leave_call">Leave call</string>
<string name="load_more_results">بار کردن نتیحه‌های بیش‌تر</string>
<string name="local_time">زمان محلی: %1$s</string>
<string name="lock_conversation">Lock conversation</string>
<string name="lock_symbol">نماد قفل</string>
<string name="mentioned">Mentioned</string>
@ -176,7 +175,6 @@
<string name="nc_important_conversation">گفتگوی مهم</string>
<string name="nc_invitations">دعوت‌ها</string>
<string name="nc_join_open_conversations">پیوستن به گفتگوهای باز</string>
<string name="nc_keep">نگاه داشتن</string>
<string name="nc_last_moderator_leaving_room_warning">You need to promote a new moderator before you can leave the conversation</string>
<string name="nc_last_modified">تغییرات %1$s | آخرین تغییرات: %2$s</string>
<string name="nc_leave">ترک گفتگو</string>
@ -196,11 +194,9 @@
<string name="nc_mark_as_unread">علامت به عنوان خوانده‌نشده</string>
<string name="nc_message_failed">Failed</string>
<string name="nc_message_failed_to_send">ناتوانی در فرستادن پیام:</string>
<string name="nc_message_offline">آفلاین</string>
<string name="nc_message_quote_cancel_reply">لغو پاسخ</string>
<string name="nc_message_read">پیام، خوانده شد</string>
<string name="nc_message_sent">پیام، ارسال شد</string>
<string name="nc_missed_call">شما یک تماس از %s را از دست دادید</string>
<string name="nc_moderator">مدیر</string>
<string name="nc_new_conversation">گفتگوی جدید</string>
<string name="nc_new_conversation_visibility">Visibility</string>
@ -402,7 +398,6 @@
<string name="translation_copy_translated_text">رونوشت متن ترجمه</string>
<string name="translation_detect_language">تشخیص زبان</string>
<string name="translation_device_settings">تنظیمات افزاره</string>
<string name="translation_error_message">امکان تشخیص زبان وجود ندارد.</string>
<string name="translation_from">از</string>
<string name="translation_to">به</string>
<string name="user_avatar">آواتار کاربر</string>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Muokkaa</string>
<string name="add_participants">Lisää</string>
<string name="appbar_search_in">Etsi kohteesta %s</string>
<string name="archived">Arkistoitu</string>
<string name="audio_output_bluetooth">Bluetooth</string>
@ -168,6 +167,7 @@
<string name="nc_guest_access_share_link">Jaa keskustelulinkki</string>
<string name="nc_hint_enter_a_message">Kirjoita viesti…</string>
<string name="nc_important_conversation">Tärkeä keskustelu</string>
<string name="nc_important_conversation_desc">Tämän keskustelun ilmoitukset eivät noudata Älä häiritse -asetuksia</string>
<string name="nc_invitations">Kutsut</string>
<string name="nc_join_open_conversations">Liity avoimiin keskusteluihin</string>
<string name="nc_last_modified">%1$s | Viimeksi muokattu: %2$s</string>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Modifier</string>
<string name="add_participants">Ajouter</string>
<string name="add_to_notes">Ajouter à Notes</string>
<string name="added_to_favorites">La conversation %1$s a été ajoutée aux favoris</string>
<string name="appbar_search_in">Rechercher dans %s</string>
@ -72,7 +71,6 @@
<string name="leave_call">Quitter l\'appel</string>
<string name="left_conversation">Vous avez quitté la conversation %1$s</string>
<string name="load_more_results">Charger plus de résultats</string>
<string name="local_time">Heure locale : %1$s</string>
<string name="lock_conversation">Verrouiller la conversation</string>
<string name="lock_symbol">Symbole de verrouillage</string>
<string name="lower_hand">Baisser la main</string>
@ -179,7 +177,6 @@
<string name="nc_delete_conversation_more">Si vous supprimez la conversation, elle sera supprimée pour tous les participants.</string>
<string name="nc_delete_message">Supprimer</string>
<string name="nc_delete_message_leaked_to_matterbridge">Message supprimé avec succès, mais il pourrait avoir été divulgué à dautres services</string>
<string name="nc_delete_now">Supprimer maintenant</string>
<string name="nc_deleted_user">L\'utilisateur %1$s a été supprimé</string>
<string name="nc_demote">Destituer de modérateur</string>
<string name="nc_description_record_voice">Enregistrer un message vocal</string>
@ -285,11 +282,10 @@
<string name="nc_ignore_battery_optimization_dialog_text">L\'optimisation de la batterie n\'est pas ignorée. Ceci devrait être modifié pour vous assurer que les notifications fonctionnent en arrière-plan. Merci de cliquer OK et sélectionner \"Toutes les applications\" -> %1$s -> Ne pas optimiser</string>
<string name="nc_ignore_battery_optimization_dialog_title">Ignorer l\'optimisation de batterie</string>
<string name="nc_important_conversation">Conversation importante</string>
<string name="nc_important_conversation_desc">Le statut utilisateur \"Ne pas déranger\" est ignoré pour les conversations importantes.</string>
<string name="nc_important_conversation_desc">Les notifications dans cette conversation l\'emportent sur les paramètres « Ne Pas Déranger ».</string>
<string name="nc_invalid_time">Heure invalide</string>
<string name="nc_invitations">Invitations</string>
<string name="nc_join_open_conversations">Rejoindre des conversations ouvertes</string>
<string name="nc_keep">Conserver</string>
<string name="nc_last_moderator_leaving_room_warning">Vous devez promouvoir un nouveau modérateur avant de pouvoir quitter la conversation.</string>
<string name="nc_last_modified">%1$s | Dernière modification : %2$s</string>
<string name="nc_leave">Quitter la conversation</string>
@ -401,8 +397,6 @@
<string name="nc_search">Recherche</string>
<string name="nc_select_an_account">Choisissez un compte</string>
<string name="nc_send_edit_message">Message de mise à jour</string>
<string name="nc_sensitive_conversation">Conversation sensible</string>
<string name="nc_sensitive_conversation_hint">La prévisualisation des messages sera désactivée dans la liste des conversations et les notifiactions</string>
<string name="nc_sent_a_gif" formatted="true">%1$s a envoyé un GIF.</string>
<string name="nc_sent_a_gif_you">Vous avez envoyé un GIF.</string>
<string name="nc_sent_a_video" formatted="true">%1$s a envoyé une vidéo.</string>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name= "nc_edit">Cuir in eagar</string>
<string name="add_participants">Cuir</string>
<string name="add_to_notes">Cuir le Nótaí</string>
<string name="added_to_favorites">Cuireadh comhrá %1$s le ceanáin</string>
<string name="appbar_search_in">Cuardaigh i %s</string>
@ -72,7 +71,6 @@
<string name="leave_call">Fág glaoch</string>
<string name="left_conversation">D\'fhág tú an comhrá %1$s</string>
<string name="load_more_results">Íoslódáil níos mó torthaí</string>
<string name="local_time">Am áitiúil: %1$s</string>
<string name="lock_conversation">Cuir glas ar an gcomhrá</string>
<string name="lock_symbol">Siombail ghlais</string>
<string name="lower_hand">Lámh íochtair</string>
@ -179,7 +177,6 @@
<string name="nc_delete_conversation_more">Má scriosann tú an comhrá, scriosfar é do gach rannpháirtí eile freisin.</string>
<string name="nc_delete_message">Scrios</string>
<string name="nc_delete_message_leaked_to_matterbridge">D\'éirigh leis an teachtaireacht a scriosadh, ach seans gur sceitheadh chuig seirbhísí eile í</string>
<string name="nc_delete_now">Scrios anois</string>
<string name="nc_deleted_user">Baineadh úsáideoir %1$s</string>
<string name="nc_demote">Léim as an modhnóir</string>
<string name="nc_description_record_voice">Taifead teachtaireacht gutha</string>
@ -285,11 +282,10 @@
<string name="nc_ignore_battery_optimization_dialog_text">Ní thugtar aird ar bharrfheabhsú ceallraí. Ba cheart é seo a athrú chun a chinntiú go n-oibríonn fógraí sa chúlra! Cliceáil OK agus roghnaigh \"Gach aip\" -> %1$s -> Ná optamaigh le do thoil</string>
<string name="nc_ignore_battery_optimization_dialog_title">Déan neamhaird de bharrfheabhsú ceallraí</string>
<string name="nc_important_conversation">Comhrá tábhachtach</string>
<string name="nc_important_conversation_desc">Déantar neamhaird ar stádas an úsáideora \"Ná cuir isteach\" i gcás comhráite tábhachtacha</string>
<string name="nc_important_conversation_desc">Sáróidh fógraí sa chomhrá seo na socruithe Ná Cuir Isteach</string>
<string name="nc_invalid_time">Am neamhbhailí</string>
<string name="nc_invitations">cuirí</string>
<string name="nc_join_open_conversations">Glac páirt i gcomhráite oscailte</string>
<string name="nc_keep">Coinnigh</string>
<string name="nc_last_moderator_leaving_room_warning">Ní mór duit modhnóir nua a chur chun cinn sula bhféadfaidh tú an comhrá a fhágáil</string>
<string name="nc_last_modified">%1$s | Athraithe is déanaí: %2$s</string>
<string name="nc_leave">Fág an comhrá</string>
@ -309,10 +305,6 @@
<string name="nc_manual">Gan socraithe</string>
<string name="nc_mark_as_read">Marcáil mar léite</string>
<string name="nc_mark_as_unread">Marcáil mar neamhléite</string>
<string name="nc_mark_conversation_as_important">Comhrá marcáilte mar thábhachtach</string>
<string name="nc_mark_conversation_as_insensitive">Dímharcáladh an comhrá mar chomhrá íogair</string>
<string name="nc_mark_conversation_as_sensitive">Comhrá marcáilte mar íogair</string>
<string name="nc_mark_conversation_as_unimportant">Dímharcáladh an comhrá mar chomhrá tábhachtach</string>
<string name="nc_meeting_ended">Deireadh leis an gcruinniú</string>
<string name="nc_message_added_to_notes">Teachtaireacht curtha leis na nótaí</string>
<string name="nc_message_failed">Theip</string>
@ -391,7 +383,6 @@
<string name="nc_rename_confirm">Athainmnigh</string>
<string name="nc_reply">Freagra</string>
<string name="nc_reply_privately">Freagair go príobháideach</string>
<string name="nc_room_retention">Coinníodh an seomra go rathúil</string>
<string name="nc_save_message">Sábháil</string>
<string name="nc_save_success">Sábháilte go rathúil</string>
<string name="nc_screen_lock_timeout_30">30 soicind</string>
@ -406,8 +397,6 @@
<string name="nc_search">Cuardach</string>
<string name="nc_select_an_account">Roghnaigh cuntas</string>
<string name="nc_send_edit_message">Nuashonraigh teachtaireacht</string>
<string name="nc_sensitive_conversation">Comhrá íogair</string>
<string name="nc_sensitive_conversation_hint">Díchumasófar réamhamharc teachtaireachta sa liosta comhráite agus sna fógraí</string>
<string name="nc_sent_a_gif" formatted="true">Sheol %1$s GIF.</string>
<string name="nc_sent_a_gif_you">Sheol tú GIF.</string>
<string name="nc_sent_a_video" formatted="true">Sheol %1$s físeán.</string>

Some files were not shown because too many files have changed in this diff Show More