mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-16 19:26:12 -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
|
## Project Structure
|
||||||
|
|
||||||
| Module | Description |
|
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/`.
|
||||||
|--------|-------------|
|
|
||||||
| `app/` | Main Android app (FOSS and Google Play flavors) |
|
|
||||||
| `app-common/` | Shared code between phone and Wear OS apps |
|
|
||||||
|
|
||||||
## Build Flavors
|
## Build Flavors
|
||||||
|
|
||||||
@@ -20,10 +17,10 @@ Quick build check: `./gradlew assembleFossDebug`
|
|||||||
|
|
||||||
| Path | Contains |
|
| Path | Contains |
|
||||||
|------|----------|
|
|------|----------|
|
||||||
| `app/src/main/java/` | Main app source (activities, fragments, services) |
|
| `app/src/main/java/` | Main app source (Compose screens, services, receivers, monitor, bluetooth, models) |
|
||||||
| `app-common/src/main/java/` | Shared logic (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/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/build.gradle.kts` | App build config, dependencies, flavors |
|
||||||
| `app/src/debug/java/.../screenshots/` | Play Store screenshot content composables |
|
| `app/src/debug/java/.../screenshots/` | Play Store screenshot content composables |
|
||||||
| `fastlane/` | Screenshot generation scripts, Play Store metadata |
|
| `fastlane/` | Screenshot generation scripts, Play Store metadata |
|
||||||
@@ -31,7 +28,6 @@ Quick build check: `./gradlew assembleFossDebug`
|
|||||||
## Development Tips
|
## Development Tips
|
||||||
|
|
||||||
- Use `assembleFossDebug` as the fastest build variant for iteration
|
- 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
|
- Follow existing patterns — the codebase uses MVVM + Hilt + Coroutines
|
||||||
- Always use string resources for user-facing text (see localization rules)
|
- Always use string resources for user-facing text (see localization rules)
|
||||||
- Check `git log --oneline -20` for commit message style before committing
|
- 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
|
description: Architecture overview, module structure, key components, data flow, and dependencies
|
||||||
globs:
|
globs:
|
||||||
- "app/**/*.kt"
|
- "app/**/*.kt"
|
||||||
- "app-common/**/*.kt"
|
|
||||||
- "**/*.gradle.kts"
|
- "**/*.gradle.kts"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Architecture
|
# Architecture
|
||||||
|
|
||||||
## Multi-Module Structure
|
## Single-Module Structure
|
||||||
|
|
||||||
- **app/**: Main Android application with FOSS and Google Play flavors
|
One Gradle module: `app/`. Source sets:
|
||||||
- **app-common/**: Shared code between main app and Wear OS app
|
|
||||||
|
- `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
|
## Core Patterns
|
||||||
|
|
||||||
@@ -22,18 +28,62 @@ globs:
|
|||||||
|
|
||||||
## Key Components
|
## Key Components
|
||||||
|
|
||||||
### PodMonitor System
|
### Device Monitoring
|
||||||
|
|
||||||
- `PodMonitor`: Core service that detects and tracks AirPods via Bluetooth LE
|
`monitor/core/` is split into two data-source siblings that `DeviceMonitor` merges:
|
||||||
- `MonitorControl`: Manages MonitorService lifecycle
|
|
||||||
- `MonitorService`: Foreground service that continuously scans for AirPods
|
- `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
|
||||||
- `BluetoothEventReceiver`: Handles system Bluetooth events
|
- `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
|
### 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
|
- `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
|
### Common Utilities
|
||||||
|
|
||||||
@@ -54,31 +104,20 @@ globs:
|
|||||||
|
|
||||||
## Data Flow
|
## Data Flow
|
||||||
|
|
||||||
The app follows a unidirectional data flow:
|
1. `BluetoothEventReceiver` / `BootCompletedReceiver` wake `MonitorService` (foreground)
|
||||||
|
2. `MonitorService` keeps `BlePodMonitor` scanning (passive advertisements) and `AapLifecycleManager` running (active L2CAP sessions via `AapConnectionManager`)
|
||||||
1. `BluetoothEventReceiver` detects Bluetooth events
|
3. `DeviceMonitor` merges BLE + AAP + cached state + profiles into `PodDevice` objects
|
||||||
2. `MonitorService` scans for AirPods beacon data
|
4. ViewModels (`OverviewViewModel`, `DeviceSettingsViewModel`, `PressControlsViewModel`, widget view models) observe `DeviceMonitor.devices`; settings/command changes are sent back through `AapConnectionManager.sendCommand(...)`
|
||||||
3. `PodMonitor` processes and stores device information
|
5. Reaction triggers (case-open popup, auto-play, notifications) and widget state updates react to the merged flow
|
||||||
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.
|
|
||||||
|
|
||||||
## Testing Strategy
|
## Testing Strategy
|
||||||
|
|
||||||
- **Unit Tests**: Located in `app-common/src/test/` for shared logic
|
- **Unit Tests**: `app/src/test/` (shared), `app/src/testFoss/`, `app/src/testGplay/` (flavor-specific — e.g. `UpgradeRepoGplayTest`, `FossUpgradeSerializationTest`)
|
||||||
- **Test Flavors**: Separate test configurations for FOSS and Google Play variants
|
- **Screenshot Tests**: `app/src/screenshotTest/` — Compose Preview Screenshot Testing, powers the Play Store screenshot pipeline
|
||||||
|
|
||||||
## Key Dependencies
|
## Key Dependencies
|
||||||
|
|
||||||
- **Hilt**: Dependency injection framework
|
- **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
|
- **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`)
|
- **fix**: Bug fixes (e.g., `fix: Handle display cutouts in landscape mode`)
|
||||||
- **feat**: New features
|
- **feat**: New features
|
||||||
- **refactor**: Code restructuring without behavior change
|
- **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
|
- **chore**: Maintenance, dependency updates, build config
|
||||||
- **docs**: Documentation changes
|
- **docs**: Documentation changes
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ globs:
|
|||||||
When adding new user-facing strings:
|
When adding new user-facing strings:
|
||||||
|
|
||||||
- **Always use string resources**: Never hardcode user-facing text in layouts or code
|
- **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
|
- **Provide context**: String names should indicate usage and location
|
||||||
- **Consider pluralization**: Use Android plural resources (`<plurals>`) when quantities vary
|
- **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_create_title` (screen title)
|
||||||
- `profiles_name_label` (form field label)
|
- `profiles_name_label` (form field label)
|
||||||
- `profiles_delete_confirmation` (dialog message)
|
- `profiles_name_default` (default value)
|
||||||
- `error_network_unavailable` (error message)
|
- `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 |
|
| File | Purpose |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| `app/src/debug/java/.../screenshots/ScreenshotContent.kt` | Mock data composables for each screen (8 screens) |
|
| `app/src/debug/java/.../screenshots/ScreenshotContent.kt` | Mock data composables for each screen (7 screens) |
|
||||||
| `app/src/screenshotTest/kotlin/.../screenshots/PlayStoreScreenshots.kt` | `@PreviewTest` functions that wire content to locale annotations |
|
| `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) |
|
| `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 |
|
| `fastlane/copy_screenshots.sh` | Copies rendered PNGs into fastlane structure |
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
```bash
|
```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
|
./fastlane/generate_screenshots.sh
|
||||||
|
|
||||||
# Smoke test — 6 locales (en, de, ja, ar, zh-CN, pt-BR), single batch
|
# 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)
|
# Copy into fastlane directories (run after generate)
|
||||||
./fastlane/copy_screenshots.sh
|
./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
|
./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)
|
4. Update the expected count in `generate_screenshots.sh` (composables per locale)
|
||||||
5. Run the full pipeline: `generate_screenshots.sh` then `copy_screenshots.sh`
|
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
|
## After UI Changes
|
||||||
|
|
||||||
When modifying a screen that appears in screenshots (check `ScreenshotContent.kt`), regenerate:
|
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
|
## 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
|
- Gradle daemon is stopped between batches to release memory
|
||||||
- `PlayStoreLocales.kt` is temporarily rewritten per batch and restored via trap
|
- `PlayStoreLocales.kt` is temporarily rewritten per batch and restored via trap
|
||||||
- Device spec: 1080x2400px @ 428 DPI (Pixel-class phone)
|
- Device spec: 1080x2400px @ 428 DPI (Pixel-class phone)
|
||||||
|
|||||||
Reference in New Issue
Block a user