mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
chore(claude): Refresh .claude/ docs to match current architecture
Drops stale app-common/Wear OS references, renames migrated classes (ReactionSettingsFragment/PopUpPodViewFactory/PodMonitor), documents the BLE/AAP split in monitor/core, the layered AAP stack, Widget (Glance) and Upgrade subsystems, Navigation3 + legacy helpers. Fixes localization examples and screenshot pipeline counts.
This commit is contained in:
+4
-8
@@ -4,10 +4,7 @@ Android app that detects and monitors AirPods via Bluetooth LE. Displays battery
|
||||
|
||||
## Project Structure
|
||||
|
||||
| Module | Description |
|
||||
|--------|-------------|
|
||||
| `app/` | Main Android app (FOSS and Google Play flavors) |
|
||||
| `app-common/` | Shared code between phone and Wear OS apps |
|
||||
Single Gradle module `app/` with multiple source sets (`main`, `foss`, `gplay`, `debug`, `test`, `testFoss`, `testGplay`, `screenshotTest`). A previous `app-common/` module was merged into `app/`.
|
||||
|
||||
## Build Flavors
|
||||
|
||||
@@ -20,10 +17,10 @@ Quick build check: `./gradlew assembleFossDebug`
|
||||
|
||||
| Path | Contains |
|
||||
|------|----------|
|
||||
| `app/src/main/java/` | Main app source (activities, fragments, services) |
|
||||
| `app-common/src/main/java/` | Shared logic (monitor, bluetooth, models) |
|
||||
| `app/src/main/java/` | Main app source (Compose screens, services, receivers, monitor, bluetooth, models) |
|
||||
| `app/src/foss/java/`, `app/src/gplay/java/` | Flavor-specific code (e.g. upgrade/billing) |
|
||||
| `app/src/main/res/` | Layouts, drawables, strings |
|
||||
| `app-common/src/test/` | Unit tests |
|
||||
| `app/src/test/`, `app/src/testFoss/`, `app/src/testGplay/` | Unit tests (shared + flavor-specific) |
|
||||
| `app/build.gradle.kts` | App build config, dependencies, flavors |
|
||||
| `app/src/debug/java/.../screenshots/` | Play Store screenshot content composables |
|
||||
| `fastlane/` | Screenshot generation scripts, Play Store metadata |
|
||||
@@ -31,7 +28,6 @@ Quick build check: `./gradlew assembleFossDebug`
|
||||
## Development Tips
|
||||
|
||||
- Use `assembleFossDebug` as the fastest build variant for iteration
|
||||
- Shared code goes in `app-common/`, app-specific code in `app/`
|
||||
- Follow existing patterns — the codebase uses MVVM + Hilt + Coroutines
|
||||
- Always use string resources for user-facing text (see localization rules)
|
||||
- Check `git log --oneline -20` for commit message style before committing
|
||||
|
||||
@@ -2,16 +2,22 @@
|
||||
description: Architecture overview, module structure, key components, data flow, and dependencies
|
||||
globs:
|
||||
- "app/**/*.kt"
|
||||
- "app-common/**/*.kt"
|
||||
- "**/*.gradle.kts"
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
## Multi-Module Structure
|
||||
## Single-Module Structure
|
||||
|
||||
- **app/**: Main Android application with FOSS and Google Play flavors
|
||||
- **app-common/**: Shared code between main app and Wear OS app
|
||||
One Gradle module: `app/`. Source sets:
|
||||
|
||||
- `main` — shared code (Compose UI, services, monitor, bluetooth, AAP protocol, widgets)
|
||||
- `foss` / `gplay` — flavor-specific code (e.g. upgrade/billing implementations)
|
||||
- `debug` — debug-only code including screenshot content composables
|
||||
- `test` / `testFoss` / `testGplay` — unit tests
|
||||
- `screenshotTest` — Compose Preview Screenshot tests for Play Store assets
|
||||
|
||||
A previous `app-common/` module was merged into `app/` (commit `be8f4919`).
|
||||
|
||||
## Core Patterns
|
||||
|
||||
@@ -22,18 +28,62 @@ globs:
|
||||
|
||||
## Key Components
|
||||
|
||||
### PodMonitor System
|
||||
### Device Monitoring
|
||||
|
||||
- `PodMonitor`: Core service that detects and tracks AirPods via Bluetooth LE
|
||||
- `MonitorControl`: Manages MonitorService lifecycle
|
||||
- `MonitorService`: Foreground service that continuously scans for AirPods
|
||||
- `BluetoothEventReceiver`: Handles system Bluetooth events
|
||||
`monitor/core/` is split into two data-source siblings that `DeviceMonitor` merges:
|
||||
|
||||
- `monitor/core/ble/BlePodMonitor` — passive BLE scanning; reads Apple advertisement beacons (battery, case state, in-ear, etc.). Works for any pod in range; no pairing required
|
||||
- `monitor/core/aap/` — AAP connection lifecycle layer on top of `AapConnectionManager`:
|
||||
- `AapLifecycleManager` — starts/stops the AAP subsystem
|
||||
- `AapAutoConnect` — auto-opens AAP sessions for bonded/known devices
|
||||
- `AapKeyPersister`, `AapLearnedSettingsPersister` — persist session keys and learned pod settings across app restarts
|
||||
- `StemConfigSender`, `StemPressReaction`, `AncGestureResolver` — push config and react to stem/HID events
|
||||
- `monitor/core/cache/DeviceStateCache` — persisted last-known state so profiles still show data when a device is out of range
|
||||
- `DeviceMonitor` — singleton that `combine`s `BlePodMonitor.devices + AapConnectionManager.allStates + DeviceStateCache + profiles` into unified `PodDevice` objects. ViewModels observe `DeviceMonitor.devices`; they do **not** reach into `BlePodMonitor` or the AAP layer directly
|
||||
- `MonitorControl` / `MonitorService` — foreground service lifecycle holding the scan awake
|
||||
- `BluetoothEventReceiver`, `BootCompletedReceiver` — system triggers that wake the service
|
||||
|
||||
**BLE vs AAP — what each path gives you:**
|
||||
|
||||
| | BLE (advertisements) | AAP (L2CAP session) |
|
||||
|---|---|---|
|
||||
| Direction | Read-only, passive | Bidirectional commands + events |
|
||||
| Prerequisite | Bluetooth on | Bonded + `BLUETOOTH_CONNECT` + active L2CAP socket |
|
||||
| Data | Battery, case open, in-ear, pod model | Settings, ANC mode control, press controls, stem events, device info |
|
||||
| Availability | Any pod in range | Only your own paired pods |
|
||||
|
||||
### Reaction System
|
||||
|
||||
- `ReactionSettingsFragment`: Configuration for popup notifications
|
||||
- `ReactionsCard`: Compose UI for reaction settings, embedded in the device settings screen
|
||||
- `PopUpWindow`: Displays AirPods status when case is opened
|
||||
- `PopUpPodViewFactory`: Creates UI components for different pod models
|
||||
- `PopUpContent`: Compose pod rendering — model-specific UI branches inline, no factory class
|
||||
|
||||
### Widget System (Glance)
|
||||
|
||||
- `BatteryGlanceWidget`, `AncGlanceWidget`: Jetpack Glance-based home-screen widgets
|
||||
- `WidgetConfigurationActivity`: Configuration UI launched on widget placement
|
||||
- Lives under `app/src/main/java/eu/darken/capod/main/ui/widget/`
|
||||
|
||||
### Upgrade / Pro Features
|
||||
|
||||
- `UpgradeRepo` interface with two flavor implementations:
|
||||
- `UpgradeRepoGplay` — billing-client backed, includes grace-period handling for interrupted purchases
|
||||
- `UpgradeControlFoss` — cache/sponsor-backed; users are `isPro = false` until they call `upgrade()`, after which the pro flag is persisted via DataStore
|
||||
- FOSS is **not** "always pro" — it's opt-in via a local sponsor flow
|
||||
|
||||
### AAP (Apple Accessory Protocol) Stack
|
||||
|
||||
Three-layer structure under `pods/core/apple/aap/`:
|
||||
|
||||
- **`protocol/`** — pure data: `AapMessage`, `AapCommand`, `AapSetting`, `AapDeviceProfile`, `AapDeviceInfo`, `StemPressEvent`, `KeyExchangeResult`. Plus `DefaultAapDeviceProfile` and `Model.Features` capturing per-model capability
|
||||
- **`engine/`** — session state machine for one connection:
|
||||
- `AapConnection` — the L2CAP socket wrapper
|
||||
- `AapSessionEngine` — drives the session lifecycle; tested in `AapSessionEngineTest`
|
||||
- `AapInboundInterpreter` / `AapOutboundController` — decode incoming messages, encode outgoing
|
||||
- `AapSettingsCoordinator`, `AapAncController`, `HidTracker`, `AapDeviceInfoDiagnostics` — feature-specific coordinators that sit on top of the session
|
||||
- **`AapConnectionManager`** (singleton) — owns all open AAP sessions keyed by `BluetoothAddress`, uses `L2capSocketFactory` to create sockets. Consumers don't touch `AapConnection` directly — they call `sendCommand(...)` and observe `allStates`
|
||||
|
||||
The monitor-layer glue (`monitor/core/aap/`) described above wires this stack into the foreground service and persists its learned state.
|
||||
|
||||
### Common Utilities
|
||||
|
||||
@@ -54,31 +104,20 @@ globs:
|
||||
|
||||
## Data Flow
|
||||
|
||||
The app follows a unidirectional data flow:
|
||||
|
||||
1. `BluetoothEventReceiver` detects Bluetooth events
|
||||
2. `MonitorService` scans for AirPods beacon data
|
||||
3. `PodMonitor` processes and stores device information
|
||||
4. ViewModels observe monitor data via repositories
|
||||
5. UI components react to ViewModel state changes
|
||||
6. `ReactionSystem` triggers popups and notifications
|
||||
|
||||
## Bluetooth LE Implementation
|
||||
|
||||
The app uses Android's Bluetooth LE APIs to scan for Apple device advertisements. The core scanning logic is in `MonitorService` which runs as a foreground service.
|
||||
|
||||
## Multi-Platform Considerations
|
||||
|
||||
Code shared between phone and Wear OS apps is placed in `app-common`. When modifying shared functionality, ensure compatibility across both platforms.
|
||||
1. `BluetoothEventReceiver` / `BootCompletedReceiver` wake `MonitorService` (foreground)
|
||||
2. `MonitorService` keeps `BlePodMonitor` scanning (passive advertisements) and `AapLifecycleManager` running (active L2CAP sessions via `AapConnectionManager`)
|
||||
3. `DeviceMonitor` merges BLE + AAP + cached state + profiles into `PodDevice` objects
|
||||
4. ViewModels (`OverviewViewModel`, `DeviceSettingsViewModel`, `PressControlsViewModel`, widget view models) observe `DeviceMonitor.devices`; settings/command changes are sent back through `AapConnectionManager.sendCommand(...)`
|
||||
5. Reaction triggers (case-open popup, auto-play, notifications) and widget state updates react to the merged flow
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
- **Unit Tests**: Located in `app-common/src/test/` for shared logic
|
||||
- **Test Flavors**: Separate test configurations for FOSS and Google Play variants
|
||||
- **Unit Tests**: `app/src/test/` (shared), `app/src/testFoss/`, `app/src/testGplay/` (flavor-specific — e.g. `UpgradeRepoGplayTest`, `FossUpgradeSerializationTest`)
|
||||
- **Screenshot Tests**: `app/src/screenshotTest/` — Compose Preview Screenshot Testing, powers the Play Store screenshot pipeline
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
- **Hilt**: Dependency injection framework
|
||||
- **AndroidX Navigation**: Fragment navigation with SafeArgs
|
||||
- **Navigation**: Navigation3 (`addNavigation3()`) drives current Compose screen routing. Some legacy `androidx.navigation` helpers still exist (`NavDirectionsExtensions`, `ViewModel3`) — don't assume SafeArgs is fully gone
|
||||
- **kotlinx.serialization**: JSON serialization for configuration and caching
|
||||
- **Material Design**: UI components following Material Design guidelines
|
||||
- **Material Design 3**: Compose Material3 UI components
|
||||
|
||||
@@ -21,6 +21,7 @@ Use the existing commit history as reference. Common prefixes:
|
||||
- **fix**: Bug fixes (e.g., `fix: Handle display cutouts in landscape mode`)
|
||||
- **feat**: New features
|
||||
- **refactor**: Code restructuring without behavior change
|
||||
- **ui**: Visual/layout-only tweaks that aren't a full feature or refactor (e.g., `ui(overview): ...`)
|
||||
- **chore**: Maintenance, dependency updates, build config
|
||||
- **docs**: Documentation changes
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ globs:
|
||||
When adding new user-facing strings:
|
||||
|
||||
- **Always use string resources**: Never hardcode user-facing text in layouts or code
|
||||
- **Follow naming conventions**: Use descriptive, hierarchical naming (e.g., `profiles_name_default`, `settings_bluetooth_enabled`)
|
||||
- **Follow naming conventions**: Use descriptive, hierarchical naming (e.g., `profiles_name_default`, `settings_monitor_mode_label`)
|
||||
- **Provide context**: String names should indicate usage and location
|
||||
- **Consider pluralization**: Use Android plural resources (`<plurals>`) when quantities vary
|
||||
|
||||
@@ -17,5 +17,7 @@ When adding new user-facing strings:
|
||||
|
||||
- `profiles_create_title` (screen title)
|
||||
- `profiles_name_label` (form field label)
|
||||
- `profiles_delete_confirmation` (dialog message)
|
||||
- `error_network_unavailable` (error message)
|
||||
- `profiles_name_default` (default value)
|
||||
- `troubleshooter_ble_result_failure_title` (status/error title)
|
||||
|
||||
Common prefixes currently in use: `device_`, `settings_`, `support_`, `profiles_`, `press_`, `general_`, `pods_`, `upgrade_`, `widget_`, `debug_`, `permission_`, `troubleshooter_`, `overview_`, `anc_`, `onboarding_`. There is no `error_*` prefix — error labels live under the relevant feature (e.g. `general_error_label`, `troubleshooter_*_failure_*`).
|
||||
|
||||
@@ -19,16 +19,17 @@ ScreenshotContent.kt (mock data + composables)
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/src/debug/java/.../screenshots/ScreenshotContent.kt` | Mock data composables for each screen (8 screens) |
|
||||
| `app/src/screenshotTest/kotlin/.../screenshots/PlayStoreScreenshots.kt` | `@PreviewTest` functions that wire content to locale annotations |
|
||||
| `app/src/debug/java/.../screenshots/ScreenshotContent.kt` | Mock data composables for each screen (7 screens) |
|
||||
| `app/src/screenshotTest/kotlin/.../screenshots/PlayStoreScreenshots.kt` | `@PreviewTest` functions (currently: `DashboardLight`, `DashboardDark`, `CasePopUp`, `DeviceProfiles`, `AddProfile`, `DeviceSettingsReactions`, `WidgetConfiguration`) |
|
||||
| `app/src/screenshotTest/kotlin/.../screenshots/PlayStoreLocales.kt` | Multi-preview annotations (auto-generated by batch script) |
|
||||
| `fastlane/generate_screenshots.sh` | Batched generation across 68 locales |
|
||||
| `fastlane/generate_screenshots.sh` | Batched generation; locale list (`ALL_LOCALES`) and `BATCH_SIZE` are defined inside the script |
|
||||
| `fastlane/copy_screenshots.sh` | Copies rendered PNGs into fastlane structure |
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Full run — all 68 locales, ~12 batches, ~7 minutes
|
||||
# Full run — iterates over ALL_LOCALES in batches of BATCH_SIZE.
|
||||
# The script prints "Locales: N | Batch size: B | Batches: ceil(N/B)" at startup.
|
||||
./fastlane/generate_screenshots.sh
|
||||
|
||||
# Smoke test — 6 locales (en, de, ja, ar, zh-CN, pt-BR), single batch
|
||||
@@ -37,7 +38,7 @@ ScreenshotContent.kt (mock data + composables)
|
||||
# Copy into fastlane directories (run after generate)
|
||||
./fastlane/copy_screenshots.sh
|
||||
|
||||
# Clean copy (removes old screenshots first)
|
||||
# Clean copy (removes old screenshots first) — REQUIRED when screens are removed or renamed
|
||||
./fastlane/copy_screenshots.sh --clean
|
||||
```
|
||||
|
||||
@@ -49,6 +50,12 @@ ScreenshotContent.kt (mock data + composables)
|
||||
4. Update the expected count in `generate_screenshots.sh` (composables per locale)
|
||||
5. Run the full pipeline: `generate_screenshots.sh` then `copy_screenshots.sh`
|
||||
|
||||
## Removing or Renaming a Screenshot
|
||||
|
||||
1. Remove the `@PreviewTest` entry and its `SCREEN_MAP` mapping
|
||||
2. Run `generate_screenshots.sh`
|
||||
3. Run `copy_screenshots.sh --clean` — **`--clean` is required** here; without it, old files (e.g. a renamed `8_reaction_settings.png`) stay in `fastlane/metadata/android/*/images/phoneScreenshots/` and get uploaded to Play Store
|
||||
|
||||
## After UI Changes
|
||||
|
||||
When modifying a screen that appears in screenshots (check `ScreenshotContent.kt`), regenerate:
|
||||
@@ -60,7 +67,7 @@ When modifying a screen that appears in screenshots (check `ScreenshotContent.kt
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- Batch size defaults to 2 locales (16 renders) to avoid layoutlib memory leak (~10MB/image)
|
||||
- Batch size defaults to 2 locales; renders per batch = `BATCH_SIZE × screen count` (currently 2 × 7 = 14). Small batches avoid layoutlib memory leaks (~10MB/image)
|
||||
- Gradle daemon is stopped between batches to release memory
|
||||
- `PlayStoreLocales.kt` is temporarily rewritten per batch and restored via trap
|
||||
- Device spec: 1080x2400px @ 428 DPI (Pixel-class phone)
|
||||
|
||||
Reference in New Issue
Block a user