Compare commits

...

285 Commits

Author SHA1 Message Date
Sowjanya Kota
e982864e55
Merge pull request from nextcloud/fix_autocomplete
Fix mentions of phone conversation
2025-07-08 13:16:17 +02:00
Sowjanya Kota
5704be67fd
Merge pull request from nextcloud/bug_fix
Scale DatePicker for smaller screens
2025-07-08 12:39:36 +02:00
Sowjanya Kota
c5942fe575
Merge pull request from nextcloud/improve_accessibility
Improve accessibility
2025-07-08 12:30:19 +02:00
sowjanyakch
2b5973e763
scale datePicker
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 12:18:07 +02:00
sowjanyakch
3ad7291cc4
maximum width
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 12:18:07 +02:00
sowjanyakch
61c0490038
fix header
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 11:16:59 +02:00
sowjanyakch
fac30aa3f5
use scaled icon + fix headers
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 11:14:22 +02:00
sowjanyakch
8787bb557b
create phone scaling icon
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 11:14:21 +02:00
sowjanyakch
e9158b291f
extract common code to PhoneUtils
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 11:14:21 +02:00
sowjanyakch
d780d4b261
use displayName instead of source to display avatars
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 11:14:21 +02:00
sowjanyakch
c2deca00ee
fix mentions
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 11:14:20 +02:00
sowjanyakch
a5a42aae61
fix mention autocomplete item
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 11:14:20 +02:00
sowjanyakch
c16a6c74fa
add circular phone icons
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 11:14:19 +02:00
Sowjanya Kota
4667fef98c
Merge pull request from nextcloud/edit_messages
Edit mentions
2025-07-08 10:57:40 +02:00
sowjanyakch
26a383e4de
add replaceMentionChipSpans function
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:26 +02:00
sowjanyakch
317984919f
avoid crash if messageParameters is null
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:25 +02:00
sowjanyakch
8b02b7797c
hide mentionAutocomplete in edit mode
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:25 +02:00
sowjanyakch
00ce1281ec
ktlint
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:25 +02:00
sowjanyakch
022077be62
refactor
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:25 +02:00
sowjanyakch
1488a33e9a
check if message is null
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:24 +02:00
sowjanyakch
3e747b34fc
remove nested block depth warning
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:24 +02:00
sowjanyakch
bbd921a031
suppress nested block depth warning
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:24 +02:00
sowjanyakch
62458fa887
format code
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:23 +02:00
sowjanyakch
85d996622d
use mention chips in edit text
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:23 +02:00
sowjanyakch
c4b3555d7f
remove unused imports
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:23 +02:00
sowjanyakch
e33e7fd0fb
editing mentions work
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:23 +02:00
sowjanyakch
15f3240f98
move edit button on click listener to setEditUI
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:22 +02:00
sowjanyakch
09905e61fe
remove unused import
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:22 +02:00
sowjanyakch
895ad61d3d
use mention name in edit text field
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:21 +02:00
sowjanyakch
f14fa59de7
also get mentions from the input textfield
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:21 +02:00
sowjanyakch
50173694ef
include mentions in edited message
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-08 10:41:21 +02:00
Marcel Hibbe
36c8f421c2
Merge pull request from nextcloud/bugfix/5126/fixContactNotClickable
fix that contact in search is clickable
2025-07-08 09:10:23 +02:00
Marcel Hibbe
41e12ec19d
Merge pull request from nextcloud/crash_fix
Crash fix
2025-07-07 12:25:47 +02:00
Marcel Hibbe
02476b10ae
fix that contact in search is clickable
A contact (so not a conversation yet!) had the VIEW_TYPE 2131558677, but 2131558687 was expected to identify the type, see FlexibleViewType.kt

The checks in onItemClick are replaced to directly change the type via kotlin "is" method.

best solution for future: FlexibleAdapter should be removed!-> replace with Compose!

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-07-07 10:45:24 +02:00
sowjanyakch
b3e89633fa
avoid null
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-07 09:26:11 +02:00
sowjanyakch
b9667e45c0
fix crash
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-07 09:23:53 +02:00
Nextcloud Android Bot
5606254a4a Weekly 22.0.0 Alpha 09 2025-07-07 03:11:38 +00:00
Andy Scherzinger
9cc950e30a
Merge pull request from nextcloud/renovate/junit-framework-monorepo
fix(deps): update dependency org.junit.vintage:junit-vintage-engine to v5.13.3
2025-07-04 20:12:49 +02:00
renovate[bot]
4f5e2ce273 fix(deps): update dependency org.junit.vintage:junit-vintage-engine to v5.13.3
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 19:57:47 +02:00
Andy Scherzinger
6b63a4f793
Merge pull request from nextcloud/renovate/gradle-8.x
chore(deps): update dependency gradle to v8.14.3
2025-07-04 19:56:54 +02:00
Andy Scherzinger
3af5981841
ci(lint): Update lint score
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-07-04 19:17:32 +02:00
Andy Scherzinger
d9be063891
ci(chksm): Add meta-data
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-07-04 19:06:30 +02:00
renovate[bot]
b5ec3cd2af
chore(deps): update dependency gradle to v8.14.3
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 18:57:43 +02:00
github-actions[bot]
fc65a7a8d0
Merge pull request from nextcloud/renovate/workversion
fix(deps): update workversion to v2.10.2
2025-07-04 16:39:11 +00:00
Andy Scherzinger
6a026c1fc7
fix(worker): Update to worker v2.10.x API
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-07-04 18:25:32 +02:00
renovate[bot]
c9f699d0d3
fix(deps): update workversion to v2.10.2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-04 18:19:45 +02:00
Marcel Hibbe
913907a7e2
Merge pull request from nextcloud/bumpJacksonCore
bump jackson core version
2025-07-03 12:30:07 +02:00
Andy Scherzinger
7f6f077680
Merge pull request from nextcloud/style/noid/outlineIcons
🎨 Migrate to outlined icon style
2025-07-03 11:27:53 +02:00
Marcel Hibbe
632e8f85a9
remove jackson from renovate rule to ignore it
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-07-03 11:18:55 +02:00
Marcel Hibbe
42bd8d2235
bump jackson core version
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-07-03 11:04:20 +02:00
Andy Scherzinger
33ad578ef5
style(icons): Migrate from filled to outline icon style
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-07-03 10:44:05 +02:00
Nextcloud bot
b7ae981c3d
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-03 02:58:35 +00:00
Andy Scherzinger
81cf077f6f
Merge pull request from nextcloud/renovate/ubuntu-noble
chore(deps): update ubuntu:noble docker digest to 440dcf6
2025-07-02 19:05:20 +02:00
sowjanyakch
c208b3d0b1
use proper wording recording instead of message
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-02 18:45:48 +02:00
sowjanyakch
df2a820e88
add content description to buttons
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-02 18:45:48 +02:00
sowjanyakch
8c0432efbf
add content description to buttons
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-02 18:45:47 +02:00
sowjanyakch
9ca6acc933
add content description to buttons
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-02 18:45:47 +02:00
renovate[bot]
1b7b11ff76
chore(deps): update ubuntu:noble docker digest to 440dcf6
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 16:09:51 +00:00
Andy Scherzinger
c8e4de6665
Merge pull request from nextcloud/renovate/ubuntu-noble
chore(deps): update ubuntu:noble docker digest to 89ef6e4
2025-07-02 12:47:59 +02:00
renovate[bot]
78cb62a3bd
chore(deps): update ubuntu:noble docker digest to 89ef6e4
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-02 10:37:59 +00:00
Marcel Hibbe
59fd9f2319
Merge pull request from nextcloud/bugfix/5106/fixCrashWhenSendingMessage
fix crash when sending message
2025-07-01 13:12:05 +02:00
Marcel Hibbe
74a1c5aeb0
fix crash when sending message
-> fix to handle null for getTempMessageForConversation

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-07-01 12:21:24 +02:00
Sowjanya Kota
a3a5272dbb
Merge pull request from nextcloud/fix_delete_all_messages
Show delete all messages in 1:1 only when canDeleteConversation is true
2025-07-01 11:36:38 +02:00
sowjanyakch
dd77ce7c4c
ktlint
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-01 11:02:38 +02:00
sowjanyakch
94db4e6892
show delete all messages option in 1:1 conversation when canDeleteConversation is true
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-07-01 11:02:37 +02:00
Andy Scherzinger
99d5fc7d19
Merge pull request from nextcloud/renovate/nextcloud-pr-feedback-action-digest
chore(deps): update nextcloud/pr-feedback-action digest to e397f3c
2025-07-01 10:48:15 +02:00
renovate[bot]
ba0d37020b
chore(deps): update nextcloud/pr-feedback-action digest to e397f3c
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-01 07:44:58 +00:00
Sowjanya Kota
c19cba3c0d
Merge pull request from nextcloud/test_push_notification
document about test push button in diagnosis screen
2025-07-01 09:43:37 +02:00
Nextcloud bot
2d0f0d9f73
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-01 02:54:26 +00:00
Andy Scherzinger
cdb1c4b0f8
Merge pull request from nextcloud/renovate/org.jetbrains.kotlinx-kotlinx-serialization-json-1.x
fix(deps): update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.9.0
2025-06-30 21:59:21 +02:00
Andy Scherzinger
ea0588cb99
Merge pull request from nextcloud/renovate/com.google.firebase-firebase-messaging-24.x
fix(deps): update dependency com.google.firebase:firebase-messaging to v24.1.2
2025-06-30 21:47:31 +02:00
renovate[bot]
ea19f60ef3
fix(deps): update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.9.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 19:26:34 +00:00
renovate[bot]
3096a73585
fix(deps): update dependency com.google.firebase:firebase-messaging to v24.1.2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 19:21:04 +00:00
sowjanyakch
dbf5c84050
test push button in diagnosis screen
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-06-30 19:12:11 +02:00
Andy Scherzinger
4c7d6f90ec
Merge pull request from nextcloud/renovate/github-codeql-action-3.x
chore(deps): update github/codeql-action action to v3.29.2
2025-06-30 16:37:33 +02:00
renovate[bot]
3930d4b740
chore(deps): update github/codeql-action action to v3.29.2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 13:15:54 +00:00
Marcel Hibbe
9b3f508652
Merge pull request from nextcloud/renovate/ksp-monorepo
chore(deps): update plugin com.google.devtools.ksp to v2.2.0-2.0.2
2025-06-30 10:34:27 +00:00
renovate[bot]
28556e12cd
chore(deps): update plugin com.google.devtools.ksp to v2.2.0-2.0.2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 12:20:26 +02:00
Marcel Hibbe
bd8ad9e672
Merge pull request from nextcloud/updateVerificationMetadata
update verification metadata
2025-06-30 09:39:38 +00:00
Marcel Hibbe
0a44067a58
update verification metadata
..especially to fix failed verification of
android-common:material-color-utilities

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-30 11:13:38 +02:00
Nextcloud bot
4d24716a4d
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-30 03:43:14 +00:00
Nextcloud Android Bot
1648c61b06 Weekly 22.0.0 Alpha 08 2025-06-30 03:12:18 +00:00
Nextcloud bot
bd050f0bc0
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-27 16:31:13 +00:00
Andy Scherzinger
f7997d339b
Merge pull request from nextcloud/renovate/github-codeql-action-3.x
chore(deps): update github/codeql-action action to v3.29.1
2025-06-27 16:38:47 +02:00
renovate[bot]
28cacf2a90
chore(deps): update github/codeql-action action to v3.29.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-27 12:01:34 +00:00
Andy Scherzinger
890a7febc2
Merge pull request from nextcloud/feat/workflow-auto-update-reuse.yml
ci: update reuse.yml workflow from template
2025-06-27 14:00:11 +02:00
Nextcloud bot
48e8caac0a ci: update reuse.yml workflow from template
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-27 10:21:08 +00:00
Tobias Kaminsky
a6fbac92c2
Merge pull request from nextcloud/repo-sync/android-config/master
🔄 synced file(s) with nextcloud/android-config
2025-06-27 10:59:25 +02:00
nextcloud-android-bot
439912754b 🔄 synced local '.github/workflows/' with remote 'config/workflows/'
Signed-off-by: nextcloud-android-bot <android@nextcloud.com>
2025-06-27 08:18:05 +00:00
Nextcloud bot
8792ac1dbd
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-26 03:09:14 +00:00
github-actions[bot]
adc0be1d1d
Merge pull request from nextcloud/renovate/major-retrofit-monorepo
fix(deps): update retrofit monorepo to v3 (major)
2025-06-25 19:31:02 +00:00
renovate[bot]
5fccf47353 fix(deps): update retrofit monorepo to v3
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 21:16:53 +02:00
github-actions[bot]
324e02a807
Merge pull request from nextcloud/renovate/media3_version
fix(deps): update media3_version to v1.7.1
2025-06-25 18:57:11 +00:00
github-actions[bot]
94894b17a6
Merge pull request from nextcloud/renovate/androidx.compose.runtime-runtime-1.x
fix(deps): update dependency androidx.compose.runtime:runtime to v1.8.3
2025-06-25 18:55:40 +00:00
renovate[bot]
f1a0d8c70a fix(deps): update media3_version to v1.7.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 20:21:28 +02:00
renovate[bot]
0edc4ea337 fix(deps): update dependency androidx.compose.runtime:runtime to v1.8.3
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 20:21:07 +02:00
github-actions[bot]
2fe295ef63
Merge pull request from nextcloud/renovate/androidx.activity-activity-compose-1.x
fix(deps): update dependency androidx.activity:activity-compose to v1.10.1
2025-06-25 20:04:34 +02:00
github-actions[bot]
9723d298ed
Merge pull request from nextcloud/renovate/lifecycleversion
fix(deps): update lifecycleversion to v2.9.1
2025-06-25 20:04:01 +02:00
github-actions[bot]
44f201800f
Merge pull request from nextcloud/renovate/org.junit.vintage-junit-vintage-engine-5.x
fix(deps): update dependency org.junit.vintage:junit-vintage-engine to v5.13.2
2025-06-25 17:52:55 +00:00
renovate[bot]
375d7316d8 fix(deps): update dependency androidx.activity:activity-compose to v1.10.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 19:45:18 +02:00
renovate[bot]
d89c243904 fix(deps): update lifecycleversion to v2.9.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 19:44:36 +02:00
github-actions[bot]
22a780d7c8
Merge pull request from nextcloud/renovate/roomversion
fix(deps): update roomversion to v2.7.2
2025-06-25 13:47:13 +00:00
github-actions[bot]
0b95237991
Merge pull request from nextcloud/renovate/org.jetbrains.kotlin.plugin.compose-2.x
chore(deps): update plugin org.jetbrains.kotlin.plugin.compose to v2.2.0
2025-06-25 12:54:16 +00:00
github-actions[bot]
351173bb6b
Merge pull request from nextcloud/renovate/androidx.compose.ui-ui-test-junit4-1.x
fix(deps): update dependency androidx.compose.ui:ui-test-junit4 to v1.8.3
2025-06-25 10:51:18 +00:00
renovate[bot]
1aa2d2aaaa
fix(deps): update dependency androidx.compose.ui:ui-test-junit4 to v1.8.3
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 10:38:48 +00:00
github-actions[bot]
06df0084cf
Merge pull request from nextcloud/renovate/com.android.tools.build-gradle-8.x
fix(deps): update dependency com.android.tools.build:gradle to v8.11.0
2025-06-25 10:26:47 +00:00
github-actions[bot]
ca73e93aeb
Merge pull request from nextcloud/renovate/kotlin-monorepo
fix(deps): update kotlin monorepo to v2.2.0
2025-06-25 12:17:25 +02:00
Andy Scherzinger
c305816f6d
ci(chksm): Add meta-data
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-06-25 12:08:05 +02:00
renovate[bot]
8bc31cdbd4
fix(deps): update dependency com.android.tools.build:gradle to v8.11.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 11:59:08 +02:00
github-actions[bot]
0d9402f496
Merge pull request from nextcloud/renovate/com.github.spotbugs.snom-spotbugs-gradle-plugin-6.x
fix(deps): update dependency com.github.spotbugs.snom:spotbugs-gradle-plugin to v6.2.1
2025-06-25 11:54:41 +02:00
renovate[bot]
9867ed08ac
fix(deps): update kotlin monorepo to v2.2.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 09:47:23 +00:00
renovate[bot]
ef5f6b67f6
chore(deps): update plugin org.jetbrains.kotlin.plugin.compose to v2.2.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 09:41:34 +00:00
renovate[bot]
5cd84f633c
fix(deps): update dependency org.junit.vintage:junit-vintage-engine to v5.13.2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 09:40:01 +00:00
renovate[bot]
d7d139c861
fix(deps): update dependency com.github.spotbugs.snom:spotbugs-gradle-plugin to v6.2.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-25 09:37:40 +00:00
Marcel Hibbe
5612cfa19d
Merge pull request from nextcloud/bugfix/5073/fixInfinityMaximumHeight
fix crash in call view
2025-06-25 07:59:39 +00:00
Nextcloud bot
b4d5af8a4c
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-25 03:09:28 +00:00
renovate[bot]
5ac7e8357b
fix(deps): update roomversion to v2.7.2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 11:35:40 +00:00
github-actions[bot]
156d142ba0
Merge pull request from nextcloud/renovate/com.mebigfatguy.fb-contrib-fb-contrib-7.x
fix(deps): update dependency com.mebigfatguy.fb-contrib:fb-contrib to v7.6.11
2025-06-24 11:28:21 +00:00
renovate[bot]
207408a606
fix(deps): update dependency com.mebigfatguy.fb-contrib:fb-contrib to v7.6.11
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 11:15:30 +00:00
Marcel Hibbe
5a7f4924ab
fix IllegalStateException for LazyVerticalGrid
LazyVerticalGrid was measured with infinite height, which Compose does not allow.
By applying the availableHeight explicitly, this should fix the Exception:

