Skip to content

Commit 2193e0b

Browse files
authored
Merge branch 'master' into autoplay-gifs
2 parents c40edff + 44e419e commit 2193e0b

46 files changed

Lines changed: 1894 additions & 1342 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.devcontainer/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM ubuntu:noble@sha256:0d39fcc8335d6d74d5502f6df2d30119ff4790ebbb60b364818d5112d9e3e932
1+
FROM ubuntu:noble@sha256:186072bba1b2f436cbb91ef2567abca677337cfc786c86e107d25b7072feef0c
22

33
ARG DEBIAN_FRONTEND=noninteractive
44
ENV ANDROID_HOME=/usr/lib/android-sdk

.github/workflows/codeql.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
with:
4444
swap-size-gb: 10
4545
- name: Initialize CodeQL
46-
uses: github/codeql-action/init@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4.33.0
46+
uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
4747
with:
4848
languages: ${{ matrix.language }}
4949
- name: Set up JDK 21
@@ -57,4 +57,4 @@ jobs:
5757
echo "org.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > "$HOME/.gradle/gradle.properties"
5858
./gradlew --no-daemon assembleDebug
5959
- name: Perform CodeQL Analysis
60-
uses: github/codeql-action/analyze@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4.33.0
60+
uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1

.github/workflows/scorecard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@ jobs:
4242

4343
# Upload the results to GitHub's code scanning dashboard.
4444
- name: "Upload to code-scanning"
45-
uses: github/codeql-action/upload-sarif@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4.33.0
45+
uses: github/codeql-action/upload-sarif@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
4646
with:
4747
sarif_file: results.sarif

