A Material 3 Expressive WhatsApp client built with Flutter.
Status: early development. Phase A (UI polish + Riverpod conversion) is complete. The Go/whatsmeow backend (Phase B) is being ported from the legacy
rzi_whatsapprepo — the FFI layer + ConnectScreen are now ported; the mock→real data swap is next. SeeROADMAP.mdandTASKS.mdfor the plan and actionable tasks.
- Material 3 Expressive UI with
m3e_coreanimations - Responsive split-view shell (chat list + chat thread)
- Riverpod state management (
hooks_riverpod+ code generation) - Search, filters, and pagination over mock data
- M3E-style dismissible behavior with undo semantics
Currently backed by mock data; the real WhatsApp (whatsmeow) backend is the next milestone (Phase B).
devenv shell # enters a shell with the Flutter SDK and Git pinned by devenv.nix
flutter pub getInside the devenv shell, FLUTTER_ROOT is exported automatically (derived from
which flutter), so the Flutter SDK path is handled for you. If you need to
point tooling at a specific SDK:
export FLUTTER_ROOT=/path/to/flutter- Install the Flutter SDK (stable) and add
bin/to yourPATH. export FLUTTER_ROOT="$(dirname "$(dirname "$(which flutter)")")"flutter pub get
flutter run -d linuxOther targets (Android, iOS, macOS, Windows, web) are scaffolded; only Linux is actively developed at the moment.
flutter test # unit and widget tests
flutter analyze # static analysis, including riverpod_lintProviders use Riverpod code generation. Regenerate after editing provider annotations:
dart run build_runner watchGenerated *.g.dart files are committed to the repo.
The app talks to a WhatsApp (whatsmeow) Go library compiled to a shared
library (libwhatsapp.so) via dart:ffi. The Go source lives in src/go/.
# From the devenv shell (Go + gcc are provided by devenv.nix):
./src/build_go.shThis runs go get whatsmeow@latest + go mod tidy, builds
build/libwhatsapp.so (and the libwhatsapp.h header), and verifies the
WA_* exports with nm. The output goes to src/build/ (gitignored).
lib/src/ffi/whatsapp_bindings.dart is ffigen-generated output — don't
edit it by hand. After changing the Go exports, rebuild the .so and run:
./generate_ffi_bindings.sh # runs: flutter pub run ffigen --config ffigen.yamlffigen.yaml points at the nix-store libclang and glibc headers for this
machine; the paths may need adjusting on another setup.
lib/main.dart boots into ConnectScreen, which:
- Loads
libwhatsapp.sofrom./src/build/(Linux dev path) - Resolves the per-platform app data dir via
path_provider(getApplicationSupportDirectory()) and passes it toWA_Init WA_Connect→ QR code event → scan with WhatsApp > Linked Devices- On
"connected", hands off toChatShell(currently still mock-data UI until the Phase B mock→real swap lands)
The app data dir is resolved per-platform via path_provider
(getApplicationSupportDirectory()):
~/.local/share/better_whatsapp on Linux, the app support dir on macOS/iOS,
the app's internal data dir on Android. It contains two SQLite files, plus
an avatars/ subdirectory for cached profile pictures:
wa-session.db— whatsmeow's own device/session store (CGO sqlite3)app.db— the app's message/chat store (pure-Go sqlite):chats,messages,message_edits,contacts,jid_mappings
Avatar caching: profile pictures are downloaded and cached to disk
(<data-dir>/avatars/<jid>.jpg) so the app doesn't re-download them on
every startup. whatsmeow's picture ID acts as an etag — it changes exactly
when the image changes — so revalidation is a cheap metadata round-trip
that transfers no image bytes when the picture is unchanged:
- Startup sweep (
WA_FetchMissingAvatars, called on "connected"): backfills chats that have no avatar yet or whose image file is missing. - Periodic recheck (every 5 min while connected): re-validates every chat via etag; downloads only on change. Staggered per chat to avoid hammering WhatsApp (whatsmeow #672).
- Eviction:
ErrProfilePictureUnauthorized/NotSet(picture gone or private) clears both the DB row and the image file — UI falls back to initials. Real network errors keep the stale image and retry next cycle. Downloads are written atomically (tmp + rename) so a reader never sees a half-written file.
Message history export: WA_GetMessages (paginated read of stored
messages) plus WA_RequestMoreHistory (asks the phone for older messages
on demand; WhatsApp doesn't always respond to these from linked devices).
CI (.github/workflows/ci.yml) runs on every push to main and on PRs:
analyze, tests, build-linux, build-ios. A green CI is the
gate for a release.
Release checklist (how to produce release artifacts):
- Preflight — make sure
mainis green: all four CI jobs pass (analyze, tests, build-linux, build-ios). - Bump the version in
pubspec.yaml(version: x.y.z+n). Follow semver — the+nsuffix is the build number. - Build the backend shared library (Linux):
Confirmed exports: 13
./src/build_go.sh
WA_*symbols insrc/build/libwhatsapp.so. - Tag the release:
git tag v$(grep '^version:' pubspec.yaml | awk '{print $2}' | cut -d+ -f1) git push origin v$(grep '^version:' pubspec.yaml | awk '{print $2}' | cut -d+ -f1)
- Build per-target artifacts:
- Linux:
flutter build linux --release→build/linux/x64/release/bundle/ - Android:
flutter build apk --release→build/app/outputs/flutter-apk/ - iOS:
flutter build ipa --release(codesign needs the team set up; CI builds--no-codesignonly)
- Linux:
- Create the GitHub release (
gh release create) and attach the artifacts above.
Note: targets may be added later (e.g. Android in CI). The Go FFI backend currently builds for Linux only; see Running the Go/FFI backend.
lib/providers/— Riverpod providers (chat list, messages, search, selection)lib/screens/— connect (pairing/QR), chat shell, picker, and thread viewslib/widgets/— shared widgets (chat tile, pull-to-refresh, M3E indicator)lib/models/—Chat/ChatMessagedata models (mirror Go's db schema)lib/src/ffi/—whatsapp_client.dart(hand-written FFI wrapper) +whatsapp_bindings.dart(ffigen-generated)src/go/— Go whatsmeow library (lib.go,internal/client,internal/db)test/— unit and widget tests
See CONTRIBUTING.md — and please follow the
CODE_OF_CONDUCT.md.
MIT — see LICENSE.