Exception java.lang.IllegalStateException: Vertically scrollable component was measured with an infinity maximum height constraints, which is disallowed. One of the common reasons is nesting layouts like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a header before the list of items please add a header as a separate item() before the main items() inside the LazyColumn scope. There are could be other reasons for this to happen: your ComposeView was added into a LinearLayout with some weight, you applied Modifier.wrapContentSize(unbounded = true) or wrote a custom layout. Please try to remove the source of infinite constraints in the hierarchy above the scrolling container.
  at androidx.compose.foundation.CheckScrollableContainerConstraintsKt.checkScrollableContainerConstraints-K40F9xA (CheckScrollableContainerConstraints.kt:35)
  at androidx.compose.foundation.lazy.grid.LazyGridKt$rememberLazyGridMeasurePolicy$1$1.invoke-0kLqBqw (LazyGrid.kt:174)
  at androidx.compose.foundation.lazy.grid.LazyGridKt$rememberLazyGridMeasurePolicy$1$1.invoke (LazyGrid.kt:172)
  at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$3$2$1.invoke-0kLqBqw (LazyLayout.kt:119)
  at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$3$2$1.invoke (LazyLayout.kt:112)
  at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1.measure-3p2s80s (SubcomposeLayout.kt:725)
  at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0 (InnerNodeCoordinator.kt:135)
  at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s (GraphicsLayerModifier.kt:646)
  at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0 (LayoutModifierNodeCoordinator.kt:188)
  at androidx.compose.foundation.layout.FillNode.measure-3p2s80s (Size.kt:699)
  at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0 (LayoutModifierNodeCoordinator.kt:188)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke (LayoutNodeLayoutDelegate.kt:316)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke (LayoutNodeLayoutDelegate.kt:315)
  at androidx.compose.runtime.snapshots.Snapshot$Companion.observe (Snapshot.kt:503)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe (SnapshotStateObserver.kt:502)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads (SnapshotStateObserver.kt:258)
  at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release (OwnerSnapshotObserver.kt:133)
  at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release (OwnerSnapshotObserver.kt:113)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0 (LayoutNodeLayoutDelegate.kt:1782)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0 (LayoutNodeLayoutDelegate.kt:40)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0 (LayoutNodeLayoutDelegate.kt:696)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0 (LayoutNodeLayoutDelegate.kt:672)
  at androidx.compose.foundation.layout.BoxMeasurePolicy.measure-3p2s80s (Box.kt:151)
  at androidx.compose.foundation.layout.BoxWithConstraintsKt$BoxWithConstraints$1$1.invoke-0kLqBqw (BoxWithConstraints.kt:70)
  at androidx.compose.foundation.layout.BoxWithConstraintsKt$BoxWithConstraints$1$1.invoke (BoxWithConstraints.kt:67)
  at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1.measure-3p2s80s (SubcomposeLayout.kt:725)
  at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0 (InnerNodeCoordinator.kt:135)
  at androidx.compose.foundation.layout.FillNode.measure-3p2s80s (Size.kt:699)
  at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0 (LayoutModifierNodeCoordinator.kt:188)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke (LayoutNodeLayoutDelegate.kt:316)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke (LayoutNodeLayoutDelegate.kt:315)
  at androidx.compose.runtime.snapshots.Snapshot$Companion.observe (Snapshot.kt:503)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe (SnapshotStateObserver.kt:502)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads (SnapshotStateObserver.kt:258)
  at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release (OwnerSnapshotObserver.kt:133)
  at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release (OwnerSnapshotObserver.kt:113)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0 (LayoutNodeLayoutDelegate.kt:1782)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0 (LayoutNodeLayoutDelegate.kt:40)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0 (LayoutNodeLayoutDelegate.kt:696)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0 (LayoutNodeLayoutDelegate.kt:672)
  at androidx.compose.ui.layout.RootMeasurePolicy.measure-3p2s80s (RootMeasurePolicy.kt:38)
  at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0 (InnerNodeCoordinator.kt:135)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke (LayoutNodeLayoutDelegate.kt:316)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke (LayoutNodeLayoutDelegate.kt:315)
  at androidx.compose.runtime.snapshots.Snapshot$Companion.observe (Snapshot.kt:2441)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe (SnapshotStateObserver.kt:502)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads (SnapshotStateObserver.kt:258)
  at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release (OwnerSnapshotObserver.kt:133)
  at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release (OwnerSnapshotObserver.kt:113)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0 (LayoutNodeLayoutDelegate.kt:1782)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0 (LayoutNodeLayoutDelegate.kt:40)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0 (LayoutNodeLayoutDelegate.kt:696)
  at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release (LayoutNode.kt:1222)
  at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA (MeasureAndLayoutDelegate.kt:367)
  at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureOnly (MeasureAndLayoutDelegate.kt:622)
  at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureOnly (MeasureAndLayoutDelegate.kt:420)
  at androidx.compose.ui.platform.AndroidComposeView.onMeasure (AndroidComposeView.android.kt:1370)
  at android.view.View.measure (View.java:27557)
  at androidx.compose.ui.platform.AbstractComposeView.internalOnMeasure$ui_release (ComposeView.android.kt:309)
  at androidx.compose.ui.platform.AbstractComposeView.onMeasure (ComposeView.android.kt:296)
  at android.view.View.measure (View.java:27557)
  at android.widget.RelativeLayout.measureChild (RelativeLayout.java:696)
  at android.widget.RelativeLayout.onMeasure (RelativeLayout.java:499)
  at android.view.View.measure (View.java:27557)
  at android.widget.LinearLayout.measureVertical (LinearLayout.java:1031)
  at android.widget.LinearLayout.onMeasure (LinearLayout.java:721)
  at android.view.View.measure (View.java:27557)
  at android.widget.RelativeLayout.measureChildHorizontal (RelativeLayout.java:735)
  at android.widget.RelativeLayout.onMeasure (RelativeLayout.java:481)
  at android.view.View.measure (View.java:27557)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:7123)
  at android.widget.FrameLayout.onMeasure (FrameLayout.java:194)
  at androidx.appcompat.widget.ContentFrameLayout.onMeasure (ContentFrameLayout.java:141)
  at android.view.View.measure (View.java:27557)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:7123)
  at android.widget.LinearLayout.measureChildBeforeLayout (LinearLayout.java:1608)
  at android.widget.LinearLayout.measureVertical (LinearLayout.java:878)
  at android.widget.LinearLayout.onMeasure (LinearLayout.java:721)
  at android.view.View.measure (View.java:27557)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:7123)
  at android.widget.FrameLayout.onMeasure (FrameLayout.java:194)
  at android.view.View.measure (View.java:27557)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:7123)
  at android.widget.LinearLayout.measureChildBeforeLayout (LinearLayout.java:1608)
  at android.widget.LinearLayout.measureVertical (LinearLayout.java:878)
  at android.widget.LinearLayout.onMeasure (LinearLayout.java:721)
  at android.view.View.measure (View.java:27557)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:7123)
  at android.widget.FrameLayout.onMeasure (FrameLayout.java:194)
  at com.android.internal.policy.DecorView.onMeasure (DecorView.java:824)
  at android.view.View.measure (View.java:27557)
  at android.view.ViewRootImpl.performMeasure (ViewRootImpl.java:4682)
  at android.view.ViewRootImpl.measureHierarchy (ViewRootImpl.java:3098)
  at android.view.ViewRootImpl.performTraversals (ViewRootImpl.java:3461)
  at android.view.ViewRootImpl.doTraversal (ViewRootImpl.java:2765)
  at android.view.ViewRootImpl$TraversalRunnable.run (ViewRootImpl.java:10219)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1544)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1553)
  at android.view.Choreographer.doCallbacks (Choreographer.java:1109)
  at android.view.Choreographer.doFrame (Choreographer.java:994)
  at android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:1527)
  at android.os.Handler.handleCallback (Handler.java:958)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loopOnce (Looper.java:257)
  at android.os.Looper.loop (Looper.java:368)
  at android.app.ActivityThread.main (ActivityThread.java:8839)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:572)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1049)

Also:
Suppress UnusedBoxWithConstraintsScope as it seems that the maxHeight and maxWidth variables are not recognized as being used when they are "only" used in calculations.

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-24 13:04:12 +02:00
github-actions[bot]
f53b90cdf6
Merge pull request from nextcloud/renovate/androidx.core-core-ktx-1.x
fix(deps): update dependency androidx.core:core-ktx to v1.16.0
2025-06-24 10:59:45 +00:00
renovate[bot]
8980bd0f64
fix(deps): update dependency androidx.core:core-ktx to v1.16.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 10:44:43 +00:00
github-actions[bot]
6b0142c6c3
Merge pull request from nextcloud/renovate/androidx.activity-activity-ktx-1.x
fix(deps): update dependency androidx.activity:activity-ktx to v1.10.1
2025-06-24 12:36:53 +02:00
renovate[bot]
011ea9dc82
fix(deps): update dependency androidx.activity:activity-ktx to v1.10.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 10:21:17 +00:00
github-actions[bot]
c95bed4be5
Merge pull request from nextcloud/renovate/androidx.compose-compose-bom-2025.x
fix(deps): update dependency androidx.compose:compose-bom to v2025.06.01
2025-06-24 12:14:42 +02:00
renovate[bot]
1a6e18b003
fix(deps): update dependency androidx.compose:compose-bom to v2025.06.01
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-24 09:58:00 +00:00
Nextcloud bot
87d75102d4
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-24 03:07:36 +00:00
Marcel Hibbe
c110ecdc2b
Merge pull request from nextcloud/targetAndroid15
Android 15 support
2025-06-23 11:00:09 +00:00
Nextcloud Android Bot
fa8a72bc74 Weekly 22.0.0 Alpha 07 2025-06-23 03:10:03 +00:00
Nextcloud bot
a8dd25a285
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-21 03:10:13 +00:00
Marcel Hibbe
486d8d1035
use full space for search text field
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 12:07:47 +02:00
Marcel Hibbe
84d09d0e58
Add clear search button
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 12:02:34 +02:00
Marcel Hibbe
cc270848b5
add extra space at bottom of conversations list
by adding a SpacerItem (legacy code...-> migrate to Compose!)

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:16 +02:00
Marcel Hibbe
8ac9427ba6
simplify initSystemBars
.. by not executing duplicated checks and not using the common lib for now (needs to be aligned if possible)

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:16 +02:00
Marcel Hibbe
6905c78796
add paddings to static layouts
... to improve edge to edge design.

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:16 +02:00
Marcel Hibbe
4a4f4fc8e1
Make navigationBar fully transparent for Android 15
= Change navigationBarStyle to SystemBarStyle.light

This is done to align with the XML screens, see comment.

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:16 +02:00
Marcel Hibbe
02eab6766a
Replace method initStatusBar (use from common lib)
add comment on adjustUIForAPILevel35

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:16 +02:00
Marcel Hibbe
6effeef2ee
solve detekt warnings
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:15 +02:00
Marcel Hibbe
d3d3776a8f
solve detekt warnings
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:15 +02:00
Marcel Hibbe
d97de1bdd2
rename method
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:15 +02:00
Marcel Hibbe
e94e2b63ae
fix lint warnings
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:15 +02:00
Marcel Hibbe
5c3cc89084
add paddings
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:14 +02:00
Marcel Hibbe
c2d3340ddb
add transparent navigationBar for all composables
color statusbar for composable screens by adding a colored Box where the statusBar is (or fallback to coloring via window.statusBarColor)

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:14 +02:00
Marcel Hibbe
cc9ca58a69
add edge to edge support (while handling special cases for xml vs compose and keyboard handling)
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:14 +02:00
Marcel Hibbe
21932df918
fix top padding for chat + use android-common 0.26
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:13 +02:00
Marcel Hibbe
f29d174a61
rename setupSystemColors to initSystemBars
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:13 +02:00
Marcel Hibbe
8e3909efbc
handle edge to edge support for android 15
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:13 +02:00
Marcel Hibbe
9d3ad47e20
WIP targetSdkVersion 35
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-20 10:57:13 +02:00
Nextcloud bot
f7fcc74c68
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-20 03:07:18 +00:00
Nextcloud bot
0eebf8b2d0
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-19 03:07:37 +00:00
Sowjanya Kota
95f8b08a19
Merge pull request from nextcloud/bugfix/4921/avoidDupMessagesByBetterSentStatus
avoid duplicate messages
2025-06-18 18:36:17 +02:00
Nextcloud bot
2a7359c1e9
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-18 03:08:20 +00:00
Marcel Hibbe
86bfaa8657
improve send status handling
replace sendingFailed with SendStatus

add auto migration (incl deleting of column sendingFailed)

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-17 17:21:36 +02:00
Marcel Hibbe
8c066eb521
avoid duplicate messages by introducing a sendStatus
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-17 17:16:53 +02:00
Andy Scherzinger
9e08af3306
Merge pull request from nextcloud/renovate/ghcr.io-nextcloud-continuous-integration-android8-4.x
chore(deps): update ghcr.io/nextcloud/continuous-integration-android8 docker tag to v4
2025-06-17 15:27:52 +02:00
Nextcloud bot
95f7a1e312
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-17 11:01:35 +00:00
Marcel Hibbe
73271018f5
Merge pull request from nextcloud/testFixAnalysisForMergeCommits
Try to run analysis also for merge commits
2025-06-17 10:08:18 +00:00
Marcel Hibbe
7111109ac0
Try to run analysis also for merge commits
Trying to fix
https://github.com/nextcloud/android-config/pull/248/files

which caused that analysis is not only blocked for forks, bit also for merge commits.

Solution: If it's not a PR the step should be omitted..

This commit will be checked when workflow runs on github. It will be merged just to test it's working..

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-17 11:24:01 +02:00
Andy Scherzinger
e3c83823e6
Merge pull request from nextcloud/renovate/gradle-actions-4.x
chore(deps): update gradle/actions action to v4.4.1
2025-06-17 10:50:08 +02:00
renovate[bot]
3b11a90aac
chore(deps): update gradle/actions action to v4.4.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-17 08:40:59 +00:00
Marcel Hibbe
bc29c67269
Merge pull request from nextcloud/fixWrongPluralsTranslation
fix wrong plurals implementation
2025-06-17 07:33:03 +00:00
Marcel Hibbe
4a93551ef9
fix wrong plurals implementation
- key was duplicated
- kotlin handling of plurals was missing

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-16 10:10:02 +02:00
Nextcloud Android Bot
d0ff4320a8 Weekly 22.0.0 Alpha 06 2025-06-16 03:11:01 +00:00
Marcel Hibbe
20b70c2728
Merge pull request from nextcloud/issue-5016-app-shortcuts
Adding a short cut to note-to-self
2025-06-13 17:24:48 +00:00
Marcel Hibbe
6aab2e27cd
Merge pull request from nextcloud/issue-5051-seekbar-illegal-state-exception
Better null and error handling when playing voice messages
2025-06-13 12:44:44 +00:00
Marcel Hibbe
a34ad80a90
remove comment
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-13 14:14:06 +02:00
rapterjet2004
5a22f27b64
Adding a Short cut to note-to-self
Translations, and focuses on edittext upon opening

Signed-off-by: rapterjet2004 <juliuslinus1@gmail.com>
2025-06-13 13:43:00 +02:00
Nextcloud bot
776ba77c3a
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-13 03:04:33 +00:00
Andy Scherzinger
3cf01d9123
Merge pull request from nextcloud/repo-sync/android-config/master
🔄 synced file(s) with nextcloud/android-config
2025-06-12 07:28:45 +02:00
nextcloud-android-bot
2ab9f168d2 🔄 synced local '.github/workflows/' with remote 'config/workflows/'
Signed-off-by: nextcloud-android-bot <android@nextcloud.com>
2025-06-12 03:14:09 +00:00
github-actions[bot]
038a30dcca
Merge pull request from nextcloud/renovate/com.github.spotbugs.snom-spotbugs-gradle-plugin-6.x
fix(deps): update dependency com.github.spotbugs.snom:spotbugs-gradle-plugin to v6.2.0
2025-06-11 10:53:34 +02:00
github-actions[bot]
ddc40537d5
Merge pull request from nextcloud/renovate/ksp-monorepo
chore(deps): update plugin com.google.devtools.ksp to v2.1.21-2.0.2
2025-06-11 10:53:07 +02:00
renovate[bot]
bd23a05a88
fix(deps): update dependency com.github.spotbugs.snom:spotbugs-gradle-plugin to v6.2.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-11 07:21:21 +00:00
renovate[bot]
76f1e1c005
chore(deps): update plugin com.google.devtools.ksp to v2.1.21-2.0.2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-11 07:17:38 +00:00
Nextcloud bot
40c9816827
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-11 03:07:38 +00:00
rapterjet2004
a7f742931e
better null and error handling in the seekbar update observer
Signed-off-by: rapterjet2004 <juliuslinus1@gmail.com>
2025-06-10 11:20:42 -05:00
Marcel Hibbe
a361240692
Merge pull request from nextcloud/ci/noid/metaData
ci(chksm): Add meta-data
2025-06-10 10:21:50 +00:00
Andy Scherzinger
fe6897baf4
ci(chksm): Add meta-data
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-06-10 11:46:53 +02:00
Nextcloud bot
18578521cf
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-10 03:05:47 +00:00
Nextcloud Android Bot
4b71c50fb2 Weekly 22.0.0 Alpha 05 2025-06-09 03:23:12 +00:00
github-actions[bot]
7ec7904691
Merge pull request from nextcloud/renovate/junit5-monorepo
fix(deps): update dependency org.junit.vintage:junit-vintage-engine to v5.13.1
2025-06-08 20:20:57 +02:00
github-actions[bot]
34992438ed
Merge pull request from nextcloud/renovate/com.mebigfatguy.fb-contrib-fb-contrib-7.x
fix(deps): update dependency com.mebigfatguy.fb-contrib:fb-contrib to v7.6.10
2025-06-08 20:17:24 +02:00
github-actions[bot]
af721510e1
Merge pull request from nextcloud/renovate/androidx.appcompat-appcompat-1.x
fix(deps): update dependency androidx.appcompat:appcompat to v1.7.1
2025-06-08 20:04:51 +02:00
renovate[bot]
393c70f6e6
fix(deps): update dependency org.junit.vintage:junit-vintage-engine to v5.13.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 17:57:26 +00:00
renovate[bot]
d28bcbd5e9
fix(deps): update dependency com.mebigfatguy.fb-contrib:fb-contrib to v7.6.10
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 17:56:11 +00:00
renovate[bot]
03e3737e69
fix(deps): update dependency androidx.appcompat:appcompat to v1.7.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 17:52:40 +00:00
github-actions[bot]
a5fe9ec133
Merge pull request from nextcloud/renovate/gradle-8.x
chore(deps): update dependency gradle to v8.14.2
2025-06-08 17:20:36 +00:00
renovate[bot]
6633ae223c
chore(deps): update dependency gradle to v8.14.2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 16:43:20 +00:00
Andy Scherzinger
1dd9d6ba7b
Merge pull request from nextcloud/renovate/ubuntu-noble
chore(deps): update ubuntu:noble docker digest to b59d215
2025-06-08 17:53:21 +02:00
renovate[bot]
1a00678e46
chore(deps): update ubuntu:noble docker digest to b59d215
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 15:52:12 +00:00
github-actions[bot]
77944b4367
Merge pull request from nextcloud/renovate/retrofit-monorepo
fix(deps): update retrofit monorepo to v2.12.0
2025-06-08 15:35:04 +00:00
Andy Scherzinger
9b889ef751
ci(lint): Bump score to reflect main branch state
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-06-08 16:57:57 +02:00
Andy Scherzinger
ccecb9005c
ci(chksm): Add meta-data
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-06-08 16:15:26 +02:00
renovate[bot]
ccb700376a
fix(deps): update retrofit monorepo to v2.12.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-08 16:00:22 +02:00
Nextcloud bot
f574359432
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-08 03:05:03 +00:00
Andy Scherzinger
ba690c0615
Merge pull request from nextcloud/repo-sync/android-config/master
🔄 synced file(s) with nextcloud/android-config
2025-06-07 08:13:45 +02:00
nextcloud-android-bot
ccf955f43e 🔄 synced local '.github/workflows/' with remote 'config/workflows/'
Signed-off-by: nextcloud-android-bot <android@nextcloud.com>
2025-06-07 03:11:25 +00:00
Nextcloud bot
856b1ebf8c
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-07 03:03:38 +00:00
Nextcloud bot
b1448e13b5
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-06 03:12:36 +00:00
Marcel Hibbe
224eb3d0e8
update changelog for v21.1.0
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-05 12:38:49 +02:00
Nextcloud bot
4158628475
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-05 03:07:45 +00:00
Sowjanya Kota
e88bd1caf7
Merge pull request from nextcloud/bugfix/4983/fixCrashForHttp405ForProfileData
Profile data in ConversationInfo: catch http 405 if endpoint is not available
2025-06-04 13:43:59 +02:00
Marcel Hibbe
27ba2acf86
catch http 405 if endpoint is not available.
E.g. for older server versions

