- Dart 89.3%
- Kotlin 7.4%
- C++ 1.5%
- CMake 1.2%
- Makefile 0.3%
- Other 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo/workflows | ||
| .githooks | ||
| .opencode | ||
| android | ||
| docs | ||
| integration_test | ||
| ios | ||
| lib | ||
| linux | ||
| macos | ||
| test | ||
| test_driver | ||
| tool | ||
| windows | ||
| .gitignore | ||
| .metadata | ||
| analysis_options.yaml | ||
| CONTRIBUTING.md | ||
| Makefile | ||
| pubspec.lock | ||
| pubspec.yaml | ||
| README.md | ||
Lullalink
Peer-to-peer baby monitor for Flutter.
- The child device (one phone, near the baby) streams audio, video, and gyro/accelerometer data to the parent device (the other phone, near the parent) over WiFi — no cloud, no server.
- Secure by default: all streams use mutual TLS (mTLS) with self-signed certificates. Two phones trust each other by scanning a QR code of either device to pin each other's certificate/fingerprint. No PIN or password is needed — mTLS alone is sufficient.
- Audio and video streaming are each independently toggleable on the transmit side (child) and the receive side (parent).
- The parent sees a live audio gain graph, a frequency spectrum of the captured audio, and a movement graph from the child phone's gyro/accelerometer (phone rests on the bed → baby movement visible).
- The parent can configure notification thresholds (e.g. sound level, movement frequency).
- Future (v0.2): optional on-device ML on either device for movement classification and audio/sound-type classification.
Development
All tests — including the full UI suite (golden/widget tests, fake plugins, no physical device) — run and are verifiable entirely in headless CI.
The canonical testing strategy is documented in CONTRIBUTING.md. Every feature follows it.
Architecture
The P2P networking, discovery, mTLS trust model, and domain model are designed in docs/architecture/networking.md.
Clean architecture with three layers:
lib/
├── core/ Constants, utilities, extensions
├── data/ Data sources, models, repository implementations
├── domain/ Entities, use cases, repository interfaces
└── presentation/ UI screens, widgets, providers, routing
Key decisions
| Concern | Choice | Rationale |
|---|---|---|
| State management | Riverpod | Testable, no BuildContext needed, compile-safe providers |
| Routing | GoRouter | Declarative, deep-link friendly, nested routes |
| Immutable models | Freezed | Code-generated value objects, union types for states |
| Serialization | json_serializable | Pairs with Freezed, compile-time safety |
Platform support
The app targets Android + desktop:
- Child device: Android phone — streams audio, video, sensors
- Parent device: Android phone or desktop (macOS/Linux/Windows) — receives streams
iOS is not supported. The native capture/discovery handlers are implemented
for Android only (android/app/src/main/kotlin/se/jilits/emidab/lullalink/LullalinkCaptureChannels.kt);
the ios/ runner ships as the stock Flutter template and is neither built nor
tested.
Desktop networking uses mDNS/broadcast discovery and manual configuration (no camera needed for pairing trust). Platform-specific channels are abstracted in data/datasources/ so the domain layer stays platform-neutral.
Running
flutter pub get
flutter run # default platform
flutter run -d linux # Linux desktop
flutter run -d macos # macOS desktop
Verification
make check # analyze + format + full headless test suite
make test # unit + widget + golden tests (headless)
make android-test # Robolectric tests for native Android handlers
make integration-test # full-app flow + screenshots (needs a target)
make hooks # install pre-commit / commit-msg git hooks
Integration tests need a target (Linux desktop headless, emulator, or device);
see CONTRIBUTING.md. Interactive widgets expose stable Keys (in
lib/core/constants/app_keys.dart) so tests and external automation/AI agents
can drive and screenshot the app headlessly (frames land in
build/screenshots/).