AGENTS.md

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: GPL-3.0-or-later
4+
-->
5+
# AGENTS.md
6+
7+
This file provides guidance to all AI agents (Claude, Codex, Gemini, etc.) working with code in this repository.
8+
9+
## Project Overview
10+
11+
Nextcloud Talk for Android — a self-hosted audio/video and chat communication app. Connects to a Nextcloud server backend. Written primarily in Kotlin (some legacy Java), targets API 26+ (minSdk 26, targetSdk 36).
12+
13+
## Build Commands
14+
15+
```bash
16+
# Assemble a debug APK (F-Droid flavor, no Google services)
17+
./gradlew assembleGenericDebug
18+
19+
# Assemble with Google Play services (push notifications)
20+
./gradlew assembleGplayDebug
21+
22+
# Run all unit tests
23+
./gradlew test
24+
25+
# Run a single unit test class
26+
./gradlew testGenericDebugUnitTest --tests "com.nextcloud.talk.utils.SomeTest"
27+
28+
# Run instrumented (on-device) tests
29+
./gradlew connectedAndroidTest
30+
31+
# Static analysis — all checks (spotbugs, lint, ktlint, detekt)
32+
./gradlew check
33+
34+
# Individual checks
35+
./gradlew ktlintCheck
36+
./gradlew ktlintFormat # auto-fix
37+
./gradlew detekt
38+
./gradlew lint
39+
40+
# Install git hooks (run once after cloning)
41+
./gradlew installGitHooks
42+
43+
# Clean build
44+
./gradlew clean assembleGenericDebug
45+
```
46+
47+
Build output: `app/build/outputs/apk/`
48+
49+
## Build Flavors
50+
51+
| Flavor | App ID | Purpose |
52+
|-----------|--------------------------|--------------------------------------|
53+
| `generic` | `com.nextcloud.talk2` | F-Droid release (no Google services) |
54+
| `gplay` | `com.nextcloud.talk2` | Google Play (Firebase push notifs) |
55+
| `qa` | `com.nextcloud.talk2.qa` | Per-PR testing builds |
56+
57+
`gplay`-only dependencies (Firebase, play-services-base) use `gplayImplementation`. Avoid introducing Play-only dependencies into `generic` code paths. F-Droid (`generic`) builds do not support Google push notifications.
58+
59+
## Architecture
60+
61+
MVVM with layered architecture:
62+
63+
- **API layer**`api/NcApi.java` (Retrofit/RxJava2) and `api/NcApiCoroutines.kt` (Retrofit/coroutines).
64+
- **Data layer**`data/` contains Room DB entities/DAOs (`data/database/`), repository impls (`data/user/`, `repositories/`), and a network monitor. The Room DB is encrypted with SQLCipher.
65+
- **Repository layer**`repositories/` and `data/user/UsersRepository.kt` are the single source of truth.
66+
- **ViewModel layer** — expose `StateFlow`/`LiveData` to UI. Located in per-feature `viewmodels/` subdirectories.
67+
- **UI layer** — Activities/Fragments per feature. Mix of traditional View/XML and Jetpack Compose (composables live alongside XML layouts in feature packages).
68+
69+
### Dependency Injection
70+
71+
Dagger 2 (via AutoDagger2). App component: `application/NextcloudTalkApplication.kt` (`@AutoComponent`). Modules in `dagger/modules/`: `RestModule`, `DatabaseModule`, `DaosModule`, `RepositoryModule`, `ViewModelModule`, `ManagerModule`, `UtilsModule`.
72+
73+
Use `@Inject` for Activities/Fragments/Services/BroadcastReceivers. For all other components, prefer constructor injection.
74+
75+
### Feature Packages (under `com/nextcloud/talk/`)
76+
77+
- `conversationlist/` — main screen after login (actively being Compose-migrated, see below)
78+
- `chat/``ChatActivity`, message input, voice recording, scheduled messages
79+
- `call/` — WebRTC participant modeling, MCU/non-MCU strategies
80+
- `webrtc/` — low-level WebRTC: `PeerConnectionWrapper`, `WebSocketInstance`, audio
81+
- `signaling/``SignalingMessageReceiver`, `SignalingMessageSender`, typed notifiers
82+
- `conversationinfo/` / `conversationinfoedit/` — room settings
83+
- `account/` — login, account verification
84+
- `settings/` — app settings
85+
- `jobs/` — WorkManager background workers
86+
- `services/``CallForegroundService`
87+
- `ui/theme/` — Nextcloud theming applied to Material components
88+
89+
### Signaling Architecture
90+
91+
Two modes selected at runtime based on server capabilities:
92+
- **No-MCU** (P2P mesh): `call/LocalStateBroadcasterNoMcu.kt`, `call/MessageSenderNoMcu.kt`
93+
- **MCU** (media server): `call/LocalStateBroadcasterMcu.java`, `call/MessageSenderMcu.java`
94+
95+
`signaling/SignalingMessageReceiver.java` dispatches to typed notifiers (`CallParticipantMessageNotifier`, `WebRtcMessageNotifier`, etc.).
96+
97+
**When changing participant or call state handling, always verify both MCU and no-MCU paths — a change that works in one mode can silently break the other.**
98+
99+
## Active Work: Compose Migration of `conversationlist/`
100+
101+
`ConversationsListActivity` is being incrementally migrated to Jetpack Compose. The plan is in `docs/compose-migration-conversations-list.md`. Steps 1–7 are complete (ViewModel state consolidation, status banners, empty states, FAB, notification warning card, federation invitation card, shimmer loading, conversation item composable). Steps 8–10 (LazyColumn list, toolbar/search bar, full Activity handover) are pending.
102+
103+
**Convention:** During the migration each component is replaced one at a time so the app remains fully functional after every step. New composables go in `conversationlist/ui/`. The existing `FlexibleAdapter`/`RecyclerView` is kept until Step 8.
104+
105+
## Code Style
106+
107+
- Line length: **120 characters**
108+
- Standard Android Studio formatter with EditorConfig.
109+
- Kotlin preferred for new code; legacy Java still present.
110+
- Do not use decorative section-divider comments of any kind (e.g. `// ── Title ───`, `// ------`, `// ======`).
111+
- Every new file must end with exactly one empty trailing line (no more, no less).
112+
- All new files require an SPDX license header:
113+
114+
Kotlin/Java:
115+
```kotlin
116+
/*
117+
* Nextcloud Talk - Android Client
118+
*
119+
* SPDX-FileCopyrightText: <year> Nextcloud GmbH and Nextcloud contributors
120+
* SPDX-License-Identifier: GPL-3.0-or-later
121+
*/
122+
```
123+
124+
XML:
125+
```xml
126+
<!--
127+
~ Nextcloud Talk - Android Client
128+
~
129+
~ SPDX-FileCopyrightText: <year> Nextcloud GmbH and Nextcloud contributors
130+
~ SPDX-License-Identifier: GPL-3.0-or-later
131+
-->
132+
```
133+
134+
- Translations via Transifex — only modify `values/strings.xml`, never translated `values-*/strings.xml` files.
135+
136+
## File Naming
137+
138+
Layout/menu files follow the component they belong to:
139+
140+
| Component | Class Name | File Name |
141+
|------------------|------------------------|---------------------------------|
142+
| Activity | `UserProfileActivity` | `activity_user_profile.xml` |
143+
| Fragment | `SignUpFragment` | `fragment_sign_up.xml` |
144+
| Dialog | `ChangePasswordDialog` | `dialog_change_password.xml` |
145+
| AdapterView item || `item_person.xml` |
146+
| Partial layout || `partial_stats_bar.xml` |
147+
148+
## Design
149+
150+
- Follow Material Design 3 guidelines
151+
- In addition to any Material Design wording guidelines, follow the Nextcloud wording guidelines at https://docs.nextcloud.com/server/latest/developer_manual/design/foundations.html#wording
152+
- Ensure the app works in both light and dark theme
153+
- Ensure the app works with different server primary colors by using the colorTheme of viewThemeUtils
154+
155+
## After Making Changes
156+
157+
After finishing code changes, run `./gradlew detekt ktlintCheck` and fix any new errors or warnings before considering the task done.
158+
159+
## Static Analysis
160+
161+
- **detekt**: config in `detekt.yml` (maxIssues: 80)
162+
- **ktlint**: via `org.jlleitschuh.gradle.ktlint` plugin
163+
- **SpotBugs**: filter in `spotbugs-filter.xml`; FindSecBugs and fb-contrib active
164+
- **lint**: HTML report at `app/build/reports/lint/lint.html`
165+
166+
## Testing
167+
168+
- **Unit tests**: `app/src/test/` — JUnit 4/5, Mockito, Robolectric, MockWebServer. Uses `useJUnitPlatform()`.
169+
- **Instrumented tests**: `app/src/androidTest/` — Espresso. Integration tests need real server credentials in `gradle.properties` (`NC_TEST_SERVER_BASEURL`, etc.).
170+
- **Room migrations**: if you change the schema, add or update migration tests under `androidTest/data/`. See `data/source/local/TalkDatabase.kt` for migration declarations.
171+
- **App startup workers**: `NextcloudTalkApplication.kt` schedules periodic workers (`CapabilitiesWorker`, signaling/WebSocket workers) at startup. Worker scheduling changes can cause subtle startup regressions.
172+
173+
## Commits
174+
175+
- All commits must be signed off (`git commit -s`) per the Developer Certificate of Origin (DCO). All PRs target `master`. Backports use `/backport to stable-X.Y` in a PR comment.
176+
177+
- Commit messages must follow the [Conventional Commits v1.0.0 specification](https://www.conventionalcommits.org/en/v1.0.0/#specification) — e.g. `feat(chat): add voice message playback`, `fix(call): handle MCU disconnect gracefully`.
178+
179+
- Every commit made with AI assistance must include an `AI-assistant` trailer identifying the coding agent, its version, and the model(s) used:
180+
181+
```
182+
AI-assistant: Claude Code 2.1.80 (Claude Sonnet 4.6)
183+
AI-assistant: Copilot 1.0.6 (Claude Sonnet 4.6)
184+
```
185+
186+
General pattern: `AI-assistant: <coding-agent> <agent-version> (<model-name> <model-version>)`
187+
188+
If multiple models are used for different roles, extend the trailer with named roles:
189+
190+
```
191+
AI-assistant: OpenCode v1.0.203 (plan: Claude Opus 4.5, edit: Claude Sonnet 4.5)
192+
```
193+
194+
Pattern with roles: `AI-assistant: <coding-agent> <agent-version> (<role>: <model-name> <model-version>, <role>: <model-name> <model-version>)`

CLAUDE.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: GPL-3.0-or-later
4+
-->
5+
# CLAUDE.md
6+
7+
See [AGENTS.md](AGENTS.md) for all project guidance. That file is the canonical reference for this repository and applies to all agents (Claude, Codex, Gemini, etc.).

app/build.gradle.kts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ val espressoVersion = "3.7.0"
4141
val androidxTestVersion = "1.5.0"
4242
val media3Version = "1.9.3"
4343
val coroutinesVersion = "1.10.2"
44-
val mockitoKotlinVersion = "6.2.3"
44+
val mockitoKotlinVersion = "6.3.0"
4545

4646
android {
4747
compileSdk = 36
@@ -62,8 +62,8 @@ android {
6262

6363
// mayor.minor.hotfix.increment (for increment: 01-50=Alpha / 51-89=RC / 90-99=stable)
6464
// xx .xxx .xx .xx
65-
versionCode = 230010009
66-
versionName = "23.1.0 Alpha 09"
65+
versionCode = 230010010
66+
versionName = "23.1.0 Alpha 10"
6767

6868
vectorDrawables.useSupportLibrary = true
6969

@@ -149,6 +149,7 @@ android {
149149
warningsAsErrors = false
150150
disable.addAll(
151151
listOf(
152+
"AndroidGradlePluginVersion",
152153
"GradleDependency",
153154
"InvalidPackage",
154155
"LogNotTimber",

app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,16 @@ class ConversationItem(
246246
}
247247

248248
private fun setLastMessage(holder: ConversationItemViewHolder, appContext: Context) {
249+
val draftText = model.messageDraft?.messageText?.takeIf { it.isNotBlank() }
250+
if (draftText != null) {
251+
showDraft(holder, appContext, draftText)
252+
return
253+
}
254+
255+
holder.binding.dialogLastMessage.setTextColor(
256+
ContextCompat.getColor(context, R.color.textColorMaxContrast)
257+
)
258+
249259
if (chatMessage != null) {
250260
holder.binding.dialogDate.visibility = View.VISIBLE
251261
holder.binding.dialogDate.text = DateUtils.getRelativeTimeSpanString(
@@ -277,6 +287,19 @@ class ConversationItem(
277287
}
278288
}
279289

290+
private fun showDraft(holder: ConversationItemViewHolder, appContext: Context, draftText: String) {
291+
holder.binding.dialogDate.visibility = View.VISIBLE
292+
holder.binding.dialogDate.text = DateUtils.getRelativeTimeSpanString(
293+
model.lastActivity * MILLIES,
294+
System.currentTimeMillis(),
295+
0,
296+
DateUtils.FORMAT_ABBREV_RELATIVE
297+
)
298+
299+
val label = String.format(appContext.getString(R.string.nc_draft_prefix), draftText)
300+
viewThemeUtils.talk.themeDraftSubline(holder.binding.dialogLastMessage, label, draftText)
301+
}
302+
280303
private fun calculateRegularLastMessageText(appContext: Context): CharSequence =
281304
if (chatMessage?.actorId == user.userId) {
282305
String.format(

app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class OfflineFirstConversationsRepository @Inject constructor(
5858
if (networkMonitor.isOnline.value) {
5959
val conversationEntitiesFromSync = getRoomsFromServer(user)
6060
if (!conversationEntitiesFromSync.isNullOrEmpty()) {
61-
val conversationModelsFromSync = conversationEntitiesFromSync.map(ConversationEntity::asModel)
61+
val conversationModelsFromSync = getListOfConversations(user.id!!)
6262
_roomListFlow.emit(conversationModelsFromSync)
6363
}
6464
}

0 commit comments

Comments
 (0)