Without this fix there would be the crash:

 E  FATAL EXCEPTION: main
 Process: com.nextcloud.talk2, PID: 7161
  retrofit2.HttpException: HTTP 405
  at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:53)
  at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:164)
  at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
  at java.lang.Thread.run(Thread.java:1012)
Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@4a67b41, Dispatchers.Main.immediate]

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-04 13:10:42 +02:00
Marcel Hibbe
4efba2b953
Merge pull request from nextcloud/ui_enhancements
UI improvements to automatic conversation deletion information for event, sip and instant meetings conversation
2025-06-04 07:51:27 +00:00
Marcel Hibbe
f64e9e7c66
Merge pull request from nextcloud/issue-5018-dark-mode-bug-fix
Fixes message timestamp color
2025-06-04 07:35:54 +00:00
Nextcloud bot
6cd8718ac8
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-04 03:06:30 +00:00
Marcel Hibbe
eb72c70520
Merge pull request from nextcloud/fixToNotShowContextSearchAccidentally
simplify how search results are shown
2025-06-03 17:24:12 +00:00
rapterjet2004
ca06333c48
Fixes message timestamp color
Signed-off-by: rapterjet2004 <juliuslinus1@gmail.com>
2025-06-03 12:09:52 -05:00
Sowjanya Kota
325ffc4443
Merge pull request from nextcloud/fixCanDeleteCanLeaveForConvMenu
Fix & rearrange options in conversation button menu
2025-06-03 18:50:22 +02:00
sowjanyakch
89bddbd8fd
format
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-06-03 18:35:10 +02:00
sowjanyakch
de62d2776c
remove menu item after pressing keep button
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-06-03 18:35:10 +02:00
sowjanyakch
9aa1622929
popup menu color
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-06-03 18:35:10 +02:00
sowjanyakch
9dbb7ab703
consistent color to text and icon
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-06-03 18:35:09 +02:00
Marcel Hibbe
8307a9a0df
Merge pull request from nextcloud/mentions_complete
Include mentions in the attachments
2025-06-03 12:24:16 +00:00
Marcel Hibbe
a354ca197e
simplify how search results are shown
1. reverting changes from commit 971cc79b76. The scroll to methods are used in other places and a user reported that the search window suddenly opened without to search.
2. remove checkIfMessageIsSaved method. If the message is saved or not does not matter. The only fact for "scroll to" would be that it's loaded in the adapter.
3. As the adapter is always null with the current implementation (when coming back from the first search window), the "scroll to" is replaced with the startContextChatWindowForMessage. This could be changed again when adapter is not null anymore after coming back from the other screen...

startContextChatWindowForMessage

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-03 14:10:07 +02:00
Marcel Hibbe
12cb0825ca
show "Archive conversation" just before Leave & Delete
same as for web/iOS

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-03 12:17:32 +02:00
Marcel Hibbe
cc8e241213
show "Delete conversation" as last entry
same as for web/iOS

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-03 12:16:16 +02:00
Marcel Hibbe
14fd9b4af8
rely on conversationOperationLeave to show "Leave conversation" button
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-03 12:12:04 +02:00
Marcel Hibbe
9fe39603c3
rely on canDeleteConversation to show "Delete conversation" button
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-06-03 12:09:50 +02:00
Nextcloud bot
707371603d
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-03 03:05:04 +00:00
sowjanyakch
632a26d3cb
format
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-06-02 15:28:50 +02:00
sowjanyakch
262205b615
Include mentions in the attachments
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-06-02 14:59:32 +02:00
Nextcloud bot
131723317b
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-02 03:39:23 +00:00
Nextcloud Android Bot
e299023ef1 Weekly 22.0.0 Alpha 04 2025-06-02 03:11:10 +00:00
Nextcloud bot
92e1028354
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-01 03:03:48 +00:00
Andy Scherzinger
ebc41c33d0
Merge pull request from nextcloud/repo-sync/android-config/master
🔄 synced file(s) with nextcloud/android-config
2025-05-31 09:05:48 +02:00
nextcloud-android-bot
b11e5a474c 🔄 synced local '.github/workflows/' with remote 'config/workflows/'
Signed-off-by: nextcloud-android-bot <android@nextcloud.com>
2025-05-31 03:08:55 +00:00
Nextcloud bot
54ac15d089
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-31 03:04:30 +00:00
github-actions[bot]
b3c7990c88
Merge pull request from nextcloud/renovate/junit5-monorepo
Update dependency org.junit.vintage:junit-vintage-engine to v5.13.0
2025-05-30 21:17:46 +02:00
github-actions[bot]
88ed309220
Merge pull request from nextcloud/renovate/com.github.spotbugs.snom-spotbugs-gradle-plugin-6.x
Update dependency com.github.spotbugs.snom:spotbugs-gradle-plugin to v6.1.13
2025-05-30 21:15:00 +02:00
github-actions[bot]
5855ec7b38
Merge pull request from nextcloud/renovate/com.android.tools.build-gradle-8.x
Update dependency com.android.tools.build:gradle to v8.10.1
2025-05-30 21:07:58 +02:00
Marcel Hibbe
6f4cc903f8
Merge pull request from nextcloud/bugfix/4983/tryToAvoidCrashInConvInfo
try to avoid crash in conversation info
2025-05-30 14:17:02 +00:00
Marcel Hibbe
c01e058ff9
Merge pull request from nextcloud/initializeRootEglBaseEarlier
initialize rootEglBase earlier (hopefully fixes crash)
2025-05-30 14:15:17 +00:00
Marcel Hibbe
a559fd5ea6
try to avoid crash in conversation info
could not reproduce.
Code copied from Julius' PR https://github.com/nextcloud/talk-android/pull/5004/

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-05-30 16:10:20 +02:00
renovate[bot]
d33081c387
Update dependency org.junit.vintage:junit-vintage-engine to v5.13.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-30 14:02:57 +00:00
renovate[bot]
b2e9429cb5
Update dependency com.github.spotbugs.snom:spotbugs-gradle-plugin to v6.1.13
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-30 14:01:21 +00:00
renovate[bot]
073a0462d9
Update dependency com.android.tools.build:gradle to v8.10.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-30 13:58:22 +00:00
Marcel Hibbe
c8b33a380a
initialize rootEglBase earlier (hopefully fixes crash)
There was a report that the app crashed after coming back from PIP mode.

And i once saw in logs

"call to OpenGL ES API with no current context (logged once per thread)"

By initializing rootEglBase very early there is a chance this fixes these issues.

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-05-30 15:34:20 +02:00
Marcel Hibbe
78a88a9ce5
Merge pull request from nextcloud/issue-4966-fix-fetch-users
Getting users to show up on query
2025-05-30 13:22:00 +00:00
rapterjet2004
1ccc3ebb94
Getting users to show up on query
Signed-off-by: rapterjet2004 <juliuslinus1@gmail.com>
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-05-30 14:43:22 +02:00
Marcel Hibbe
89d51837b7
Merge pull request from nextcloud/add_button_to_add_participants
"Add" button to add participants
2025-05-30 12:07:39 +00:00
Marcel Hibbe
6ec4e05cb1
suppress kltint warning about Property name
not sure why "Property name should start with a lowercase letter and use camel case (cannot be auto-corrected)" is shown.

also see commit
523b8080d1

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-05-30 09:49:00 +02:00
sowjanyakch
87f8272b10
ktlintFormat
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:49:00 +02:00
sowjanyakch
05815ebeae
bug with search view alignment
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:59 +02:00
sowjanyakch
6d4e5d2774
hide added groups/circles
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:59 +02:00
sowjanyakch
f0cbe5113f
remove add width ratio
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:59 +02:00
sowjanyakch
4f8584bc75
remove already selected participants from the list
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:59 +02:00
sowjanyakch
652dd5033a
use constants and proper names
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:59 +02:00
sowjanyakch
6e48b86940
suppress lint - property name
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:58 +02:00
sowjanyakch
18b75233a5
lintformat
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:58 +02:00
sowjanyakch
b9e9d0ccd1
show selected participants when clicking on add button
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:58 +02:00
sowjanyakch
c3ebeebcb0
update contacts list after clicking add button
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:58 +02:00
sowjanyakch
c5c129b706
show add button even when search query is empty
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:58 +02:00
sowjanyakch
96541b7ad8
enable add button only when you select contacts
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:57 +02:00
sowjanyakch
e689e4f7f8
adjust search component width
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:57 +02:00
sowjanyakch
ebe374c9cb
lint format
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:57 +02:00
sowjanyakch
350bba1b95
detekt
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:57 +02:00
sowjanyakch
29d5293587
Add button to add participants
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-30 09:48:57 +02:00
Julius Linus
eb75f488c5
Merge pull request from nextcloud/simplifyCallGridLayoutForVoiceOnly
simplify call grid design logic for voice only calls
2025-05-29 10:20:56 -05:00
Marcel Hibbe
c349f120dc
Merge pull request from nextcloud/sensitive_conversation
Follow-up fixes for Sensitive conversation
2025-05-28 13:34:03 +00:00
Marcel Hibbe
6445633c94
simplify call grid design for voice only calls
this will remove the workaround regarding heightForNonGridComponents by using a BoxWithConstraints

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-05-28 15:04:05 +02:00
sowjanyakch
ac170f0803
adjust layout
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-28 10:21:50 +02:00
sowjanyakch
1fe356eee2
apply theming to switch
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-28 08:37:34 +02:00
Marcel Hibbe
b35bfc1ee7
Merge pull request from nextcloud/important_conversations
Important conversations
2025-05-27 13:51:22 +00:00
Marcel Hibbe
25639702f6
minor refactoring
Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-05-27 15:25:08 +02:00
sowjanyakch
6c5347ef72
ktlintFormat + suppress property name
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-27 14:38:21 +02:00
sowjanyakch
3f512c7b54
modify layout
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-27 14:38:21 +02:00
sowjanyakch
a943dc1070
modify database scheme
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-27 14:38:21 +02:00
sowjanyakch
2e41b27dc8
revert 14.json
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-27 14:38:21 +02:00
sowjanyakch
38482b8bb5
mark conversation as important
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-27 14:38:21 +02:00
sowjanyakch
62041d2581
refactor
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-27 14:38:20 +02:00
sowjanyakch
7548ce58b5
mark conversation as important / unimportant
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-27 14:38:20 +02:00
sowjanyakch
799d108708
add repo and repo implementation
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-27 14:38:20 +02:00
sowjanyakch
4803712ca3
add endpoints and hasImportant variable
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-27 14:38:19 +02:00
Marcel Hibbe
8db97fe0d9
Merge pull request from nextcloud/archive_conversation
hide archive conversations from conversation list in default view
2025-05-27 10:54:01 +00:00
Nextcloud bot
635624006d
fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-27 10:36:30 +00:00
Marcel Hibbe
d3acf3078b
Merge pull request from nextcloud/fixStringsxmlNoLinebreak
remove linebreak
2025-05-27 10:33:40 +00:00
Marcel Hibbe
85a7bd010b
remove linebreak
...should fix transifex

also add comment for this

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
2025-05-27 12:31:44 +02:00
sowjanyakch
e7d3af1a21
hide archive conversation initially
Signed-off-by: sowjanyakch <sowjanya.kch@gmail.com>
2025-05-26 20:04:23 +02:00
github-actions[bot]
6a8cb99aeb
Merge pull request from nextcloud/renovate/com.github.spotbugs.snom-spotbugs-gradle-plugin-6.x
fix(deps): update dependency com.github.spotbugs.snom:spotbugs-gradle-plugin to v6.1.12
2025-05-26 16:17:49 +00:00
Andy Scherzinger
29054d9062
Merge pull request from nextcloud/renovate/gradle-actions-4.x
chore(deps): update gradle/actions action to v4.4.0
2025-05-26 18:11:05 +02:00
renovate[bot]
ff408cd988
chore(deps): update gradle/actions action to v4.4.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-25 18:28:50 +00:00
renovate[bot]
9289f3662d
fix(deps): update dependency com.github.spotbugs.snom:spotbugs-gradle-plugin to v6.1.12
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-25 18:28:45 +00:00
Andy Scherzinger
71c8719f88
ci: Update signature
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-02-17 19:09:06 +01:00
renovate[bot]
2afa9ca80a chore(deps): update ghcr.io/nextcloud/continuous-integration-android8 docker tag to v4
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-17 19:07:42 +01:00
249 changed files with 7952 additions and 1240 deletions
.devcontainer
.drone.yml
.github/workflows
CHANGELOG.md
app
build.gradle
schemas/com.nextcloud.talk.data.source.local.TalkDatabase
src/main
java/com/nextcloud/talk
PhoneUtils.kt
account
activities
adapters
api
bottomsheet/items
call/components
chat
components
contacts
conversationcreation
conversationinfo
conversationinfoedit
conversationlist
data
diagnose
fullscreenfile
invitation
location
messagesearch
models
openconversations
profile
receivers
repositories/conversations
settings
shareditems/activities
translate/ui
ui/dialog
utils
res

View File

@ -1,4 +1,4 @@
FROM ubuntu:noble@sha256:6015f66923d7afbc53558d7ccffd325d43b4e249f41a6e93eef074c9505d2233
FROM ubuntu:noble@sha256:440dcf6a5640b2ae5c77724e68787a906afb8ddee98bf86db94eea8528c2c076
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:3
image: ghcr.io/nextcloud/continuous-integration-android8:4
commands:
- ./gradlew --console=plain assembleGeneric
@ -27,7 +27,7 @@ name: gplay
steps:
- name: gplay
image: ghcr.io/nextcloud/continuous-integration-android8:3
image: ghcr.io/nextcloud/continuous-integration-android8:4
commands:
- ./gradlew --console=plain assembleGplay
@ -46,7 +46,7 @@ name: tests
steps:
- name: all
image: ghcr.io/nextcloud/continuous-integration-android8:3
image: ghcr.io/nextcloud/continuous-integration-android8:4
privileged: true
commands:
- emulator -avd android -no-snapshot -gpu swiftshader_indirect -no-window -no-audio -skin 500x833 &
@ -81,4 +81,6 @@ trigger:
- pull_request
---
kind: signature
hmac: cdce3f7eea46ef85c0223f62f66d1fe53d7dad007ef095c9f70fa063450d8c75
hmac: cf0c19e54fa45d1ee226f5f05202a32329b90aaf46711ea073c566a4c4a8a6c5
...

View File

@ -29,7 +29,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Disabled on forks
if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
if: ${{ github.event_name == 'pull_request' && 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@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1
uses: gradle/actions/wrapper-validation@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.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@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
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@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2

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@1883b38a033fb16f576875e0cf45f98b857655c4 # main
- uses: nextcloud/pr-feedback-action@e397f3c7e655092b746e3610d121545530c6a90e # main
with:
feedback-message: |
Hello there,

View File

@ -29,8 +29,6 @@ jobs:
permissions:
# for hmarr/auto-approve-action to approve PRs
pull-requests: write
# for alexwilson/enable-github-automerge-action to approve PRs
contents: write
steps:
- name: Disabled on forks
@ -46,13 +44,18 @@ jobs:
# GitHub actions bot approve
- uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 # v4.0.0
if: startsWith(steps.branchname.outputs.branch, 'renovate/')
if: github.actor == 'renovate[bot]'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
# Enable GitHub auto merge
- name: Auto merge
uses: alexwilson/enable-github-automerge-action@56e3117d1ae1540309dc8f7a9f2825bc3c5f06ff # v2.0.0
if: startsWith(steps.branchname.outputs.branch, 'renovate/')
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ github.head_ref }}
# Enable GitHub auto merge
- name: Enable Pull Request Automerge
if: github.actor == 'renovate[bot]'
run: gh pr merge --merge --auto
env:
GH_TOKEN: ${{ secrets.AUTOMERGE }}

View File

@ -12,11 +12,11 @@ name: REUSE Compliance Check
on: [pull_request]
permissions:
contents: read
contents: read
jobs:
reuse-compliance-check:
runs-on: ubuntu-latest
runs-on: ubuntu-latest-low
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

View File

@ -34,7 +34,7 @@ jobs:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
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@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with:
sarif_file: results.sarif

View File

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

View File

@ -9,6 +9,36 @@ 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

