|
| 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>)` |
0 commit comments