Files
Warp/apps/mobile
tajniak81andClaude Opus 5.5 879e22141b Mobile: spaces; /hierarchy always sends children_state
Mobile:
- The room list's title picks a space: All chats, or one space, which
  narrows the list to its rooms (and its subspaces' rooms).
- A space browser over /hierarchy: the rooms of a space and of its
  subspaces, joined or joinable, with Join; for those who may, adding an
  existing room or a new one to the space.
- New space in the new-chat sheet; invites to spaces show with the other
  invites and open the space when joined.
- Fixes several setState calls that were handed a Future (reloading the
  members, sessions, push distributor, threads), which Flutter rejects.

Server: /hierarchy left out children_state for a room without children.
The spec requires it on every room (an empty list), and SpaceRoom's
omitempty dropped empty lists too, despite its comment. The famedly Dart
SDK fails to parse such a response. The client API now wraps each room so
the field is always written; the federation response, which wants it left
out of the children it lists, is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 23:14:40 +02:00
..

Warp for Android

A Matrix client for Android, written in Flutter on the famedly Matrix Dart SDK with vodozemac for end-to-end encryption. It speaks the plain Client-Server API, so it works against any Matrix homeserver, not only Warp. The same code is meant to ship to iOS later (PLAN.md §8.5); only Android is built and tested today.

It looks like the web client: the same tokens (colours, radii), the Outfit and Figtree fonts, the Warp symbol.

What it does

  • Sign in with a password, on a server name (example.org, found through /.well-known/matrix/client), a homeserver address, or a full user ID alone. Sign up when the server allows it, with a registration token when it asks for one.
  • The session survives restarts: sync state, rooms and keys are in SQLite in the app's private storage, and the access token and cached secrets also in the Android Keystore (flutter_secure_storage). They are excluded from cloud backups and device transfers, since a restored copy would be a second session with this one's keys.
  • Room list: unread and mention counts, invites first with Join and Decline, a search, and filters for unread, people and rooms.
  • Timeline: grouped messages, day separators, room events as one-line notices, older history on scroll, links that open in the browser, images (full screen with zoom), files saved where you choose. Media is fetched with the access token (authenticated media, Matrix 1.11).
  • Sending: text (/me and the SDK's other commands), photos and videos from the gallery or the camera, any file. A message shows at once as Sending, then Sent, then Read once someone else has read it; a failed one says Not sent, with Retry and Delete on a long press.
  • Typing notifications, read receipts, deleting messages, editing shown as edited.
  • New rooms (end-to-end encrypted by default, public or not), direct messages (encrypted), joining by address. Room info: members with their role and verification shield, invite, rename, topic, notification level, turning encryption on, leaving.
  • End-to-end encryption, on by default:
    • Set up encryption creates cross-signing keys, secret storage and a key backup in one step, behind a recovery key shown once.
    • A new session verifies itself with the recovery key (which also restores older messages from the backup) or from another session.
    • Verifying people and sessions by comparing emoji, from room info, the session list, or an incoming request.
    • A message this session can't decrypt says why, not just "encrypted".
    • Reset encryption for a lost recovery key.
  • Sessions: every session of the account, which are verified, verify one, sign one out.
  • Notifications while the app runs, one per room, opening the room when tapped, and push notifications through UnifiedPush (below).
  • Settings: display name, avatar, theme (system, light, dark).

Not yet: replies, reactions, threads, spaces, voice and video calls, QR-code verification, Simplified Sliding Sync (the SDK has no client for it, so the app uses /sync), a per-message trust shield like the web client's.

Push notifications

Warp doesn't use Firebase. Push goes through UnifiedPush: the user installs a distributor app, such as ntfy, and turns on Push notifications in Settings. The distributor gives the app an endpoint, which becomes a Matrix pusher on the homeserver. When something notifies, the homeserver posts to the distributor's Matrix gateway, the distributor wakes the app, and the app fetches the event, decrypts it if it can, and shows it.

  • Pushes are event_id_only: the gateway and the distributor never see message content, in encrypted rooms or not.
  • The gateway is found with UnifiedPush's discovery (GET <endpoint origin>/_matrix/push/v1/notify; ntfy has one). A distributor without one goes through UnifiedPush's public gateway, matrix.gateway.unifiedpush.org.
  • The homeserver needs push.enabled (the default); see docs/push.md. It accepts any gateway URL with the path /_matrix/push/v1/notify.
  • Without a distributor the app is still notified, but only while Android keeps it running.

Build and run

You need:

  • Flutter 3.44 or later (Dart 3.12).

  • The Android SDK with an NDK and CMake (Android Studio installs them).

  • Rust, with the Android targets: vodozemac is compiled from source during the build.

    rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android
    

Then:

Command What
make mobile release APKs, one per ABI, to bin/warp-android-<abi>.apk
make mobile-run run on a connected phone or emulator (ARGS=--release for a release build)
make mobile-test flutter analyze and the unit tests

The first build compiles the Rust crate and takes a few minutes; later builds reuse it.

Release builds are signed with the key in android/key.properties (storeFile, storePassword, keyAlias, keyPassword; not committed). Without that file they are signed with the debug key, which is fine for trying a release build and not for distributing one.

Against a local homeserver

Debug builds may use plain HTTP, release builds only HTTPS. From the emulator, the host machine is 10.0.2.2:

WARP_SERVER_NAME=dev.local WARP_PRIVATE_KEY=./keys/dev.key ./bin/warp generate-keys
WARP_SERVER_NAME=dev.local WARP_PRIVATE_KEY=./keys/dev.key WARP_REGISTRATION_ENABLED=true ./bin/warp serve

and sign up on the server http://10.0.2.2:8008.

Windows notes

  • android/gradle.properties turns off Kotlin's incremental compilation: with the project and the pub cache on different drives, plugins fail to compile with "different roots".
  • flutter test builds the native code of every dependency for the host, including webcrypto (pulled in by UnifiedPush's Linux support). Its build needs CMake 3.13 or later; if Visual Studio's developer environment puts an older CMake first (Visual Studio 2017 ships 3.12), the build fails with "does not appear to contain CMakeLists.txt". Android builds use the Android SDK's CMake and are not affected.

Layout

Path What
lib/main.dart start-up: notifications, the client, push; a push alone starts the app without UI (--unifiedpush-bg)
lib/app.dart the app shell: signed in or not, prompts the SDK raises (password, verification requests), opening rooms from notifications
lib/matrix/ everything around the SDK: the client and homeserver discovery (client.dart), the timeline projection, media, encryption setup, notifications, push
lib/screens/ sign-in, sign-up, room list, room, room info, settings, encryption, sessions
lib/widgets/ message bubbles, avatars, new chat, verification, password prompts
lib/theme.dart the design tokens as a Flutter theme
test/ the timeline projection, formatting
assets/ the brand fonts (as TTF: Flutter can't load woff2) and the symbol
tool/icons/ launcher icon sources; dart run flutter_launcher_icons regenerates the icons

The SDK's objects change in place, as matrix-js-sdk's do in the web client. Screens watch a counter bumped on every sync (syncProvider in lib/state.dart) and read the client again.

Licences

The Matrix Dart SDK is AGPL-3.0. An APK built from this app includes it, so distributing the APK has to follow the AGPL. The fonts are under the SIL Open Font License.