@ -13,9 +13,9 @@ import com.github.spotbugs.snom.Effort
import com.github.spotbugs.snom.SpotBugsTask
plugins {
id "org.jetbrains.kotlin.plugin.compose" version "2.1.21"
id "org.jetbrains.kotlin.plugin.compose" version "2.2.0"
id "org.jetbrains.kotlin.kapt"
id 'com.google.devtools.ksp' version '2.1.21-2.0.1'
id 'com.google.devtools.ksp' version '2.2.0-2.0.2'
}
apply plugin: 'com.android.application'
@ -28,19 +28,19 @@ apply plugin: "org.jlleitschuh.gradle.ktlint"
apply plugin: 'kotlinx-serialization'
android {
compileSdk 34
compileSdk 35
namespace 'com.nextcloud.talk'
defaultConfig {
minSdkVersion 26
targetSdkVersion 34
targetSdkVersion 35
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
// mayor.minor.hotfix.increment (for increment: 01-50=Alpha / 51-89=RC / 90-99=stable)
// xx .xxx .xx .xx
versionCode 220000003
versionName "22.0.0 Alpha 03"
versionCode 220000009
versionName "22.0.0 Alpha 09"
flavorDimensions "default"
renderscriptTargetApi 19
@ -155,18 +155,18 @@ ext {
daggerVersion = "2.56.2"
emojiVersion = "1.5.0"
fidoVersion = "4.1.0-patch2"
lifecycleVersion = '2.8.7'
lifecycleVersion = '2.9.1'
okhttpVersion = "4.12.0"
markwonVersion = "4.6.2"
materialDialogsVersion = "3.3.0"
parcelerVersion = "1.1.13"
prismVersion = "2.0.0"
retrofit2Version = "2.11.0"
roomVersion = "2.7.1"
workVersion = "2.9.1"
retrofit2Version = "3.0.0"
roomVersion = "2.7.2"
workVersion = "2.10.2"
espressoVersion = "3.6.1"
androidxTestVersion = "1.5.0"
media3_version = "1.4.1"
media3_version = "1.7.1"
coroutines_version = "1.10.2"
mockitoKotlinVersion = "5.4.0"
}
@ -180,10 +180,10 @@ configurations.configureEach {
dependencies {
spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0'
spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.9'
spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.11'
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.8")
implementation("androidx.compose.runtime:runtime:1.7.8")
implementation("androidx.compose.runtime:runtime:1.8.3")
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.datastore:datastore-core:1.1.7'
implementation 'androidx.datastore:datastore-preferences:1.1.7'
@ -191,9 +191,9 @@ dependencies {
implementation fileTree(include: ['*'], dir: 'libs')
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0"
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation "com.vanniktech:emoji-google:0.21.0"
@ -236,7 +236,7 @@ dependencies {
implementation "com.squareup.okhttp3:logging-interceptor:${okhttpVersion}"
implementation 'com.bluelinelabs:logansquare:1.3.7'
implementation 'com.fasterxml.jackson.core:jackson-core:2.14.3'
implementation 'com.fasterxml.jackson.core:jackson-core:2.19.1'
kapt 'com.bluelinelabs:logansquare-compiler:1.3.7'
implementation "com.squareup.retrofit2:retrofit:${retrofit2Version}"
@ -301,24 +301,24 @@ dependencies {
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
})
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.activity:activity-ktx:1.9.3'
implementation 'com.github.nextcloud.android-common:ui:0.23.2'
implementation 'androidx.core:core-ktx:1.16.0'
implementation 'androidx.activity:activity-ktx:1.10.1'
implementation 'com.github.nextcloud.android-common:ui:0.26.0'
implementation 'com.github.nextcloud-deps:android-talk-webrtc:132.6834.0'
gplayImplementation 'com.google.android.gms:play-services-base:18.6.0'
gplayImplementation "com.google.firebase:firebase-messaging:24.1.1"
gplayImplementation "com.google.firebase:firebase-messaging:24.1.2"
//compose
implementation(platform("androidx.compose:compose-bom:2025.04.00"))
implementation(platform("androidx.compose:compose-bom:2025.06.01"))
implementation("androidx.compose.ui:ui")
implementation 'androidx.compose.material3:material3:1.3.2'
implementation("androidx.compose.ui:ui-tooling-preview")
implementation 'androidx.activity:activity-compose:1.9.3'
implementation 'androidx.activity:activity-compose:1.10.1'
debugImplementation("androidx.compose.ui:ui-tooling")
//tests
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.7.8")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.8.3")
debugImplementation("androidx.compose.ui:ui-test-manifest")
testImplementation 'junit:junit:4.13.2'
@ -343,11 +343,11 @@ dependencies {
androidTestImplementation('com.android.support.test.espresso:espresso-intents:3.0.2')
androidTestImplementation(platform("androidx.compose:compose-bom:2025.04.00"))
androidTestImplementation(platform("androidx.compose:compose-bom:2025.06.01"))
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
testImplementation 'org.junit.vintage:junit-vintage-engine:5.12.2'
testImplementation 'org.junit.vintage:junit-vintage-engine:5.13.3'
}
tasks.register('installGitHooks', Copy) {

View File

@ -0,0 +1,731 @@
{
"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

@ -0,0 +1,730 @@
{
"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

@ -0,0 +1,14 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Sowjanya Kota <sowjanya.kch@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk
object PhoneUtils {
fun isPhoneNumber(input: String?): Boolean {
return input?.matches(Regex("^\\+?\\d+$")) == true
}
}

View File

@ -91,7 +91,7 @@ class AccountVerificationActivity : BaseActivity() {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
setContentView(binding.root)
actionBar?.hide()
setupSystemColors()
initSystemBars()
handleIntent()
}
@ -490,9 +490,9 @@ class AccountVerificationActivity : BaseActivity() {
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
.observeForever { workInfo: WorkInfo ->
.observeForever { workInfo: WorkInfo? ->
when (workInfo.state) {
when (workInfo?.state) {
WorkInfo.State.SUCCEEDED -> {
val intent = Intent(this, ServerSelectionActivity::class.java)
startActivity(intent)

View File

@ -78,7 +78,7 @@ class ServerSelectionActivity : BaseActivity() {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
setContentView(binding.root)
actionBar?.hide()
setupSystemColors()
initSystemBars()
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
}

View File

@ -86,7 +86,7 @@ class SwitchAccountActivity : BaseActivity() {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
setContentView(binding.root)
setupActionBar()
setupSystemColors()
initSystemBars()
Configuration.getInstance().load(context, PreferenceManager.getDefaultSharedPreferences(context))

View File

@ -114,7 +114,7 @@ class WebViewLoginActivity : BaseActivity() {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
setContentView(binding.root)
actionBar?.hide()
setupSystemColors()
initSystemBars()
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
handleIntent()
@ -286,6 +286,7 @@ class WebViewLoginActivity : BaseActivity() {
}
}
@SuppressLint("DiscouragedPrivateApi")
@Suppress("Detekt.TooGenericExceptionCaught")
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
try {
@ -388,9 +389,9 @@ class WebViewLoginActivity : BaseActivity() {
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
.observeForever { workInfo: WorkInfo ->
.observeForever { workInfo: WorkInfo? ->
when (workInfo.state) {
when (workInfo?.state) {
WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
restartApp()
}

View File

@ -11,11 +11,13 @@ package com.nextcloud.talk.activities
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.webkit.SslErrorHandler
@ -37,6 +39,7 @@ import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.FileViewerUtils
import com.nextcloud.talk.utils.UriUtils
import com.nextcloud.talk.utils.adjustUIForAPILevel35
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.preferences.AppPreferences
@ -81,6 +84,7 @@ open class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
adjustUIForAPILevel35()
super.onCreate(savedInstanceState)
cleanTempCertPreference()
@ -111,9 +115,22 @@ open class BaseActivity : AppCompatActivity() {
eventBus.unregister(this)
}
fun setupSystemColors() {
colorizeStatusBar()
colorizeNavigationBar()
/*
* May be aligned with android-common lib in the future: .../ui/util/extensions/AppCompatActivityExtensions.kt
*/
fun initSystemBars() {
window.decorView.setOnApplyWindowInsetsListener { view, insets ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
val statusBarHeight = insets.getInsets(WindowInsets.Type.statusBars()).top
view.setPadding(0, statusBarHeight, 0, 0)
val color = ResourcesCompat.getColor(resources, R.color.bg_default, context.theme)
view.setBackgroundColor(color)
} else {
colorizeStatusBar()
colorizeNavigationBar()
}
insets
}
}
open fun colorizeStatusBar() {

View File

@ -376,6 +376,8 @@ 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()
@ -765,7 +767,6 @@ class CallActivity : CallBaseActivity() {
}
private fun basicInitialization() {
rootEglBase = EglBase.create()
createCameraEnumerator()
// Create a new PeerConnectionFactory instance.
@ -947,8 +948,7 @@ class CallActivity : CallBaseActivity() {
ParticipantGrid(
participantUiStates = participantUiStates,
eglBase = rootEglBase!!,
isVoiceOnlyCall = isVoiceOnlyCall,
isInPipMode = isInPipMode
isVoiceOnlyCall = isVoiceOnlyCall
) {
animateCallControls(true, 0)
}

View File

@ -194,7 +194,6 @@ class ContactItem(
}
companion object {
const val VIEW_TYPE = FlexibleItemViewType.CONTACT_ITEM
private const val FULLY_OPAQUE: Float = 1.0f
private const val SEMI_TRANSPARENT: Float = 0.38f
}

View File

@ -19,6 +19,7 @@ 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
@ -155,6 +156,30 @@ 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)
}
@ -406,9 +431,9 @@ class ConversationItem(
)
return lastMessage
} else if (MessageType.SINGLE_NC_ATTACHMENT_MESSAGE == chatMessage?.getCalculateMessageType()) {
var attachmentName = chatMessage.message
var attachmentName = chatMessage.text
if (attachmentName == "{file}") {
attachmentName = chatMessage.messageParameters?.get("file")?.get("name")
attachmentName = chatMessage.messageParameters?.get("file")?.get("name") ?: ""
}
val author = authorName(chatMessage)

View File

@ -14,5 +14,4 @@ object FlexibleItemViewType {
const val POLL_RESULT_HEADER_ITEM: Int = 1120391234
const val POLL_RESULT_VOTER_ITEM: Int = 1120391235
const val POLL_RESULT_VOTERS_OVERVIEW_ITEM: Int = 1120391236
const val CONTACT_ITEM: Int = 2131558687
}

View File

@ -15,6 +15,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.ResourcesCompat
import coil.Coil
import coil.request.ImageRequest
import com.nextcloud.talk.PhoneUtils.isPhoneNumber
import com.nextcloud.talk.R
import com.nextcloud.talk.adapters.items.ParticipantItem.ParticipantItemViewHolder
import com.nextcloud.talk.data.user.model.User
@ -129,14 +130,22 @@ class MentionAutocompleteItem(
private fun setAvatar(holder: ParticipantItemViewHolder, objectId: String?) {
when (source) {
SOURCE_CALLS -> {
run {}
run {
holder.binding.avatarView.loadUserAvatar(
viewThemeUtils.talk.themePlaceholderAvatar(
holder.binding.avatarView,
R.drawable.ic_avatar_group
if (isPhoneNumber(displayName)) {
holder.binding.avatarView.loadUserAvatar(
viewThemeUtils.talk.themePlaceholderAvatar(
holder.binding.avatarView,
R.drawable.ic_phone_small
)
)
)
} else {
holder.binding.avatarView.loadUserAvatar(
viewThemeUtils.talk.themePlaceholderAvatar(
holder.binding.avatarView,
R.drawable.ic_avatar_group
)
)
}
}
}

View File

@ -0,0 +1,41 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.adapters.items
import android.view.View
import com.nextcloud.talk.R
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
class SpacerItem(private val height: Int) : AbstractFlexibleItem<SpacerItem.ViewHolder>() {
override fun getLayoutRes(): Int = R.layout.item_spacer
override fun createViewHolder(view: View?, adapter: FlexibleAdapter<IFlexible<*>?>?): ViewHolder {
return ViewHolder(view!!, adapter!!)
}
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<*>?>?,
holder: ViewHolder,
position: Int,
payloads: MutableList<Any>?
) {
holder.itemView.layoutParams.height = height
}
override fun equals(other: Any?) = other is SpacerItem
override fun hashCode(): Int {
return 0
}
class ViewHolder(view: View, adapter: FlexibleAdapter<*>) :
FlexibleViewHolder(view, adapter)
}

View File

@ -20,6 +20,7 @@ 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
@ -157,7 +158,7 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
binding.messageEditIndicator.visibility = View.GONE
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
}
binding.messageTime.setTextColor(ContextCompat.getColor(context, R.color.no_emphasis_text))
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
// parent message handling
if (!message.isDeleted && message.parentMessageId != null) {
processParentMessage(message)

View File

@ -29,6 +29,7 @@ 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
@ -105,7 +106,6 @@ 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
@ -172,7 +172,7 @@ class OutcomingTextMessageViewHolder(itemView: View) :
binding.messageEditIndicator.visibility = View.GONE
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
}
binding.messageTime.setTextColor(ContextCompat.getColor(context, R.color.no_emphasis_text))
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
setBubbleOnChatMessage(message)
// parent message handling
if (!message.isDeleted && message.parentMessageId != null) {
@ -185,7 +185,7 @@ class OutcomingTextMessageViewHolder(itemView: View) :
binding.checkMark.visibility = View.INVISIBLE
binding.sendingProgress.visibility = View.GONE
if (message.sendingFailed) {
if (message.sendStatus == SendStatus.FAILED) {
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

@ -179,6 +179,18 @@ 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,

View File

@ -6,6 +6,7 @@
*/
package com.nextcloud.talk.bottomsheet.items
import android.annotation.SuppressLint
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
@ -65,6 +66,7 @@ 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

@ -9,14 +9,15 @@
package com.nextcloud.talk.call.components
import android.annotation.SuppressLint
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
@ -29,6 +30,7 @@ import com.nextcloud.talk.adapters.ParticipantUiState
import org.webrtc.EglBase
import kotlin.math.ceil
@SuppressLint("UnusedBoxWithConstraintsScope")
@Suppress("LongParameterList")
@Composable
fun ParticipantGrid(
@ -36,7 +38,6 @@ fun ParticipantGrid(
eglBase: EglBase?,
participantUiStates: List<ParticipantUiState>,
isVoiceOnlyCall: Boolean,
isInPipMode: Boolean,
onClick: () -> Unit
) {
val configuration = LocalConfiguration.current
@ -44,63 +45,61 @@ fun ParticipantGrid(
val minItemHeight = 100.dp
val columns =
if (isPortrait) {
when (participantUiStates.size) {
1, 2, 3 -> 1
else -> 2
}
} else {
when (participantUiStates.size) {
1 -> 1
2, 4 -> 2
else -> 3
}
if (participantUiStates.isEmpty()) return
val columns = if (isPortrait) {
when (participantUiStates.size) {
1, 2, 3 -> 1
else -> 2
}
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 {
0.dp
}
when (participantUiStates.size) {
1 -> 1
2, 4 -> 2
else -> 3
}
}.coerceAtLeast(1) // Prevent 0
val rows = ceil(participantUiStates.size / columns.toFloat()).toInt().coerceAtLeast(1)
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
val rawItemHeight = availableHeight / rows
val itemHeight = maxOf(rawItemHeight, minItemHeight)
LazyVerticalGrid(
columns = GridCells.Fixed(columns),
modifier = Modifier
BoxWithConstraints(
modifier = modifier
.fillMaxSize()
.padding(horizontal = edgePadding)
.clickable { onClick() },
verticalArrangement = Arrangement.spacedBy(itemSpacing),
horizontalArrangement = Arrangement.spacedBy(itemSpacing),
contentPadding = PaddingValues(vertical = edgePadding)
.clickable { onClick() }
) {
items(
participantUiStates,
key = { it.sessionKey }
) { participant ->
ParticipantTile(
participantUiState = participant,
modifier = Modifier
.height(itemHeight)
.fillMaxWidth(),
eglBase = eglBase,
isVoiceOnlyCall = isVoiceOnlyCall
)
val availableHeight = maxHeight
val gridAvailableHeight = availableHeight - totalVerticalSpacing - totalVerticalPadding
val rawItemHeight = gridAvailableHeight / rows
val itemHeight = maxOf(rawItemHeight, minItemHeight)
LazyVerticalGrid(
columns = GridCells.Fixed(columns),
modifier = Modifier
.fillMaxWidth()
.height(availableHeight),
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
)
}
}
}
}
@ -111,8 +110,7 @@ fun ParticipantGridPreview() {
ParticipantGrid(
participantUiStates = getTestParticipants(1),
eglBase = null,
isVoiceOnlyCall = false,
isInPipMode = false
isVoiceOnlyCall = false
) {}
}
@ -122,8 +120,7 @@ fun TwoParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(2),
eglBase = null,
isVoiceOnlyCall = false,
isInPipMode = false
isVoiceOnlyCall = false
) {}
}
@ -133,8 +130,7 @@ fun ThreeParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(3),
eglBase = null,
isVoiceOnlyCall = false,
isInPipMode = false
isVoiceOnlyCall = false
) {}
}
@ -144,8 +140,7 @@ fun FourParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(4),
eglBase = null,
isVoiceOnlyCall = false,
isInPipMode = false
isVoiceOnlyCall = false
) {}
}
@ -155,8 +150,7 @@ fun FiveParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(5),
eglBase = null,
isVoiceOnlyCall = false,
isInPipMode = false
isVoiceOnlyCall = false
) {}
}
@ -166,8 +160,7 @@ fun SevenParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(7),
eglBase = null,
isVoiceOnlyCall = false,
isInPipMode = false
isVoiceOnlyCall = false
) {}
}
@ -177,8 +170,7 @@ fun FiftyParticipants() {
ParticipantGrid(
participantUiStates = getTestParticipants(50),
eglBase = null,
isVoiceOnlyCall = false,
isInPipMode = false
isVoiceOnlyCall = false
) {}
}
@ -192,8 +184,7 @@ fun OneParticipantLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(1),
eglBase = null,
isVoiceOnlyCall = false,
isInPipMode = false
isVoiceOnlyCall = false
) {}
}
@ -207,8 +198,7 @@ fun TwoParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(2),
eglBase = null,
isVoiceOnlyCall = false,
isInPipMode = false
isVoiceOnlyCall = false
) {}
}
@ -222,8 +212,7 @@ fun ThreeParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(3),
eglBase = null,
isVoiceOnlyCall = false,
isInPipMode = false
isVoiceOnlyCall = false
) {}
}
@ -237,8 +226,7 @@ fun FourParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(4),
eglBase = null,
isVoiceOnlyCall = false,
isInPipMode = false
isVoiceOnlyCall = false
) {}
}
@ -252,8 +240,7 @@ fun SevenParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(7),
eglBase = null,
isVoiceOnlyCall = false,
isInPipMode = false
isVoiceOnlyCall = false
) {}
}
@ -267,8 +254,7 @@ fun FiftyParticipantsLandscape() {
ParticipantGrid(
participantUiStates = getTestParticipants(50),
eglBase = null,
isVoiceOnlyCall = false,
isInPipMode = false
isVoiceOnlyCall = false
) {}
}

View File

@ -7,6 +7,7 @@
package com.nextcloud.talk.call.components
import android.annotation.SuppressLint
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
@ -40,6 +41,7 @@ const val NICK_OFFSET = 4f
const val NICK_BLUR_RADIUS = 4f
const val AVATAR_SIZE_FACTOR = 0.6f
@SuppressLint("UnusedBoxWithConstraintsScope")
@Suppress("Detekt.LongMethod")
@Composable
fun ParticipantTile(

View File

@ -16,7 +16,6 @@ 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
@ -67,6 +66,8 @@ import androidx.core.graphics.drawable.toDrawable
import androidx.core.net.toUri
import androidx.core.os.bundleOf
import androidx.core.text.bold
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.emoji2.text.EmojiCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.commit
@ -276,6 +277,8 @@ class ChatActivity :
lateinit var conversationInfoViewModel: ConversationInfoViewModel
lateinit var messageInputViewModel: MessageInputViewModel
private var chatMenu: Menu? = null
private val startSelectContactForResult = registerForActivityResult(
ActivityResultContracts
.StartActivityForResult()
@ -307,12 +310,7 @@ class ChatActivity :
runBlocking {
val id = intent?.getStringExtra(MessageSearchActivity.RESULT_KEY_MESSAGE_ID)
id?.let {
val isSaved = chatViewModel.isMessageSaved(id.toLong())
if (isSaved) {
onMessageSearchResult(intent)
} else {
startContextChatWindowForMessage(id)
}
startContextChatWindowForMessage(id)
}
}
}
@ -366,6 +364,7 @@ class ChatActivity :
var startCallFromRoomSwitch: Boolean = false
var voiceOnly: Boolean = true
var focusInput: Boolean = false
private lateinit var path: String
var myFirstMessage: CharSequence? = null
@ -461,7 +460,28 @@ class ChatActivity :
binding = ActivityChatBinding.inflate(layoutInflater)
setupActionBar()
setContentView(binding.root)
setupSystemColors()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.chat_container)) { view, insets ->
val statusBarInsets = insets.getInsets(WindowInsetsCompat.Type.statusBars())
val navBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
val imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime())
val isKeyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
val bottomPadding = if (isKeyboardVisible) imeInsets.bottom else navBarInsets.bottom
view.setPadding(
view.paddingLeft,
statusBarInsets.top,
view.paddingRight,
bottomPadding
)
WindowInsetsCompat.CONSUMED
}
} else {
colorizeStatusBar()
colorizeNavigationBar()
}
conversationUser = currentUserProvider.currentUser.blockingGet()
handleIntent(intent)
@ -489,7 +509,7 @@ class ChatActivity :
initObservers()
pickMultipleMedia = registerForActivityResult(
ActivityResultContracts.PickMultipleVisualMedia(5)
ActivityResultContracts.PickMultipleVisualMedia(MAX_AMOUNT_MEDIA_FILE_PICKER)
) { uris ->
if (uris.isNotEmpty()) {
onChooseFileResult(uris)
@ -549,6 +569,8 @@ 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() {
@ -640,12 +662,17 @@ 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 != "?" &&
CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MENTION_FLAG)
hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MENTION_FLAG)
) {
binding.chatToolbar.setOnClickListener { _ -> showConversationInfoScreen() }
}
@ -680,7 +707,7 @@ class ChatActivity :
?.split("#")
?.getOrNull(1)
?.toLongOrNull()
val currentTimeStamp = (System.currentTimeMillis() / 1000).toLong()
val currentTimeStamp = (System.currentTimeMillis() / ONE_SECOND_IN_MILLIS).toLong()
val retentionPeriod = retentionOfEventRooms(spreedCapabilities)
val isPastEvent = eventEndTimeStamp?.let { it < currentTimeStamp }
if (isPastEvent == true && retentionPeriod != 0) {
@ -1102,6 +1129,8 @@ class ChatActivity :
context.getString(R.string.nc_room_retention),
Snackbar.LENGTH_LONG
).show()
chatMenu?.removeItem(R.id.conversation_event)
}
is ChatViewModel.UnbindRoomUiState.Error -> {
Snackbar.make(
@ -1237,11 +1266,17 @@ class ChatActivity :
bringToFront()
}
val deleteNoticeText = binding.conversationDeleteNotice.findViewById<TextView>(R.id.deletion_message)
viewThemeUtils.material.themeCardView(binding.conversationDeleteNotice)
deleteNoticeText.text = String.format(
resources.getString(R.string.nc_conversation_auto_delete_notice),
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 =
@ -1958,8 +1993,8 @@ class ChatActivity :
WorkManager.getInstance().enqueue(downloadWorker)
WorkManager.getInstance(context).getWorkInfoByIdLiveData(downloadWorker.id)
.observeForever { workInfo: WorkInfo ->
if (workInfo.state == WorkInfo.State.SUCCEEDED) {
.observeForever { workInfo: WorkInfo? ->
if (workInfo?.state == WorkInfo.State.SUCCEEDED) {
funToCallWhenDownloadSuccessful()
}
}
@ -2042,7 +2077,7 @@ class ChatActivity :
private fun shouldShowLobby(): Boolean {
if (currentConversation != null) {
return CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.WEBINARY_LOBBY) &&
return hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.WEBINARY_LOBBY) &&
currentConversation?.lobbyState == ConversationEnums.LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
!ConversationUtils.canModerate(currentConversation!!, spreedCapabilities) &&
!participantPermissions.canIgnoreLobby()
@ -2297,15 +2332,8 @@ 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 == Activity.RESULT_OK) {
if (result.resultCode == RESULT_OK) {
onResult(result.data)
} else {
Log.e(TAG, "resultCode for received intent was != ok")
@ -2319,7 +2347,7 @@ class ChatActivity :
if (position != null && position >= 0) {
binding.messagesListView.scrollToPosition(position)
} else {
startContextChatWindowForMessage(messageId)
Log.d(TAG, "message $messageId that should be scrolled to was not found (scrollToMessageWithId)")
}
}
@ -2332,10 +2360,12 @@ class ChatActivity :
binding.messagesListView.height / 2
)
} else {
startContextChatWindowForMessage(messageId)
Log.d(
TAG,
"message $messageId that should be scrolled " +
"to was not found (scrollToAndCenterMessageWithId)"
)
}
} ?: run {
startContextChatWindowForMessage(messageId)
}
}
@ -2797,7 +2827,7 @@ class ChatActivity :
}
if (this::spreedCapabilities.isInitialized) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MESSAGE_EXPIRATION)) {
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MESSAGE_EXPIRATION)) {
deleteExpiredMessages()
}
} else {
@ -3044,6 +3074,7 @@ 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)
@ -3057,7 +3088,6 @@ class ChatActivity :
loadAvatarForStatusBar()
setActionBarTitle()
}
return true
}
@ -3065,7 +3095,7 @@ class ChatActivity :
super.onPrepareOptionsMenu(menu)
if (this::spreedCapabilities.isInitialized) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.READ_ONLY_ROOMS)) {
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.READ_ONLY_ROOMS)) {
checkShowCallButtons()
}
@ -3086,7 +3116,7 @@ class ChatActivity :
}.collect()
}
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.SILENT_CALL)) {
if (hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.SILENT_CALL)) {
Handler().post {
findViewById<View?>(R.id.conversation_voice_call)?.setOnLongClickListener {
showCallButtonMenu(true)
@ -3145,10 +3175,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(
@ -3597,7 +3627,7 @@ class ChatActivity :
fun copyMessage(message: IMessage?) {
val clipboardManager =
getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
val clipData = ClipData.newPlainText(
resources?.getString(R.string.nc_app_product_name),
message?.text
@ -3911,7 +3941,7 @@ class ChatActivity :
val isOlderThanSixHours = message
.createdAt
.before(Date(System.currentTimeMillis() - AGE_THRESHOLD_FOR_DELETE_MESSAGE))
val hasDeleteMessagesUnlimitedCapability = CapabilitiesUtil.hasSpreedFeatureCapability(
val hasDeleteMessagesUnlimitedCapability = hasSpreedFeatureCapability(
spreedCapabilities,
SpreedFeatures.DELETE_MESSAGES_UNLIMITED
)
@ -3921,7 +3951,7 @@ class ChatActivity :
!hasDeleteMessagesUnlimitedCapability && isOlderThanSixHours -> false
message.systemMessageType != ChatMessage.SystemMessageType.DUMMY -> false
message.isDeleted -> false
!CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.DELETE_MESSAGES) -> false
!hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.DELETE_MESSAGES) -> false
!participantPermissions.hasChatPermission() -> false
hasDeleteMessagesUnlimitedCapability -> true
else -> true
@ -4186,5 +4216,6 @@ class ChatActivity :
const val OUT_OF_OFFICE_ALPHA = 76
const val ZERO_INDEX = 0
const val ONE_INDEX = 1
const val MAX_AMOUNT_MEDIA_FILE_PICKER = 10
}
}

View File

@ -64,6 +64,7 @@ import com.nextcloud.talk.chat.viewmodels.ChatViewModel
import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.databinding.FragmentMessageInputBinding
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
import com.nextcloud.talk.models.json.chat.ChatUtils
import com.nextcloud.talk.models.json.mention.Mention
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage
import com.nextcloud.talk.presenters.MentionAutocompletePresenter
@ -77,6 +78,7 @@ import com.nextcloud.talk.utils.CharPolicy
import com.nextcloud.talk.utils.ImageEmojiEditText
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import com.nextcloud.talk.utils.message.MessageUtils
import com.nextcloud.talk.utils.text.Spans
import com.otaliastudios.autocomplete.Autocomplete
import com.stfalcon.chatkit.commons.models.IMessage
@ -124,6 +126,9 @@ class MessageInputFragment : Fragment() {
@Inject
lateinit var networkMonitor: NetworkMonitor
@Inject
lateinit var messageUtils: MessageUtils
lateinit var binding: FragmentMessageInputBinding
private lateinit var conversationInternalId: String
private var typedWhileTypingTimerIsRunning: Boolean = false
@ -200,7 +205,7 @@ class MessageInputFragment : Fragment() {
val connectionGained = (!wasOnline && isOnline)
Log.d(TAG, "isOnline: $isOnline\nwasOnline: $wasOnline\nconnectionGained: $connectionGained")
if (connectionGained) {
chatActivity.messageInputViewModel.sendTempMessages(
chatActivity.messageInputViewModel.sendUnsentMessages(
chatActivity.conversationUser!!.getCredentials(),
ApiUtils.getUrlForChat(
chatActivity.chatApiVersion,
@ -412,10 +417,22 @@ class MessageInputFragment : Fragment() {
}
binding.fragmentMessageInputView.editMessageButton.setOnClickListener {
val text = binding.fragmentMessageInputView.inputEditText.text.toString()
val editable = binding.fragmentMessageInputView.inputEditText!!.editableText
replaceMentionChipSpans(editable)
val inputEditText = editable.toString()
val message = chatActivity.messageInputViewModel.getEditChatMessage.value as ChatMessage
if (message.message!!.trim() != text.trim()) {
editMessageAPI(message, text)
if (message.message!!.trim() != inputEditText.trim()) {
if (message.messageParameters != null) {
val editedMessage = messageUtils.processEditMessageParameters(
message.messageParameters!!,
message,
inputEditText
)
editMessageAPI(message, editedMessage.toString())
} else {
editMessageAPI(message, inputEditText.toString())
}
}
clearEditUI()
}
@ -837,27 +854,7 @@ class MessageInputFragment : Fragment() {
private fun submitMessage(sendWithoutNotification: Boolean) {
if (binding.fragmentMessageInputView.inputEditText != null) {
val editable = binding.fragmentMessageInputView.inputEditText!!.editableText
val mentionSpans = editable.getSpans(
0,
editable.length,
Spans.MentionChipSpan::class.java
)
var mentionSpan: Spans.MentionChipSpan
for (i in mentionSpans.indices) {
mentionSpan = mentionSpans[i]
var mentionId = mentionSpan.id
val shouldQuote = mentionId.contains(" ") ||
mentionId.contains("@") ||
mentionId.startsWith("guest/") ||
mentionId.startsWith("group/") ||
mentionId.startsWith("email/") ||
mentionId.startsWith("team/")
if (shouldQuote) {
mentionId = "\"" + mentionId + "\""
}
editable.replace(editable.getSpanStart(mentionSpan), editable.getSpanEnd(mentionSpan), "@$mentionId")
}
replaceMentionChipSpans(editable)
binding.fragmentMessageInputView.inputEditText?.setText("")
sendStopTypingMessage()
val replyMessageId = binding.fragmentMessageInputView
@ -887,6 +884,31 @@ class MessageInputFragment : Fragment() {
)
}
private fun replaceMentionChipSpans(editable: Editable) {
val mentionSpans = editable.getSpans(
0,
editable.length,
Spans.MentionChipSpan::class.java
)
for (mentionSpan in mentionSpans) {
var mentionId = mentionSpan.id
val shouldQuote = mentionId.contains(" ") ||
mentionId.contains("@") ||
mentionId.startsWith("guest/") ||
mentionId.startsWith("group/") ||
mentionId.startsWith("email/") ||
mentionId.startsWith("team/")
if (shouldQuote) {
mentionId = "\"$mentionId\""
}
editable.replace(
editable.getSpanStart(mentionSpan),
editable.getSpanEnd(mentionSpan),
"@$mentionId"
)
}
}
private fun showSendButtonMenu() {
val popupMenu = PopupMenu(
ContextThemeWrapper(requireContext(), R.style.ChatSendButtonMenu),
@ -932,8 +954,12 @@ class MessageInputFragment : Fragment() {
}
private fun setEditUI(message: ChatMessage) {
binding.fragmentEditView.editMessage.text = message.message
binding.fragmentMessageInputView.inputEditText.setText(message.message)
val editedMessage = ChatUtils.getParsedMessage(message.message, message.messageParameters)
binding.fragmentEditView.editMessage.text = editedMessage
binding.fragmentMessageInputView.inputEditText.setText(editedMessage)
if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) {
mentionAutocomplete?.dismissPopup()
}
val end = binding.fragmentMessageInputView.inputEditText.text.length
binding.fragmentMessageInputView.inputEditText.setSelection(end)
binding.fragmentMessageInputView.messageSendButton.visibility = View.GONE

View File

@ -76,8 +76,6 @@ interface ChatMessageRepository : LifecycleAwareManager {
*/
suspend fun getMessage(messageId: Long, bundle: Bundle): Flow<ChatMessage>
suspend fun checkIfMessageIsSaved(messageId: Long): Boolean
@Suppress("LongParameterList")
suspend fun sendChatMessage(
credentials: String,
@ -112,7 +110,7 @@ interface ChatMessageRepository : LifecycleAwareManager {
suspend fun editTempChatMessage(message: ChatMessage, editedMessageText: String): Flow<Boolean>
suspend fun sendTempChatMessages(credentials: String, url: String)
suspend fun sendUnsentChatMessages(credentials: String, url: String)
suspend fun deleteTempMessage(chatMessage: ChatMessage)
}

View File

@ -183,19 +183,26 @@ class MediaPlayerManager : LifecycleAwareManager {
continue
}
if (mediaPlayer != null && mediaPlayer?.isPlaying == true) {
val pos = mediaPlayer!!.currentPosition
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
mediaPlayerPosition = pos
val progress = (pos.toFloat() / mediaPlayerDuration) * DIVIDER
val progressI = ceil(progress).toInt()
val seconds = (pos / ONE_SEC)
_mediaPlayerSeekBarPosition.emit(progressI)
currentCycledMessage?.let {
it.isPlayingVoiceMessage = true
it.voiceMessageSeekbarProgress = progressI
it.voiceMessagePlayedSeconds = seconds
if (progressI >= IS_PLAYED_CUTOFF) it.wasPlayedVoiceMessage = true
_mediaPlayerSeekBarPositionMsg.emit(it)
currentCycledMessage?.let { msg ->
msg.isPlayingVoiceMessage = true
msg.voiceMessageSeekbarProgress = progressI
msg.voiceMessagePlayedSeconds = seconds
if (progressI >= IS_PLAYED_CUTOFF) msg.wasPlayedVoiceMessage = true
_mediaPlayerSeekBarPositionMsg.emit(msg)
}
}

View File

@ -14,6 +14,7 @@ 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
@ -119,7 +120,7 @@ data class ChatMessage(
var referenceId: String? = null,
var sendingFailed: Boolean = true,
var sendStatus: SendStatus? = null,
var silent: Boolean = false

View File

@ -19,6 +19,7 @@ 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
@ -214,7 +215,8 @@ class OfflineFirstChatRepository @Inject constructor(
)
}
sendTempChatMessages(credentials, urlForChatting)
// this call could be deleted when we have a worker to send messages..
sendUnsentChatMessages(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).
@ -365,11 +367,18 @@ 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 { _removeMessageFlow.emit(it) }
oldTempMessages.forEach {
Log.d(TAG, "oldTempMessage to be removed from UI: " + it.message)
_removeMessageFlow.emit(it)
}
// add new messages to UI
val tripleChatMessages = Triple(lookIntoFuture, showUnreadMessagesMarker, receivedChatMessages)
@ -378,6 +387,9 @@ 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!! }
@ -389,6 +401,10 @@ 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)
}
@ -475,15 +491,6 @@ class OfflineFirstChatRepository @Inject constructor(
.map(ChatMessageEntity::asModel)
}
override suspend fun checkIfMessageIsSaved(messageId: Long): Boolean {
try {
chatDao.getChatMessageForConversation(internalConversationId, messageId)
return true
} catch (_: Exception) {
return false
}
}
@Suppress("UNCHECKED_CAST", "MagicNumber", "Detekt.TooGenericExceptionCaught")
private fun getMessagesFromServer(bundle: Bundle): Pair<Int, List<ChatMessageJson>>? {
val fieldMap = bundle.getSerializable(BundleKeys.KEY_FIELD_MAP) as HashMap<String, Int>
@ -852,6 +859,17 @@ 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 ->
@ -862,7 +880,7 @@ class OfflineFirstChatRepository @Inject constructor(
referenceId
).firstOrNull()
failedMessage?.let {
it.sendingFailed = true
it.sendStatus = SendStatus.FAILED
chatDao.updateChatMessage(it)
val failedMessageModel = it.asModel()
@ -882,22 +900,28 @@ class OfflineFirstChatRepository @Inject constructor(
sendWithoutNotification: Boolean,
referenceId: String
): Flow<Result<ChatMessage?>> {
val messageToResend = chatDao.getTempMessageForConversation(internalConversationId, referenceId).first()
messageToResend.sendingFailed = false
chatDao.updateChatMessage(messageToResend)
val messageToResend = chatDao.getTempMessageForConversation(internalConversationId, referenceId).firstOrNull()
return if (messageToResend != null) {
messageToResend.sendStatus = SendStatus.PENDING
chatDao.updateChatMessage(messageToResend)
val messageToResendModel = messageToResend.asModel()
_updateMessageFlow.emit(messageToResendModel)
val messageToResendModel = messageToResend.asModel()
_updateMessageFlow.emit(messageToResendModel)
return sendChatMessage(
credentials,
url,
message,
displayName,
replyTo,
sendWithoutNotification,
referenceId
)
sendChatMessage(
credentials,
url,
message,
displayName,
replyTo,
sendWithoutNotification,
referenceId
)
} else {
flow {
emit(Result.failure(IllegalStateException("No temporary message found to resend")))
}
}
}
@Suppress("Detekt.TooGenericExceptionCaught")
@ -939,8 +963,8 @@ class OfflineFirstChatRepository @Inject constructor(
}
}
override suspend fun sendTempChatMessages(credentials: String, url: String) {
val tempMessages = chatDao.getTempMessagesForConversation(internalConversationId).first()
override suspend fun sendUnsentChatMessages(credentials: String, url: String) {
val tempMessages = chatDao.getTempUnsentMessagesForConversation(internalConversationId).first()
tempMessages.sortedBy { it.internalId }.onEach {
sendChatMessage(
credentials,
@ -1034,7 +1058,7 @@ class OfflineFirstChatRepository @Inject constructor(
actorDisplayName = currentUser.displayName!!,
referenceId = referenceId,
isTemporary = true,
sendingFailed = false,
sendStatus = SendStatus.PENDING,
silent = sendWithoutNotification
)
return entity

View File

@ -285,10 +285,6 @@ class ChatViewModel @Inject constructor(
conversationRepository.getRoom(token)
}
suspend fun isMessageSaved(messageId: Long): Boolean {
return chatRepository.checkIfMessageIsSaved(messageId)
}
fun getCapabilities(user: User, token: String, conversationModel: ConversationModel) {
Log.d(TAG, "Remote server ${conversationModel.remoteServer}")
if (conversationModel.remoteServer.isNullOrEmpty()) {

View File

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

View File

@ -1,7 +1,6 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
@ -9,16 +8,41 @@
package com.nextcloud.talk.components
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.layout.windowInsetsTopHeight
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
@Composable
fun SetupSystemBars() {
fun ColoredStatusBar() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
Box(modifier = Modifier.fillMaxSize()) {
Box(
Modifier
.windowInsetsTopHeight(WindowInsets.statusBars)
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surface)
)
}
} else {
ColorLegacyStatusBar()
}
}
@Composable
private fun ColorLegacyStatusBar() {
val view = LocalView.current
val isDarkMode = isSystemInDarkTheme()
val statusBarColor = MaterialTheme.colorScheme.surface.toArgb()

View File

@ -0,0 +1,24 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.components
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@Composable
fun VerticallyCenteredRow(content: @Composable RowScope.() -> Unit) {
Row(
modifier = Modifier.fillMaxHeight(),
verticalAlignment = Alignment.CenterVertically,
content = content
)
}

View File

@ -18,9 +18,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import autodagger.AutoInjector
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.components.ColoredStatusBar
import com.nextcloud.talk.contacts.CompanionClass.Companion.KEY_HIDE_ALREADY_EXISTING_PARTICIPANTS
import com.nextcloud.talk.extensions.getParcelableArrayListExtraProvider
import com.nextcloud.talk.components.SetupSystemBars
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
import com.nextcloud.talk.utils.bundle.BundleKeys
import javax.inject.Inject
@ -64,11 +64,11 @@ class ContactsActivity : BaseActivity() {
MaterialTheme(
colorScheme = colorScheme
) {
ColoredStatusBar()
ContactsScreen(
contactsViewModel = contactsViewModel,
uiState = uiState.value
)
SetupSystemBars()
}
}
}

View File

@ -11,16 +11,18 @@ package com.nextcloud.talk.contacts
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.nextcloud.talk.R
import com.nextcloud.talk.contacts.components.AppBar
import com.nextcloud.talk.contacts.components.ContactsAppBar
import com.nextcloud.talk.contacts.components.ContactsList
import com.nextcloud.talk.contacts.components.ContactsSearchAppBar
import com.nextcloud.talk.contacts.components.ConversationCreationOptions
@Composable
@ -29,33 +31,40 @@ 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(
modifier = Modifier
.statusBarsPadding(),
topBar = {
AppBar(
title = stringResource(R.string.nc_app_product_name),
searchQuery = searchQuery,
isSearchActive = isSearchActive,
isAddParticipants = isAddParticipants,
autocompleteUsers = autocompleteUsers,
onEnableSearch = {
contactsViewModel.setSearchActive(true)
},
onDisableSearch = {
contactsViewModel.setSearchActive(false)
},
onUpdateSearchQuery = {
contactsViewModel.updateSearchQuery(query = it)
},
onUpdateAutocompleteUsers = {
contactsViewModel.getContactsFromSearchParams()
}
)
if (isSearchActive) {
ContactsSearchAppBar(
searchQuery = searchQuery,
onTextChange = {
contactsViewModel.updateSearchQuery(it)
contactsViewModel.getContactsFromSearchParams()
},
onCloseSearch = {
contactsViewModel.updateSearchQuery("")
contactsViewModel.setSearchActive(false)
contactsViewModel.getContactsFromSearchParams()
},
enableAddButton = enableAddButton,
isAddParticipants = isAddParticipants,
clickAddButton = { contactsViewModel.modifyClickAddButton(true) }
)
} else {
ContactsAppBar(
isAddParticipants = isAddParticipants,
autocompleteUsers = autocompleteUsers,
onStartSearch = { contactsViewModel.setSearchActive(true) }
)
}
},
content = {
content = { paddingValues ->
Column(
Modifier
.padding(it)
.padding(0.dp, paddingValues.calculateTopPadding(), 0.dp, 0.dp)
.background(colorResource(id = R.color.bg_default))
) {
if (!isAddParticipants) {

View File

@ -36,6 +36,15 @@ 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 {
@ -46,14 +55,28 @@ 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>) {
@ -76,20 +99,23 @@ class ContactsViewModel @Inject constructor(
}
@Suppress("Detekt.TooGenericExceptionCaught")
fun getContactsFromSearchParams() {
fun getContactsFromSearchParams(query: String = "") {
_contactsViewState.value = ContactsUiState.Loading
viewModelScope.launch {
try {
val contacts = repository.getContacts(
searchQuery.value,
if (query != "") query else searchQuery.value,
shareTypeList
)
val contactsList: MutableList<AutocompleteUser>? = contacts.ocs!!.data?.toMutableList()
if (hideAlreadyAddedParticipants) {
if (hideAlreadyAddedParticipants && !_clickAddButton.value) {
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

@ -1,89 +0,0 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.contacts.components
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.nextcloud.talk.R
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
@SuppressLint("UnrememberedMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppBar(
title: String,
searchQuery: String,
isSearchActive: Boolean,
isAddParticipants: Boolean,
autocompleteUsers: List<AutocompleteUser>,
onEnableSearch: () -> Unit,
onDisableSearch: () -> Unit,
onUpdateSearchQuery: (String) -> Unit,
onUpdateAutocompleteUsers: () -> Unit
) {
val context = LocalContext.current
TopAppBar(
title = { Text(text = title) },
navigationIcon = {
IconButton(onClick = {
(context as? Activity)?.finish()
}) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back_button))
}
},
actions = {
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 {
SearchComponent(
text = searchQuery,
onTextChange = { searchQuery ->
onUpdateSearchQuery(searchQuery)
onUpdateAutocompleteUsers()
},
onDisableSearch = onDisableSearch
)
}
}
}

View File

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

View File

@ -0,0 +1,77 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.contacts.components
import android.app.Activity
import android.content.Intent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.height
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.nextcloud.talk.R
import com.nextcloud.talk.components.VerticallyCenteredRow
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ContactsAppBar(isAddParticipants: Boolean, autocompleteUsers: List<AutocompleteUser>, onStartSearch: () -> Unit) {
val context = LocalContext.current
TopAppBar(
modifier = Modifier
.height(60.dp),
title = {
VerticallyCenteredRow {
Text(
text = if (isAddParticipants) {
stringResource(R.string.nc_participants_add)
} else {
stringResource(R.string.nc_new_conversation)
}
)
}
},
navigationIcon = {
VerticallyCenteredRow {
IconButton(onClick = { (context as? Activity)?.finish() }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back_button))
}
}
},
actions = {
VerticallyCenteredRow {
IconButton(onClick = onStartSearch) {
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()
}
)
}
}
}
)
}

View File

@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.HorizontalDivider
@ -44,9 +43,13 @@ fun ContactsItem(contacts: List<AutocompleteUser>, contactsViewModel: ContactsVi
}
LazyColumn(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth(),
contentPadding = PaddingValues(all = 10.dp),
contentPadding = PaddingValues(
top = 10.dp,
bottom = 40.dp,
start = 10.dp,
end = 10.dp
),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
groupedContacts.forEach { (initial, contactsForInitial) ->

View File

@ -0,0 +1,117 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.contacts.components
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import com.nextcloud.talk.R
import com.nextcloud.talk.components.VerticallyCenteredRow
@Suppress("LongParameterList")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ContactsSearchAppBar(
searchQuery: String,
onTextChange: (String) -> Unit,
onCloseSearch: () -> Unit,
enableAddButton: Boolean,
isAddParticipants: Boolean,
clickAddButton: (Boolean) -> Unit
) {
val keyboardController = LocalSoftwareKeyboardController.current
Surface(
modifier = Modifier.height(60.dp)
) {
VerticallyCenteredRow {
IconButton(
modifier = Modifier.padding(start = 4.dp),
onClick = onCloseSearch
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.back_button)
)
}
TextField(
value = searchQuery,
onValueChange = onTextChange,
placeholder = { Text(text = stringResource(R.string.nc_search)) },
singleLine = true,
modifier = Modifier
.weight(1f)
.padding(horizontal = 8.dp),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = searchKeyboardActions(searchQuery, keyboardController),
colors = searchTextFieldColors(),
trailingIcon = {
if (searchQuery.isNotEmpty()) {
IconButton(onClick = { onTextChange("") }) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.nc_search_clear)
)
}
}
}
)
if (isAddParticipants) {
TextButton(
onClick = {
onCloseSearch()
clickAddButton(true)
},
enabled = enableAddButton
) {
Text(text = stringResource(R.string.add_participants))
}
}
}
}
}
@Composable
fun searchTextFieldColors() =
TextFieldDefaults.colors(
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent
)
fun searchKeyboardActions(text: String, keyboardController: SoftwareKeyboardController?) =
KeyboardActions(
onSearch = {
if (text.trim().isNotEmpty()) {
keyboardController?.hide()
}
}
)

View File

@ -1,103 +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.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
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.nextcloud.talk.R
@Composable
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) },
placeholder = { Text(text = stringResource(R.string.nc_search)) },
textStyle = TextStyle(fontSize = 16.sp),
singleLine = true,
leadingIcon = { LeadingIcon(onTextChange, onDisableSearch) },
trailingIcon = { TrailingIcon(text, onTextChange) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
keyboardActions = searchKeyboardActions(text, keyboardController),
colors = searchTextFieldColors(),
maxLines = 1
)
}
@Composable
fun searchTextFieldColors() =
TextFieldDefaults.colors(
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent
)
@Composable
fun LeadingIcon(onTextChange: (String) -> Unit, onDisableSearch: () -> Unit) {
IconButton(
onClick = {
onTextChange("")
onDisableSearch()
}
) {
Icon(
imageVector = Icons.AutoMirrored.Default.ArrowBack,
contentDescription = stringResource(R.string.back_button)
)
}
}
@Composable
fun TrailingIcon(text: String, onTextChange: (String) -> Unit) {
if (text.isNotEmpty()) {
IconButton(
onClick = { onTextChange("") }
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = stringResource(R.string.close_icon)
)
}
}
}
fun searchKeyboardActions(text: String, keyboardController: SoftwareKeyboardController?) =
KeyboardActions(
onSearch = {
if (text.trim().isNotEmpty()) {
keyboardController?.hide()
}
}
)

View File

@ -84,8 +84,8 @@ import com.nextcloud.talk.R
import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.components.ColoredStatusBar
import com.nextcloud.talk.contacts.ContactsActivity
import com.nextcloud.talk.components.SetupSystemBars
import com.nextcloud.talk.contacts.loadImage
import com.nextcloud.talk.extensions.getParcelableArrayListExtraProvider
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
@ -117,7 +117,6 @@ class ConversationCreationActivity : BaseActivity() {
colorScheme = colorScheme
) {
ConversationCreationScreen(conversationCreationViewModel, context, pickImage)
SetupSystemBars()
}
}
}
@ -172,6 +171,7 @@ fun ConversationCreationScreen(
}
)
ColoredStatusBar()
Scaffold(
topBar = {
TopAppBar(
@ -191,7 +191,7 @@ fun ConversationCreationScreen(
content = { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.padding(0.dp, paddingValues.calculateTopPadding(), 0.dp, 0.dp)
.background(colorResource(id = R.color.bg_default))
.fillMaxSize()
.verticalScroll(rememberScrollState())
@ -289,7 +289,7 @@ fun UploadAvatar(
}
) {
Icon(
painter = painterResource(id = R.drawable.ic_mimetype_folder),
painter = painterResource(id = R.drawable.ic_folder),
contentDescription = null,
modifier = Modifier.size(24.dp)
)

View File

@ -190,7 +190,7 @@ class ConversationInfoActivity :
binding = ActivityConversationInfoBinding.inflate(layoutInflater)
setupActionBar()
setContentView(binding.root)
setupSystemColors()
initSystemBars()
viewModel =
ViewModelProvider(this, viewModelFactory)[ConversationInfoViewModel::class.java]
@ -252,6 +252,8 @@ class ConversationInfoActivity :
initClearChatHistoryObserver()
initMarkConversationAsSensitiveObserver()
initMarkConversationAsInsensitiveObserver()
initMarkConversationAsImportantObserver()
initMarkConversationAsUnimportantObserver()
}
private fun initMarkConversationAsSensitiveObserver() {
@ -381,7 +383,47 @@ class ConversationInfoActivity :
}
}
@SuppressLint("SetTextI18n")
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) {
@ -402,8 +444,10 @@ class ConversationInfoActivity :
)
}
if (conversation!!.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
viewModel.getProfileData(conversationUser, conversation!!.name)
conversation?.let {
if (it.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
viewModel.getProfileData(conversationUser, it.name)
}
}
}
@ -418,28 +462,48 @@ class ConversationInfoActivity :
viewModel.getProfileViewState.observe(this) { state ->
when (state) {
is ConversationInfoViewModel.GetProfileSuccessState -> {
val profile = state.profile
val pronouns = profile.pronouns ?: ""
binding.pronouns.text = pronouns
try {
// Pronouns
val profile = state.profile
val pronouns = profile.pronouns ?: ""
binding.pronouns.text = pronouns
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
// 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
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))
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)
// 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
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 -> {}
@ -504,7 +568,8 @@ class ConversationInfoActivity :
binding.guestAccessView.allowGuestsSwitch,
binding.guestAccessView.passwordProtectionSwitch,
binding.recordingConsentView.recordingConsentForConversationSwitch,
binding.lockConversationSwitch
binding.lockConversationSwitch,
binding.notificationSettingsView.sensitiveConversationSwitch
).forEach(viewThemeUtils.talk::colorSwitch)
}
}
@ -817,16 +882,13 @@ class ConversationInfoActivity :
private fun selectParticipantsToAdd() {
val bundle = Bundle()
val existingParticipants = ArrayList<AutocompleteUser>()
for (userItem in userItems) {
if (userItem.model.calculatedActorType == USERS) {
val user = AutocompleteUser(
userItem.model.calculatedActorId!!,
userItem.model.displayName,
userItem.model.calculatedActorType.name.lowercase()
)
existingParticipants.add(user)
}
val user = AutocompleteUser(
userItem.model.calculatedActorId!!,
userItem.model.displayName,
userItem.model.calculatedActorType.name.lowercase()
)
existingParticipants.add(user)
}
bundle.putBoolean(BundleKeys.KEY_ADD_PARTICIPANTS, true)
@ -1003,21 +1065,13 @@ class ConversationInfoActivity :
) {
binding.addParticipantsAction.visibility = GONE
binding.startGroupChat.visibility = VISIBLE
showDeleteAllMessagesOption(conversationCopy)
} else if (ConversationUtils.canModerate(conversationCopy, spreedCapabilities)) {
binding.addParticipantsAction.visibility = VISIBLE
if (hasSpreedFeatureCapability(
spreedCapabilities,
SpreedFeatures.CLEAR_HISTORY
) && conversationCopy.canDeleteConversation
) {
binding.clearConversationHistory.visibility = VISIBLE
} else {
binding.clearConversationHistory.visibility = GONE
}
showDeleteAllMessagesOption(conversationCopy)
showOptionsMenu()
} else {
binding.addParticipantsAction.visibility = GONE
if (ConversationUtils.isNoteToSelfConversation(conversation)) {
binding.notificationSettingsView.notificationSettings.visibility = VISIBLE
} else {
@ -1025,6 +1079,31 @@ 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
@ -1224,6 +1303,18 @@ class ConversationInfoActivity :
}
}
fun showDeleteAllMessagesOption(conversationCopy: ConversationModel) {
if (hasSpreedFeatureCapability(
spreedCapabilities,
SpreedFeatures.CLEAR_HISTORY
) && conversationCopy.canDeleteConversation
) {
binding.clearConversationHistory.visibility = VISIBLE
} else {
binding.clearConversationHistory.visibility = GONE
}
}
private fun submitRecordingConsentChanges() {
val state = if (binding.recordingConsentView.recordingConsentForConversationSwitch.isChecked) {
RECORDING_CONSENT_REQUIRED_FOR_CONVERSATION
@ -1756,13 +1847,6 @@ 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
@ -1780,9 +1864,6 @@ 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

@ -124,6 +124,18 @@ 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
@ -307,14 +319,19 @@ class ConversationInfoViewModel @Inject constructor(
}
}
@Suppress("Detekt.TooGenericExceptionCaught")
fun getProfileData(user: User, userId: String) {
val url = ApiUtils.getUrlForProfile(user.baseUrl!!, userId)
viewModelScope.launch {
val profile = conversationsRepository.getProfile(user.getCredentials(), url)
if (profile != null) {
_getProfileViewState.value = GetProfileSuccessState(profile)
} else {
_getProfileViewState.value = GetProfileErrorState
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)
}
}
}
@ -356,6 +373,34 @@ 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 {
@ -480,4 +525,16 @@ 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

@ -99,7 +99,7 @@ class ConversationInfoEditActivity : BaseActivity() {
binding = ActivityConversationInfoEditBinding.inflate(layoutInflater)
setupActionBar()
setContentView(binding.root)
setupSystemColors()
initSystemBars()
val extras: Bundle? = intent.extras

View File

@ -16,7 +16,6 @@ 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
@ -41,6 +40,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.graphics.drawable.toDrawable
import androidx.core.net.toUri
import androidx.core.os.bundleOf
@ -79,6 +81,7 @@ import com.nextcloud.talk.adapters.items.GenericTextHeaderItem
import com.nextcloud.talk.adapters.items.LoadMoreResultsItem
import com.nextcloud.talk.adapters.items.MessageResultItem
import com.nextcloud.talk.adapters.items.MessagesTextHeaderItem
import com.nextcloud.talk.adapters.items.SpacerItem
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
@ -160,7 +163,6 @@ import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import retrofit2.HttpException
import java.io.File
import java.util.Objects
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -211,7 +213,7 @@ class ConversationsListActivity :
private var adapter: FlexibleAdapter<AbstractFlexibleItem<*>>? = null
private var conversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private var conversationItemsWithHeader: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private val searchableConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private var searchableConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private var filterableConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private var nearFutureEventConversationItems: MutableList<AbstractFlexibleItem<*>> = ArrayList()
private var searchItem: MenuItem? = null
@ -235,9 +237,9 @@ class ConversationsListActivity :
private var searchViewDisposable: Disposable? = null
private var filterState =
mutableMapOf(
FilterConversationFragment.MENTION to false,
FilterConversationFragment.UNREAD to false,
FilterConversationFragment.ARCHIVE to false,
MENTION to false,
UNREAD to false,
ARCHIVE to false,
FilterConversationFragment.DEFAULT to true
)
val searchBehaviorSubject = BehaviorSubject.createDefault(false)
@ -264,7 +266,7 @@ class ConversationsListActivity :
binding = ActivityConversationsBinding.inflate(layoutInflater)
setupActionBar()
setContentView(binding.root)
setupSystemColors()
initSystemBars()
viewThemeUtils.material.themeCardView(binding.searchToolbar)
viewThemeUtils.material.themeSearchBarText(binding.searchText)
@ -291,14 +293,13 @@ class ConversationsListActivity :
override fun onResume() {
super.onResume()
// actionBar?.show()
if (adapter == null) {
adapter = FlexibleAdapter(conversationItems, this, true)
addEmptyItemForEdgeToEdgeIfNecessary()
} else {
binding.loadingContent.visibility = View.GONE
}
adapter!!.addListener(this)
adapter?.addListener(this)
prepareViews()
showNotificationWarning()
@ -314,8 +315,10 @@ class ConversationsListActivity :
showServerEOLDialog()
return
}
if (isUnifiedSearchAvailable(currentUser!!.capabilities!!.spreedCapability!!)) {
searchHelper = MessageSearchHelper(unifiedSearchRepository)
currentUser?.capabilities?.spreedCapability?.let { spreedCapabilities ->
if (isUnifiedSearchAvailable(spreedCapabilities)) {
searchHelper = MessageSearchHelper(unifiedSearchRepository)
}
}
credentials = ApiUtils.getCredentials(currentUser!!.username, currentUser!!.token)
@ -340,6 +343,14 @@ class ConversationsListActivity :
showSearchOrToolbar()
}
// if edge to edge is used, add an empty item at the bottom of the list
@Suppress("MagicNumber")
private fun addEmptyItemForEdgeToEdgeIfNecessary() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
adapter?.addScrollableFooter(SpacerItem(200))
}
}
@Suppress("LongMethod")
private fun initObservers() {
this.lifecycleScope.launch {
@ -409,6 +420,10 @@ class ConversationsListActivity :
conversationsListViewModel.getRoomsFlow
.onEach { list ->
setConversationList(list)
val noteToSelf = list
.firstOrNull { ConversationUtils.isNoteToSelfConversation(it) }
val isNoteToSelfAvailable = noteToSelf != null
handleNoteToSelfShortcut(isNoteToSelfAvailable, noteToSelf?.token ?: "")
}.collect()
}
@ -461,7 +476,13 @@ class ConversationsListActivity :
userItems.add(contactItem)
}
searchableConversationItems.addAll(userItems)
val list = searchableConversationItems.filter {
it !is ContactItem
}.toMutableList()
list.addAll(userItems)
searchableConversationItems = list
}
else -> {}
@ -519,6 +540,29 @@ 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()
@ -526,7 +570,7 @@ class ConversationsListActivity :
nearFutureEventConversationItems.clear()
for (conversation in list) {
if (!isFutureEvent(conversation)) {
if (!isFutureEvent(conversation) && !conversation.hasArchived) {
addToNearFutureEventConversationItems(conversation)
}
addToConversationItems(conversation)
@ -557,9 +601,6 @@ class ConversationsListActivity :
intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1)
)
fetchOpenConversations(apiVersion)
// Get users
fetchUsers()
}
fun applyFilter() {
@ -637,7 +678,7 @@ class ConversationsListActivity :
}
}
val archiveFilterOn = filterState[FilterConversationFragment.ARCHIVE] ?: false
val archiveFilterOn = filterState[ARCHIVE] == true
if (archiveFilterOn && newItems.isEmpty()) {
binding.noArchivedConversationLayout.visibility = View.VISIBLE
} else {
@ -659,7 +700,7 @@ class ConversationsListActivity :
for ((k, v) in filterState) {
if (v) {
when (k) {
FilterConversationFragment.MENTION -> result = (result && conversation.unreadMention) ||
MENTION -> result = (result && conversation.unreadMention) ||
(
result &&
(
@ -669,10 +710,10 @@ class ConversationsListActivity :
(conversation.unreadMessages > 0)
)
FilterConversationFragment.UNREAD -> result = result && (conversation.unreadMessages > 0)
UNREAD -> result = result && (conversation.unreadMessages > 0)
FilterConversationFragment.DEFAULT -> {
result = if (filterState[FilterConversationFragment.ARCHIVE] == true) {
result = if (filterState[ARCHIVE] == true) {
result && conversation.hasArchived
} else {
result && !conversation.hasArchived
@ -752,7 +793,7 @@ class ConversationsListActivity :
}
private fun initSearchView() {
val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager?
val searchManager = getSystemService(SEARCH_SERVICE) as SearchManager?
if (searchItem != null) {
searchView = MenuItemCompat.getActionView(searchItem) as SearchView
viewThemeUtils.talk.themeSearchView(searchView!!)
@ -931,8 +972,7 @@ class ConversationsListActivity :
} else {
showToolbar()
}
colorizeStatusBar()
colorizeNavigationBar()
initSystemBars()
}
}
@ -1168,8 +1208,8 @@ class ConversationsListActivity :
}
}
private fun fetchUsers() {
contactsViewModel.getContactsFromSearchParams()
private fun fetchUsers(query: String = "") {
contactsViewModel.getContactsFromSearchParams(query)
}
private fun handleHttpExceptions(throwable: Throwable) {
@ -1214,7 +1254,7 @@ class ConversationsListActivity :
})
binding.recyclerView.setOnTouchListener { v: View, _: MotionEvent? ->
if (!isDestroyed) {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
}
false
@ -1369,12 +1409,15 @@ class ConversationsListActivity :
clearMessageSearchResults()
binding.noArchivedConversationLayout.visibility = View.GONE
fetchUsers(filter)
if (hasFilterEnabled()) {
adapter?.updateDataSet(conversationItems)
adapter?.setFilter(filter)
adapter?.filterItems()
adapter?.updateDataSet(filterableConversationItems)
} else {
adapter?.updateDataSet(searchableConversationItems)
adapter?.setFilter(filter)
adapter?.filterItems()
}
@ -1389,9 +1432,10 @@ class ConversationsListActivity :
private fun resetSearchResults() {
clearMessageSearchResults()
adapter?.updateDataSet(conversationItems)
adapter?.setFilter("")
adapter?.filterItems()
val archiveFilterOn = filterState[FilterConversationFragment.ARCHIVE] ?: false
val archiveFilterOn = filterState[ARCHIVE] == true
if (archiveFilterOn && adapter!!.isEmpty) {
binding.noArchivedConversationLayout.visibility = View.VISIBLE
} else {
@ -1438,10 +1482,9 @@ class ConversationsListActivity :
override fun onItemClick(view: View, position: Int): Boolean {
val item = adapter?.getItem(position)
if (item != null) {
when (item.itemViewType) {
MessageResultItem.VIEW_TYPE -> {
val messageItem: MessageResultItem = item as MessageResultItem
val token = messageItem.messageEntry.conversationToken
when (item) {
is MessageResultItem -> {
val token = item.messageEntry.conversationToken
val conversationName = (
conversationItems.first {
(it is ConversationItem) && it.model.token == token
@ -1455,27 +1498,26 @@ class ConversationsListActivity :
bundle.putString(BundleKeys.KEY_CREDENTIALS, credentials!!)
bundle.putString(BundleKeys.KEY_BASE_URL, currentUser!!.baseUrl)
bundle.putString(KEY_ROOM_TOKEN, token)
bundle.putString(BundleKeys.KEY_MESSAGE_ID, messageItem.messageEntry.messageId)
bundle.putString(BundleKeys.KEY_MESSAGE_ID, item.messageEntry.messageId)
bundle.putString(BundleKeys.KEY_CONVERSATION_NAME, conversationName)
ContextChatCompose(bundle).GetDialogView(shouldDismiss, context)
}
}
}
LoadMoreResultsItem.VIEW_TYPE -> {
is LoadMoreResultsItem -> {
loadMoreMessages()
}
ConversationItem.VIEW_TYPE -> {
handleConversation((Objects.requireNonNull(item) as ConversationItem).model)
is ConversationItem -> {
handleConversation(item.model)
}
ContactItem.VIEW_TYPE -> {
val contact = item as ContactItem
is ContactItem -> {
contactsViewModel.createRoom(
ROOM_TYPE_ONE_ONE,
null,
contact.model.actorId!!,
item.model.actorId!!,
null
)
}
@ -1802,7 +1844,7 @@ class ConversationsListActivity :
val callsChannelNotEnabled = !NotificationUtils.isCallsNotificationChannelEnabled(this)
val serverNotificationAppInstalled =
currentUser?.capabilities?.notificationsCapability?.features?.isNotEmpty() ?: false
currentUser?.capabilities?.notificationsCapability?.features?.isNotEmpty() == true
val settingsOfUserAreWrong = notificationPermissionNotGranted ||
batteryOptimizationNotIgnored ||
@ -1931,9 +1973,9 @@ class ConversationsListActivity :
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
.observeForever { workInfo: WorkInfo ->
.observeForever { workInfo: WorkInfo? ->
when (workInfo.state) {
when (workInfo?.state) {
WorkInfo.State.SUCCEEDED -> {
val text = String.format(
context.resources.getString(R.string.nc_deleted_user),
@ -2134,8 +2176,8 @@ class ConversationsListActivity :
}
fun updateFilterState(mention: Boolean, unread: Boolean) {
filterState[FilterConversationFragment.MENTION] = mention
filterState[FilterConversationFragment.UNREAD] = unread
filterState[MENTION] = mention
filterState[UNREAD] = unread
}
fun setFilterableItems(items: MutableList<AbstractFlexibleItem<*>>) {
@ -2176,5 +2218,6 @@ class ConversationsListActivity :
const val ROOM_TYPE_ONE_ONE = "1"
private const val SIXTEEN_HOURS_IN_SECONDS: Long = 57600
const val LONG_1000: Long = 1000
private const val NOTE_TO_SELF_SHORTCUT_ID = "NOTE_TO_SELF_SHORTCUT_ID"
}
}

View File

@ -50,6 +50,18 @@ 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 *
@ -60,7 +72,7 @@ interface ChatMessagesDao {
ORDER BY timestamp DESC, id DESC
"""
)
fun getTempMessageForConversation(internalConversationId: String, referenceId: String): Flow<ChatMessageEntity>
fun getTempMessageForConversation(internalConversationId: String, referenceId: String): Flow<ChatMessageEntity?>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsertChatMessages(chatMessages: List<ChatMessageEntity>)

View File

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

View File

@ -62,7 +62,8 @@ fun ConversationModel.asEntity() =
remoteServer = remoteServer,
remoteToken = remoteToken,
hasArchived = hasArchived,
hasSensitive = hasSensitive
hasSensitive = hasSensitive,
hasImportant = hasImportant
)
fun ConversationEntity.asModel() =
@ -115,7 +116,8 @@ fun ConversationEntity.asModel() =
remoteServer = remoteServer,
remoteToken = remoteToken,
hasArchived = hasArchived,
hasSensitive = hasSensitive
hasSensitive = hasSensitive,
hasImportant = hasImportant
)
fun Conversation.asEntity(accountId: Long) =
@ -167,5 +169,6 @@ fun Conversation.asEntity(accountId: Long) =
remoteServer = remoteServer,
remoteToken = remoteToken,
hasArchived = hasArchived,
hasSensitive = hasSensitive
hasSensitive = hasSensitive,
hasImportant = hasImportant
)

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 = "sendingFailed") var sendingFailed: Boolean = false,
@ColumnInfo(name = "sendStatus") var sendStatus: SendStatus? = null,
@ColumnInfo(name = "silent") var silent: Boolean = false,
@ColumnInfo(name = "systemMessage") var systemMessageType: ChatMessage.SystemMessageType,
@ColumnInfo(name = "timestamp") var timestamp: Long = 0

View File

@ -95,7 +95,8 @@ data class ConversationEntity(
@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 = "hasSensitive") var hasSensitive: Boolean = false,
@ColumnInfo(name = "hasImportant") var hasImportant: Boolean = false
// missing/not needed: attendeeId
// missing/not needed: attendeePin
// missing/not needed: attendeePermissions

View File

@ -0,0 +1,14 @@
/*
* 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,18 +1,31 @@
/*
* 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")
@ -65,10 +78,19 @@ object Migrations {
val MIGRATION_14_15 = object : Migration(14, 15) {
override fun migrate(db: SupportSQLiteDatabase) {
Log.i("Migrations", "Migrating 14 to 15")
addisSensitive(db)
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 (" +
@ -290,7 +312,7 @@ object Migrations {
}
}
fun addisSensitive(db: SupportSQLiteDatabase) {
fun addIsSensitive(db: SupportSQLiteDatabase) {
try {
db.execSQL(
"ALTER TABLE Conversations " +
@ -301,6 +323,17 @@ object Migrations {
}
}
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-2024 Marcel Hibbe <dev@mhibbe.de>
* SPDX-FileCopyrightText: 2023-2025 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,12 +23,14 @@ 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
@ -49,9 +51,10 @@ import java.util.Locale
ChatMessageEntity::class,
ChatBlockEntity::class
],
version = 15,
version = 17,
autoMigrations = [
AutoMigration(from = 9, to = 10)
AutoMigration(from = 9, to = 10),
AutoMigration(from = 16, to = 17, spec = AutoMigration16To17::class)
],
exportSchema = true
)
@ -63,7 +66,8 @@ import java.util.Locale
SignalingSettingsConverter::class,
HashMapHashMapConverter::class,
LinkedHashMapConverter::class,
ArrayListConverter::class
ArrayListConverter::class,
SendStatusConverter::class
)
abstract class TalkDatabase : RoomDatabase() {
@ -117,7 +121,8 @@ abstract class TalkDatabase : RoomDatabase() {
Migrations.MIGRATION_11_12,
Migrations.MIGRATION_12_13,
Migrations.MIGRATION_13_14,
Migrations.MIGRATION_14_15
Migrations.MIGRATION_14_15,
Migrations.MIGRATION_15_16
)
.allowMainThreadQueries()
.addCallback(

View File

@ -0,0 +1,23 @@
/*
* 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

@ -21,6 +21,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.collectAsState
@ -28,8 +29,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.ViewModelProvider
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.lifecycle.ViewModelProvider
import autodagger.AutoInjector
import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R
@ -37,8 +39,8 @@ import com.nextcloud.talk.activities.BaseActivity
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
import com.nextcloud.talk.components.ColoredStatusBar
import com.nextcloud.talk.components.StandardAppBar
import com.nextcloud.talk.components.SetupSystemBars
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.BrandingUtils
import com.nextcloud.talk.utils.ClosedInterfaceImpl
@ -109,18 +111,22 @@ class DiagnoseActivity : BaseActivity() {
MaterialTheme(
colorScheme = colorScheme
) {
ColoredStatusBar()
Scaffold(
modifier = Modifier
.statusBarsPadding(),
topBar = {
StandardAppBar(
title = stringResource(R.string.nc_settings_diagnose_title),
menuItems
)
},
content = {
content = { paddingValues ->
val viewState = diagnoseViewModel.notificationViewState.collectAsState().value
Column(
Modifier
.padding(it)
.padding(0.dp, paddingValues.calculateTopPadding(), 0.dp, 0.dp)
.background(backgroundColor)
.fillMaxSize()
) {
@ -136,7 +142,6 @@ class DiagnoseActivity : BaseActivity() {
}
}
)
SetupSystemBars()
}
}
}

View File

@ -65,7 +65,12 @@ fun DiagnoseContentComposable(
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.padding(
start = 16.dp,
top = 0.dp,
end = 16.dp,
bottom = 0.dp
)
.verticalScroll(rememberScrollState())
) {
data.value.forEach { element ->
@ -100,6 +105,7 @@ fun DiagnoseContentComposable(
ShowTestPushButton(onTestPushClick)
}
ShowNotificationData(isLoading, showDialog, context, viewState, onDismissDialog)
Spacer(modifier = Modifier.height(40.dp))
}
}

View File

@ -190,6 +190,7 @@ 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

@ -65,7 +65,7 @@ class InvitationsActivity : BaseActivity() {
binding = ActivityInvitationsBinding.inflate(layoutInflater)
setupActionBar()
setContentView(binding.root)
setupSystemColors()
initSystemBars()
adapter = InvitationsAdapter(currentUser) { invitation, action ->
handleInvitation(invitation, action)

View File

@ -67,7 +67,7 @@ class GeocodingActivity :
binding = ActivityGeocodingBinding.inflate(layoutInflater)
setupActionBar()
setContentView(binding.root)
setupSystemColors()
initSystemBars()
Configuration.getInstance().load(context, PreferenceManager.getDefaultSharedPreferences(context))

View File

@ -129,7 +129,7 @@ class LocationPickerActivity :
binding = ActivityLocationBinding.inflate(layoutInflater)
setupActionBar()
setContentView(binding.root)
setupSystemColors()
initSystemBars()
getInstance().load(context, PreferenceManager.getDefaultSharedPreferences(context))

View File

@ -70,7 +70,7 @@ class MessageSearchActivity : BaseActivity() {
binding = ActivityMessageSearchBinding.inflate(layoutInflater)
setupActionBar()
setContentView(binding.root)
setupSystemColors()
initSystemBars()
viewModel = ViewModelProvider(this, viewModelFactory)[MessageSearchViewModel::class.java]
user = currentUserProvider.currentUser.blockingGet()
@ -148,13 +148,12 @@ class MessageSearchActivity : BaseActivity() {
adapter!!.addListener(object : FlexibleAdapter.OnItemClickListener {
override fun onItemClick(view: View?, position: Int): Boolean {
val item = adapter!!.getItem(position)
when (item?.itemViewType) {
LoadMoreResultsItem.VIEW_TYPE -> {
when (item) {
is LoadMoreResultsItem -> {
viewModel.loadMore()
}
MessageResultItem.VIEW_TYPE -> {
val messageItem = item as MessageResultItem
viewModel.selectMessage(messageItem.messageEntry)
is MessageResultItem -> {
viewModel.selectMessage(item.messageEntry)
}
}
return false

View File

@ -62,10 +62,10 @@ class ConversationModel(
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
) {
companion object {
@ -128,7 +128,8 @@ class ConversationModel(
remoteServer = conversation.remoteServer,
remoteToken = conversation.remoteToken,
hasArchived = conversation.hasArchived,
hasSensitive = conversation.hasSensitive
hasSensitive = conversation.hasSensitive,
hasImportant = conversation.hasImportant
)
}
}

View File

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

View File

@ -168,5 +168,8 @@ data class Conversation(
var hasArchived: Boolean = false,
@JsonField(name = ["isSensitive"])
var hasSensitive: Boolean = false
var hasSensitive: Boolean = false,
@JsonField(name = ["isImportant"])
var hasImportant: Boolean = false
) : Parcelable

View File

@ -57,7 +57,7 @@ class ListOpenConversationsActivity : BaseActivity() {
binding = ActivityOpenConversationsBinding.inflate(layoutInflater)
setupActionBar()
setContentView(binding.root)
setupSystemColors()
initSystemBars()
viewThemeUtils.platform.colorImageView(binding.searchOpenConversations, ColorRole.ON_SURFACE)
viewThemeUtils.material.colorTextInputLayout(binding.textInputLayout)

View File

@ -119,7 +119,7 @@ class ProfileActivity : BaseActivity() {
binding = ActivityProfileBinding.inflate(layoutInflater)
setupActionBar()
setContentView(binding.root)
setupSystemColors()
initSystemBars()
}
override fun onResume() {

View File

@ -6,14 +6,17 @@
*/
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
@ -162,9 +165,15 @@ class DirectReplyReceiver : BroadcastReceiver() {
// Set the updated style
previousBuilder.setStyle(previousStyle)
// Check if notification still exists
if (findActiveNotification(systemNotificationId!!) != null) {
NotificationManagerCompat.from(context).notify(systemNotificationId!!, previousBuilder.build())
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())
}
}
}
.subscribeOn(Schedulers.io())

View File

@ -53,4 +53,8 @@ interface ConversationsRepository {
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

@ -139,6 +139,24 @@ class ConversationsRepositoryImpl(
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

@ -145,7 +145,7 @@ class SettingsActivity :
binding = ActivitySettingsBinding.inflate(layoutInflater)
setupActionBar()
setContentView(binding.root)
setupSystemColors()
initSystemBars()
binding.avatarImage.let { ViewCompat.setTransitionName(it, "userAvatar.transitionTag") }
@ -681,9 +681,9 @@ class SettingsActivity :
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
.observeForever { workInfo: WorkInfo ->
.observeForever { workInfo: WorkInfo? ->
when (workInfo.state) {
when (workInfo?.state) {
WorkInfo.State.SUCCEEDED -> {
val text = String.format(
context.resources.getString(R.string.nc_deleted_user),

View File

@ -13,7 +13,6 @@ import android.os.Bundle
import android.util.Log
import android.view.MenuItem
import android.view.View
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
@ -28,7 +27,6 @@ import com.nextcloud.talk.databinding.ActivitySharedItemsBinding
import com.nextcloud.talk.shareditems.adapters.SharedItemsAdapter
import com.nextcloud.talk.shareditems.model.SharedItemType
import com.nextcloud.talk.shareditems.viewmodels.SharedItemsViewModel
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_NAME
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import javax.inject.Inject
@ -57,15 +55,11 @@ class SharedItemsActivity : BaseActivity() {
setSupportActionBar(binding.sharedItemsToolbar)
setContentView(binding.root)
viewThemeUtils.platform.themeStatusBar(this)
initSystemBars()
viewThemeUtils.material.themeToolbar(binding.sharedItemsToolbar)
viewThemeUtils.material.themeTabLayoutOnSurface(binding.sharedItemsTabs)
DisplayUtils.applyColorToNavigationBar(
this.window,
ResourcesCompat.getColor(resources, R.color.bg_default, null)
)
supportActionBar?.title = conversationName
supportActionBar?.setDisplayHomeAsUpEnabled(true)

View File

@ -86,7 +86,7 @@ class TranslateActivity : BaseActivity() {
}
setupActionBar()
setContentView(binding.root)
setupSystemColors()
initSystemBars()
setupTextViews()
viewModel.getLanguages()
setupCopyButton()

View File

@ -118,7 +118,6 @@ class ConversationsListBottomDialog(
currentUser.capabilities?.spreedCapability!!,
SpreedFeatures.FAVORITES
)
val canModerate = ConversationUtils.canModerate(conversation, currentUser.capabilities?.spreedCapability!!)
binding.conversationRemoveFromFavorites.visibility = setVisibleIf(
hasFavoritesCapability && conversation.favorite
@ -149,14 +148,11 @@ class ConversationsListBottomDialog(
)
binding.conversationOperationDelete.visibility = setVisibleIf(
canModerate
conversation.canDeleteConversation
)
binding.conversationOperationLeave.visibility = setVisibleIf(
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!!)
conversation.canLeaveConversation
)
}

View File

@ -7,16 +7,19 @@
package com.nextcloud.talk.ui.dialog
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredSizeIn
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
@ -42,6 +45,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
@ -110,12 +114,11 @@ class DateTimeCompose(val bundle: Bundle) {
) {
Surface(
shape = RoundedCornerShape(INT_8.dp),
modifier = Modifier.animateContentSize()
modifier = Modifier.fillMaxWidth().animateContentSize()
) {
Column(
modifier = Modifier
.padding(INT_16.dp)
.fillMaxWidth()
) {
Header()
Body()
@ -281,6 +284,7 @@ class DateTimeCompose(val bundle: Bundle) {
HorizontalDivider()
}
@SuppressLint("UnusedBoxWithConstraintsScope")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CollapsableDateTime(shouldDismiss: MutableState<Boolean>, isCollapsed: MutableState<Boolean>) {
@ -294,9 +298,18 @@ class DateTimeCompose(val bundle: Bundle) {
val datePickerState = rememberDatePickerState()
val timePickerState = rememberTimePickerState()
DatePicker(
state = datePickerState
)
BoxWithConstraints(
modifier = Modifier
.requiredSizeIn(minWidth = 360.dp)
) {
val scale = remember(maxWidth) { if (maxWidth < 360.dp) maxWidth / 360.dp else 1f }
DatePicker(
state = datePickerState,
modifier = Modifier
.scale(scale)
)
}
TimePicker(
state = timePickerState

View File

@ -18,6 +18,7 @@ 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
@ -58,9 +59,10 @@ class TempMessageActionsDialog(
private fun initMenuItems() {
this.lifecycleScope.launch {
initResendMessage(message.sendingFailed && networkMonitor.isOnline.value)
initMenuEditMessage(message.sendingFailed || !networkMonitor.isOnline.value)
initMenuDeleteMessage(message.sendingFailed || !networkMonitor.isOnline.value)
val sendingFailed = message.sendStatus == SendStatus.FAILED
initResendMessage(sendingFailed && networkMonitor.isOnline.value)
initMenuEditMessage(sendingFailed || !networkMonitor.isOnline.value)
initMenuDeleteMessage(sendingFailed || !networkMonitor.isOnline.value)
initMenuItemCopy()
}
}

View File

@ -205,6 +205,10 @@ 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"

View File

@ -0,0 +1,38 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.utils
import android.graphics.Color
import android.os.Build
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
/**
* This method is similar to "adjustUIForAPILevel35" in
* AppCompatActivityExtensions.kt in https://github.com/nextcloud/android-common/
* Only window.addSystemBarPaddings() had to be removed. This could be unified again at some point.
*/
@JvmOverloads
fun AppCompatActivity.adjustUIForAPILevel35(
statusBarStyle: SystemBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT),
// It may make sense to change navigationBarStyle to "SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT)"
// For now, it is set to "light" to have a fully transparent navigation bar to align with the XML screens.
// It may be wanted to have a semi transparent navigation bar in the future. Then set it to "auto" and try to
// migrate the XML screens to Compose (having semi transparent navigation bar for XML did not work out. In
// general, supporting both XML and Compose system bar handling is a pain and we will have it easier without XML)
// So in short: migrate all screens to Compose. Then it's easier to decide if navigation bar should be semi
// transparent or not for all screens.
navigationBarStyle: SystemBarStyle = SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT)
) {
val isApiLevel35OrHigher = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM)
if (!isApiLevel35OrHigher) {
return
}
enableEdgeToEdge(statusBarStyle, navigationBarStyle)
}

View File

@ -59,7 +59,8 @@ enum class SpreedFeatures(val value: String) {
ARCHIVE_CONVERSATIONS("archived-conversations-v2"),
CONVERSATION_CREATION_ALL("conversation-creation-all"),
UNBIND_CONVERSATION("unbind-conversation"),
SENSITIVE_CONVERSATIONS("sensitive-conversations")
SENSITIVE_CONVERSATIONS("sensitive-conversations"),
IMPORTANT_CONVERSATIONS("important-conversations")
}
@Suppress("TooManyFunctions")

View File

@ -54,6 +54,7 @@ import coil.request.ImageRequest
import coil.target.Target
import coil.transform.CircleCropTransformation
import com.google.android.material.chip.ChipDrawable
import com.nextcloud.talk.PhoneUtils.isPhoneNumber
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.data.user.model.User
@ -162,8 +163,9 @@ object DisplayUtils {
val config = context.resources.configuration
chip.setLayoutDirection(config.layoutDirection)
val drawable: Int
val isCallOrGroup = "call" == type || "calls" == type || "groups" == type || "user-group" == type
if (!isCallOrGroup) {
val isCall = "call" == type || "calls" == type
val isGroup = "groups" == type || "user-group" == type
if (!isGroup && !isCall) {
drawable = if (chipResource == R.xml.chip_you) {
R.drawable.mention_chip
} else {
@ -176,8 +178,12 @@ object DisplayUtils {
if (type == "circle" || type == "teams") {
chip.setChipIconResource(R.drawable.icon_team)
}
if (isCall && isPhoneNumber(label.toString())) {
chip.setChipIconResource(R.drawable.icon_circular_phone)
}
chip.setBounds(0, 0, chip.intrinsicWidth, chip.intrinsicHeight)
if (!isCallOrGroup) {
if (!isGroup) {
var url = getUrlForAvatar(conversationUser.baseUrl, id, false)
if ("guests" == type || "guest" == type || "email" == type) {
url = getUrlForGuestAvatar(

View File

@ -81,4 +81,5 @@ 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

@ -159,6 +159,30 @@ class MessageUtils(val context: Context) {
return messageStringInternal
}
fun processEditMessageParameters(
messageParameters: HashMap<String?, HashMap<String?, String?>>,
message: ChatMessage?,
inputEditText: String
): Spanned {
var result = inputEditText
for (key in messageParameters.keys) {
val individualHashMap = message?.messageParameters?.get(key)
if (individualHashMap != null) {
val mentionId = individualHashMap["mention-id"]
val type = individualHashMap["type"]
val name = individualHashMap["name"]
val placeholder = "@$name"
result = when (type) {
"user", "guest", "email" -> result.replace(placeholder, "@$mentionId", ignoreCase = false)
"user-group", "circle" -> result.replace(placeholder, "@\"$mentionId\"", ignoreCase = false)
"call" -> result.replace(placeholder, "@all", ignoreCase = false)
else -> result
}
}
}
return SpannableString(result)
}
private fun defaultMessageParameters(
messageString: Spanned,
individualHashMap: HashMap<String?, String?>,

View File

@ -30,6 +30,11 @@ class DummyChatMessagesDaoImpl : ChatMessagesDao {
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

View File

@ -1,7 +1,7 @@
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2018-2024 Google LLC
~ SPDX-FileCopyrightText: 2018-2025 Google LLC
~ SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
@ -11,5 +11,6 @@
android:viewportHeight="960">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M280,920Q247,920 223.5,896.5Q200,873 200,840L200,120Q200,87 223.5,63.5Q247,40 280,40L680,40Q713,40 736.5,63.5Q760,87 760,120L760,840Q760,873 736.5,896.5Q713,920 680,920L280,920ZM280,720L680,720L680,240L280,240L280,720Z" />
android:pathData="M280,920Q247,920 223.5,896.5Q200,873 200,840L200,120Q200,87 223.5,63.5Q247,40 280,40L680,40Q713,40 736.5,63.5Q760,87 760,120L760,244Q778,251 789,266Q800,281 800,300L800,380Q800,399 789,414Q778,429 760,436L760,840Q760,873 736.5,896.5Q713,920 680,920L280,920ZM280,840L680,840Q680,840 680,840Q680,840 680,840L680,120Q680,120 680,120Q680,120 680,120L280,120Q280,120 280,120Q280,120 280,120L280,840Q280,840 280,840Q280,840 280,840ZM280,840Q280,840 280,840Q280,840 280,840L280,120Q280,120 280,120Q280,120 280,120L280,120Q280,120 280,120Q280,120 280,120L280,840Q280,840 280,840Q280,840 280,840ZM480,240Q497,240 508.5,228.5Q520,217 520,200Q520,183 508.5,171.5Q497,160 480,160Q463,160 451.5,171.5Q440,183 440,200Q440,217 451.5,228.5Q463,240 480,240Z" />
</vector>

View File

@ -1,7 +1,7 @@
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2018-2024 Google LLC
~ SPDX-FileCopyrightText: 2018-2025 Google LLC
~ SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
@ -12,5 +12,5 @@
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M-19,861L-19,718Q-19,677 0.69,644.15Q20.38,611.29 53,594Q119,560 189.27,543Q259.54,526 332,526Q404.12,526 474.06,543Q544,560 610,594Q642.63,611.29 662.31,644.15Q682,677 682,718L682,861L-19,861ZM767,861L767,706Q767,664 744,614Q721,564 672,529Q735,535 790.5,551.5Q846,568 893,594Q934.84,617.77 956.92,646.88Q979,676 979,706L979,861L767,861ZM331,473Q253.45,473 198.23,417.78Q143,362.55 143,285Q143,207.45 198.23,152.22Q253.45,97 331,97Q408.55,97 463.77,152.22Q519,207.45 519,285Q519,362.55 463.77,417.78Q408.55,473 331,473ZM816,285Q816,362.55 760.78,417.78Q705.55,473 628,473Q613,473 587,469.5Q561,466 546,461Q575,423 589.5,378.15Q604,333.3 604,285Q604,236.7 589.5,191.85Q575,147 546,109Q566,102 587,99.5Q608,97 628,97Q705.55,97 760.78,152.22Q816,207.45 816,285Z" />
android:pathData="M40,800L40,688Q40,654 57.5,625.5Q75,597 104,582Q166,551 230,535.5Q294,520 360,520Q426,520 490,535.5Q554,551 616,582Q645,597 662.5,625.5Q680,654 680,688L680,800L40,800ZM760,800L760,680Q760,636 735.5,595.5Q711,555 666,526Q717,532 762,546.5Q807,561 846,582Q882,602 901,626.5Q920,651 920,680L920,800L760,800ZM360,480Q294,480 247,433Q200,386 200,320Q200,254 247,207Q294,160 360,160Q426,160 473,207Q520,254 520,320Q520,386 473,433Q426,480 360,480ZM760,320Q760,386 713,433Q666,480 600,480Q589,480 572,477.5Q555,475 544,472Q571,440 585.5,401Q600,362 600,320Q600,278 585.5,239Q571,200 544,168Q558,163 572,161.5Q586,160 600,160Q666,160 713,207Q760,254 760,320ZM120,720L600,720L600,688Q600,677 594.5,668Q589,659 580,654Q526,627 471,613.5Q416,600 360,600Q304,600 249,613.5Q194,627 140,654Q131,659 125.5,668Q120,677 120,688L120,720ZM360,400Q393,400 416.5,376.5Q440,353 440,320Q440,287 416.5,263.5Q393,240 360,240Q327,240 303.5,263.5Q280,287 280,320Q280,353 303.5,376.5Q327,400 360,400ZM360,720L360,720L360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720Q360,720 360,720ZM360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Q360,320 360,320Z" />
</vector>

View File

@ -1,15 +1,16 @@
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
~ SPDX-License-Identifier: GPL-3.0-or-later
~ SPDX-FileCopyrightText: 2018-2025 Google LLC
~ SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFFFF"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FFFFFFFF"
android:pathData="m7.95,0.65c-4.1,0 -7.4,3.3 -7.4,7.4s3.3,7.4 7.4,7.4 7.4,-3.3 7.4,-7.4 -3.3,-7.4 -7.4,-7.4zM8.75,1.55c1.3,0 2.4,0.8 3.5,1.3l1.8,2.5 -0.3,1.1 0.6,0.3v2.4c-0.2,0.7 -0.6,1.3 -0.9,2 -0.2,0.1 0,-0.8 -0.1,-1 0,-0.6 -0.5,-0.6 -0.9,-0.2 -0.4,0.3 -1.4,0.3 -1.5,-0.4 -0.3,-0.8 0,-1.7 0.3,-2.5l-0.6,-0.7 0.2,-1.8 -0.8,-0.9 0.2,-1 -1,-0.6c-0.2,-0.2 -0.6,-0.2 -0.7,-0.4 0.1,0 0.2,-0.1 0.2,-0.1zM6.15,1.65s0.1,0 0.1,0.1c0.4,0.2 -0.1,0.4 -0.2,0.6 -0.5,0.3 0.3,0.7 0.5,1 0.4,-0.1 0.8,-0.7 1.4,-0.5 0.7,-0.2 0.6,0.6 1.1,1 0.1,0.2 0.9,0.8 0.4,0.6 -0.5,-0.4 -1,-0.4 -1.3,0.1 -0.8,0.5 -0.3,-0.9 -0.7,-1.2 -0.6,-0.7 -0.4,0.5 -0.4,0.9 -0.4,0 -1.1,-0.3 -1.5,0.2l0.4,0.6 0.5,-0.7c0,-0.3 0.1,0.2 0.3,0.3 0.1,0.2 0.8,0.7 0.3,0.9 -0.8,0.4 -1.4,1.1 -2.1,1.7 -0.2,0.5 -0.7,0.4 -1,0 -0.7,-0.4 -0.7,0.7 -0.6,1.1l0.6,-0.4v1.1c-0.4,0.4 -0.9,-0.7 -1.3,-0.9v-1.6c0,-0.4 -0.1,-0.9 0,-1.3 0.8,-0.9 1.7,-1.9 2.2,-3h0.8c0.6,0.2 0.3,-0.7 0.5,-0.6zM4.95,9.85c0.1,0 0.2,0 0.3,0.1 0.8,0.1 1.4,0.7 2,1.1 0.5,0.5 1.6,0.3 1.7,1.2 -0.2,0.9 -1.1,1.4 -1.8,1.7 -0.2,0.1 -0.4,0.2 -0.6,0.2 -0.7,0.2 -1,-0.6 -1.2,-1.1 -0.3,-0.7 -1.1,-1.2 -1,-2.1 0,-0.4 0.2,-1 0.6,-1.1z" />
android:fillColor="@android:color/white"
android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,473 799.5,465.5Q799,458 799,453Q794,482 772,501Q750,520 720,520L640,520Q607,520 583.5,496.5Q560,473 560,440L560,400L400,400L400,320Q400,287 423.5,263.5Q447,240 480,240L520,240L520,240Q520,217 532.5,199.5Q545,182 563,171Q543,166 522.5,163Q502,160 480,160Q346,160 253,253Q160,346 160,480Q160,480 160,480Q160,480 160,480L360,480Q426,480 473,527Q520,574 520,640L520,680L400,680L400,790Q420,795 439.5,797.5Q459,800 480,800Z" />
</vector>

View File

@ -1,15 +1,16 @@
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2018-2024 Google LLC
~ SPDX-FileCopyrightText: 2018-2025 Google LLC
~ SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFFFF"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,400Q160,367 183.5,343.5Q207,320 240,320L280,320L280,240Q280,157 338.5,98.5Q397,40 480,40Q563,40 621.5,98.5Q680,157 680,240L680,320L720,320Q753,320 776.5,343.5Q800,367 800,400L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM480,680Q513,680 536.5,656.5Q560,633 560,600Q560,567 536.5,543.5Q513,520 480,520Q447,520 423.5,543.5Q400,567 400,600Q400,633 423.5,656.5Q447,680 480,680ZM360,320L600,320L600,240Q600,190 565,155Q530,120 480,120Q430,120 395,155Q360,190 360,240L360,320Z" />
android:fillColor="@android:color/white"
android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,400Q160,367 183.5,343.5Q207,320 240,320L280,320L280,240Q280,157 338.5,98.5Q397,40 480,40Q563,40 621.5,98.5Q680,157 680,240L680,320L720,320Q753,320 776.5,343.5Q800,367 800,400L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM240,800L720,800Q720,800 720,800Q720,800 720,800L720,400Q720,400 720,400Q720,400 720,400L240,400Q240,400 240,400Q240,400 240,400L240,800Q240,800 240,800Q240,800 240,800ZM480,680Q513,680 536.5,656.5Q560,633 560,600Q560,567 536.5,543.5Q513,520 480,520Q447,520 423.5,543.5Q400,567 400,600Q400,633 423.5,656.5Q447,680 480,680ZM360,320L600,320L600,240Q600,190 565,155Q530,120 480,120Q430,120 395,155Q360,190 360,240L360,320ZM240,800Q240,800 240,800Q240,800 240,800L240,400Q240,400 240,400Q240,400 240,400L240,400Q240,400 240,400Q240,400 240,400L240,800Q240,800 240,800Q240,800 240,800Z" />
</vector>

View File

@ -0,0 +1,27 @@
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2018-2025 Google LLC
~ SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2z"
android:fillColor="#3B3B3B" />
<group
android:scaleX="0.55"
android:scaleY="0.55"
android:pivotX="12"
android:pivotY="12">
<path
android:pathData="M6.62,10.79C8.06,13.62 10.38,15.94 13.21,17.38l2.2,-2.2c0.27,-0.27 0.67,-0.36 1.02,-0.24 1.12,0.37 2.33,0.57 3.57,0.57 0.55,0 1,0.45 1,1v3.5c0,0.55 -0.45,1 -1,1C10.07,21 3,13.93 3,5.5 3,4.95 3.45,4.5 4,4.5H7.5c0.55,0 1,0.45 1,1 0,1.24 0.2,2.45 0.57,3.57 0.11,0.35 0.03,0.74 -0.25,1.02l-2.2,2.2z"
android:fillColor="#FFFFFF" />
</group>
</vector>

View File

@ -1,16 +1,16 @@
<!--
~ Nextcloud Talk - Android Client
~
~ SPDX-FileCopyrightText: 2018-2024 Google LLC
~ SPDX-FileCopyrightText: 2018-2025 Google LLC
~ SPDX-License-Identifier: Apache-2.0
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:fillColor="#ffffff"
android:tint="#ffffff"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M480,680Q497,680 508.5,668.5Q520,657 520,640Q520,623 508.5,611.5Q497,600 480,600Q463,600 451.5,611.5Q440,623 440,640Q440,657 451.5,668.5Q463,680 480,680ZM440,520L520,520L520,280L440,280L440,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880Z" />
android:pathData="M480,680Q497,680 508.5,668.5Q520,657 520,640Q520,623 508.5,611.5Q497,600 480,600Q463,600 451.5,611.5Q440,623 440,640Q440,657 451.5,668.5Q463,680 480,680ZM440,520L520,520L520,280L440,280L440,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z" />
</vector>

View File

@ -4,15 +4,13 @@
~ SPDX-FileCopyrightText: 2018-2025 Google LLC
~ SPDX-License-Identifier: Apache-2.0
-->
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#969696"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp">
<path android:fillColor="#969696"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#969696"
android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM240,680L720,680L570,480L450,640L360,520L240,680ZM340,400Q365,400 382.5,382.5Q400,365 400,340Q400,315 382.5,297.5Q365,280 340,280Q315,280 297.5,297.5Q280,315 280,340Q280,365 297.5,382.5Q315,400 340,400Z" />
</vector>

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