From 4e279ca44d36a778976f817a595079bc4a94b800 Mon Sep 17 00:00:00 2001 From: darken Date: Sat, 7 Feb 2026 19:30:19 +0100 Subject: [PATCH] chore: Restructure Claude Code config into modular rules Migrate monolithic root CLAUDE.md into .claude/rules/ structure with focused topic files and glob-based contextual loading. Clean up leftover helper scripts in .claude/tmp/. --- .claude/CLAUDE.md | 45 ++++++ .claude/rules/agent-instructions.md | 39 ++++++ .claude/rules/architecture.md | 85 ++++++++++++ .claude/rules/build-commands.md | 71 ++++++++++ .claude/rules/commit-guidelines.md | 41 ++++++ .claude/rules/localization.md | 21 +++ .claude/settings.json | 11 ++ .claude/tmp/fix_imports.py | 59 -------- .claude/tmp/fix_qualified_r_references.py | 58 -------- .claude/tmp/fix_remaining_r_imports.py | 87 ------------ .claude/tmp/merge_strings.py | 105 -------------- CLAUDE.md | 158 ---------------------- 12 files changed, 313 insertions(+), 467 deletions(-) create mode 100644 .claude/CLAUDE.md create mode 100644 .claude/rules/agent-instructions.md create mode 100644 .claude/rules/architecture.md create mode 100644 .claude/rules/build-commands.md create mode 100644 .claude/rules/commit-guidelines.md create mode 100644 .claude/rules/localization.md delete mode 100644 .claude/tmp/fix_imports.py delete mode 100644 .claude/tmp/fix_qualified_r_references.py delete mode 100644 .claude/tmp/fix_remaining_r_imports.py delete mode 100644 .claude/tmp/merge_strings.py delete mode 100644 CLAUDE.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 00000000..aae19f52 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,45 @@ +# CAPod - Companion for AirPods + +Android app that detects and monitors AirPods via Bluetooth LE. Displays battery levels, triggers popup notifications on case open, and provides home screen widgets. + +## Project Structure + +| Module | Description | +|--------|-------------| +| `app/` | Main Android app (FOSS and Google Play flavors) | +| `app-common/` | Shared code between phone and Wear OS apps | + +## Build Flavors + +- **FOSS** (`foss`): Open-source, no Google Play dependencies +- **Google Play** (`gplay`): Includes billing client for IAP + +Quick build check: `./gradlew assembleFossDebug` + +## Key Locations + +| 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/res/` | Layouts, drawables, strings | +| `app-common/src/test/` | Unit tests | +| `app/build.gradle.kts` | App build config, dependencies, flavors | + +## 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 + +## Rules Reference + +Detailed guidelines are in `.claude/rules/`: + +- `architecture.md` — Module structure, key components, data flow, dependencies +- `build-commands.md` — Build, test, lint, and release commands +- `localization.md` — String resource naming conventions +- `commit-guidelines.md` — Commit message format and prefixes +- `agent-instructions.md` — Sub-agent delegation and critical thinking diff --git a/.claude/rules/agent-instructions.md b/.claude/rules/agent-instructions.md new file mode 100644 index 00000000..fbf35a5e --- /dev/null +++ b/.claude/rules/agent-instructions.md @@ -0,0 +1,39 @@ +--- +description: Instructions for Claude Code sub-agents and task delegation +globs: + - "**" +--- + +# Agent Instructions + +## Critical Thinking + +- Do not blindly accept information at face value +- Verify assumptions against actual code before proceeding +- When encountering unexpected behavior, investigate root causes rather than applying workarounds +- If something seems wrong, it probably is — dig deeper + +## Explore vs. Implement + +- **Explore first**: Before making changes, understand the existing code structure and patterns +- **Read before writing**: Always read relevant files before modifying them +- **Follow existing patterns**: Match the code style and architecture already in use +- **Minimal changes**: Only change what's necessary to accomplish the task + +## Sub-Agent Delegation + +When using Task tool to spawn sub-agents: + +- Provide complete context — sub-agents don't share your conversation history unless noted +- Be specific about what you need: research only, or research + implementation +- Use `Explore` agent type for codebase investigation +- Use `Bash` agent type for running builds and tests +- Parallelize independent sub-agent tasks for efficiency + +## Common Pitfalls + +- Don't create new files when editing existing ones would suffice +- Don't add features beyond what was requested +- Don't refactor surrounding code when fixing a bug +- Don't add comments or documentation to code you didn't change +- Don't guess at file paths — use Glob/Grep to find them diff --git a/.claude/rules/architecture.md b/.claude/rules/architecture.md new file mode 100644 index 00000000..9c22bfbf --- /dev/null +++ b/.claude/rules/architecture.md @@ -0,0 +1,85 @@ +--- +description: Architecture overview, module structure, key components, data flow, and dependencies +globs: + - "app/**/*.kt" + - "app-common/**/*.kt" + - "**/*.gradle.kts" +--- + +# Architecture + +## Multi-Module Structure + +- **app/**: Main Android application with FOSS and Google Play flavors +- **app-common/**: Shared code between main app and Wear OS app + +## Core Patterns + +- **MVVM**: ViewModels with LiveData/StateFlow for UI state management +- **Dependency Injection**: Hilt/Dagger for dependency management +- **Coroutines**: Kotlin coroutines for async operations +- **Repository Pattern**: Data layer abstraction for monitoring and settings + +## Key Components + +### PodMonitor System + +- `PodMonitor`: Core service that detects and tracks AirPods via Bluetooth LE +- `MonitorControl`: Manages background monitoring worker lifecycle +- `MonitorWorker`: Background worker that continuously scans for AirPods +- `BluetoothEventReceiver`: Handles system Bluetooth events + +### Reaction System + +- `ReactionSettingsFragment`: Configuration for popup notifications +- `PopUpWindow`: Displays AirPods status when case is opened +- `PopUpPodViewFactory`: Creates UI components for different pod models + +### Common Utilities + +- `EdgeToEdgeHelper`: Handles Android edge-to-edge display insets + +## Build Configuration + +### Flavors + +- **FOSS**: Open-source version without Google Play dependencies +- **Google Play (gplay)**: Version with billing client for in-app purchases + +### Build Types + +- **debug**: Unobfuscated, full logging, no minification +- **beta**: Obfuscated, production-ready with strict lint checks +- **release**: Fully optimized for production distribution + +## Data Flow + +The app follows a unidirectional data flow: + +1. `BluetoothEventReceiver` detects Bluetooth events +2. `MonitorWorker` 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 `MonitorWorker` which runs as a long-lived background task. + +## 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 + +- **Unit Tests**: Located in `app-common/src/test/` for shared logic +- **Test Flavors**: Separate test configurations for FOSS and Google Play variants + +## Key Dependencies + +- **Hilt**: Dependency injection framework +- **AndroidX Navigation**: Fragment navigation with SafeArgs +- **WorkManager**: Background task scheduling for monitoring +- **Moshi**: JSON serialization for configuration and debugging +- **Material Design**: UI components following Material Design guidelines diff --git a/.claude/rules/build-commands.md b/.claude/rules/build-commands.md new file mode 100644 index 00000000..f7329d52 --- /dev/null +++ b/.claude/rules/build-commands.md @@ -0,0 +1,71 @@ +--- +description: Build, test, lint, and release commands for Gradle +globs: + - "**/*.gradle.kts" + - "**/*.gradle" + - "gradle/**" +--- + +# Build Commands + +## Build + +```bash +# Build debug version +./gradlew assembleDebug + +# Build all variants (FOSS and Google Play flavors) +./gradlew assemble + +# Build specific flavor and type +./gradlew assembleFossDebug +./gradlew assembleGplayRelease + +# Build app bundles for Play Store +./gradlew bundleGplayRelease +``` + +## Testing + +```bash +# Run all unit tests +./gradlew test + +# Run unit tests for specific variant +./gradlew testFossDebugUnitTest + +# Run instrumentation tests (requires connected device/emulator) +./gradlew connectedAndroidTest +./gradlew connectedFossDebugAndroidTest + +# Run all checks (lint + tests) +./gradlew check +``` + +## Code Quality + +```bash +# Run lint for all variants +./gradlew lint + +# Run lint for specific variant +./gradlew lintFossDebug + +# Auto-fix lint issues where possible +./gradlew lintFix + +# Update lint baseline +./gradlew updateLintBaseline +``` + +## Release + +```bash +./gradlew assembleFossRelease assembleGplayRelease +``` + +## Notes + +- Use `assembleFossDebug` as the default quick-check build (fastest variant) +- Run `./gradlew check` before submitting changes to catch lint and test issues +- Instrumentation tests require a connected device or running emulator diff --git a/.claude/rules/commit-guidelines.md b/.claude/rules/commit-guidelines.md new file mode 100644 index 00000000..b0f0200e --- /dev/null +++ b/.claude/rules/commit-guidelines.md @@ -0,0 +1,41 @@ +--- +description: Git commit message format and conventions +globs: + - "**" +--- + +# Commit Guidelines + +## Format + +``` +: +``` + +Summary line should be concise and describe the change. No period at the end. + +## Prefixes + +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 +- **chore**: Maintenance, dependency updates, build config +- **docs**: Documentation changes + +## Component Scope (optional) + +When a change is scoped to a specific area, include it after the prefix: + +- `fix(widget): Fix layout for devices with single charge detection` +- `feat(monitor): Add battery level caching` +- `refactor(popup): Extract pod view factory` + +## Rules + +- Keep the summary line under 72 characters +- Use imperative mood ("Add feature" not "Added feature") +- Reference issue numbers when applicable +- Do not include `Co-authored-by` trailers +- Look at recent `git log` output to match the project's existing style diff --git a/.claude/rules/localization.md b/.claude/rules/localization.md new file mode 100644 index 00000000..364ecfdd --- /dev/null +++ b/.claude/rules/localization.md @@ -0,0 +1,21 @@ +--- +description: Guidelines for adding and naming Android string resources +globs: + - "**/res/values*/strings.xml" +--- + +# Localization Guidelines + +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`) +- **Provide context**: String names should indicate usage and location +- **Consider pluralization**: Use Android plural resources (``) when quantities vary + +## Naming Examples + +- `profiles_create_title` (screen title) +- `profiles_name_label` (form field label) +- `profiles_delete_confirmation` (dialog message) +- `error_network_unavailable` (error message) diff --git a/.claude/settings.json b/.claude/settings.json index 39d49a5b..4fe00b61 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -8,6 +8,17 @@ "Bash(ls:*)", "Bash(grep:*)", "Bash(rg:*)", + "Bash(git add:*)", + "Bash(git log:*)", + "Bash(git commit:*)", + "Bash(git show:*)", + "Bash(git checkout:*)", + "Bash(git branch:*)", + "Bash(git stash:*)", + "Bash(git pop:*)", + "Bash(gh issue list:*)", + "Bash(gh pr list:*)", + "Bash(gh label list:*)", "WebSearch", "WebFetch(domain:support.google.com)", "WebFetch(domain:github.com)", diff --git a/.claude/tmp/fix_imports.py b/.claude/tmp/fix_imports.py deleted file mode 100644 index 12a7d348..00000000 --- a/.claude/tmp/fix_imports.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 -""" -Script to fix import statements after removing app-common module. -This script fixes R class imports from app-common to app module. -""" - -import os -import re -from pathlib import Path - -def fix_r_import(file_path): - """Fix R import in a single file.""" - print(f"Fixing R import in: {file_path}") - - with open(file_path, 'r', encoding='utf-8') as f: - content = f.read() - - # Replace the import statement - updated_content = re.sub( - r'import eu\.darken\.capod\.common\.R$', - 'import eu.darken.capod.R', - content, - flags=re.MULTILINE - ) - - if updated_content != content: - with open(file_path, 'w', encoding='utf-8') as f: - f.write(updated_content) - print(f" ✓ Updated import in {file_path}") - return True - else: - print(f" - No changes needed in {file_path}") - return False - -def main(): - """Main function to fix all import statements.""" - project_root = Path.cwd() - app_src = project_root / "app" / "src" - - # Find all Kotlin files that import eu.darken.capod.common.R - files_to_fix = [] - for kt_file in app_src.rglob("*.kt"): - with open(kt_file, 'r', encoding='utf-8') as f: - content = f.read() - if re.search(r'import eu\.darken\.capod\.common\.R$', content, re.MULTILINE): - files_to_fix.append(kt_file) - - print(f"Found {len(files_to_fix)} files with incorrect R imports") - - success_count = 0 - for file_path in files_to_fix: - if fix_r_import(file_path): - success_count += 1 - - print(f"\nFixed imports in {success_count} files") - return 0 - -if __name__ == "__main__": - exit(main()) \ No newline at end of file diff --git a/.claude/tmp/fix_qualified_r_references.py b/.claude/tmp/fix_qualified_r_references.py deleted file mode 100644 index 0718a60b..00000000 --- a/.claude/tmp/fix_qualified_r_references.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -""" -Script to fix fully qualified R references in code. -This script finds and replaces eu.darken.capod.common.R with R. -""" - -import os -import re -from pathlib import Path - -def fix_qualified_r_references(file_path): - """Fix fully qualified R references in a single file.""" - print(f"Fixing qualified R references in: {file_path}") - - with open(file_path, 'r', encoding='utf-8') as f: - content = f.read() - - # Replace fully qualified references - updated_content = re.sub( - r'\beu\.darken\.capod\.common\.R\.', - 'R.', - content - ) - - if updated_content != content: - with open(file_path, 'w', encoding='utf-8') as f: - f.write(updated_content) - print(f" ✓ Fixed qualified R references in {file_path}") - return True - else: - print(f" - No changes needed in {file_path}") - return False - -def main(): - """Main function to fix all qualified R references.""" - project_root = Path.cwd() - app_src = project_root / "app" / "src" - - # Find all Kotlin files that have qualified R references - files_to_fix = [] - for kt_file in app_src.rglob("*.kt"): - with open(kt_file, 'r', encoding='utf-8') as f: - content = f.read() - if re.search(r'\beu\.darken\.capod\.common\.R\.', content): - files_to_fix.append(kt_file) - - print(f"Found {len(files_to_fix)} files with qualified R references") - - success_count = 0 - for file_path in files_to_fix: - if fix_qualified_r_references(file_path): - success_count += 1 - - print(f"\nFixed qualified R references in {success_count} files") - return 0 - -if __name__ == "__main__": - exit(main()) \ No newline at end of file diff --git a/.claude/tmp/fix_remaining_r_imports.py b/.claude/tmp/fix_remaining_r_imports.py deleted file mode 100644 index 0cafd661..00000000 --- a/.claude/tmp/fix_remaining_r_imports.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python3 -""" -Script to fix remaining R import issues. -This script finds files that use R.* but don't have R imports and adds them. -""" - -import os -import re -from pathlib import Path - -def needs_r_import(file_path): - """Check if file uses R.* but doesn't import R.""" - with open(file_path, 'r', encoding='utf-8') as f: - content = f.read() - - # Check if file uses R.something - uses_r = re.search(r'\bR\.[a-zA-Z_]', content) - if not uses_r: - return False - - # Check if file already imports R - has_import = re.search(r'import.*\.R$', content, re.MULTILINE) - if has_import: - return False - - return True - -def add_r_import(file_path): - """Add R import to a file.""" - print(f"Adding R import to: {file_path}") - - with open(file_path, 'r', encoding='utf-8') as f: - content = f.read() - - # Find the package line - package_match = re.search(r'^package\s+[^\n]+$', content, re.MULTILINE) - if not package_match: - print(f" Error: No package declaration found in {file_path}") - return False - - # Find existing imports - import_pattern = r'^import\s+[^\n]+$' - existing_imports = list(re.finditer(import_pattern, content, re.MULTILINE)) - - if existing_imports: - # Insert after the last import - last_import = existing_imports[-1] - insert_pos = last_import.end() - updated_content = (content[:insert_pos] + - "\nimport eu.darken.capod.R" + - content[insert_pos:]) - else: - # Insert after package line - insert_pos = package_match.end() - updated_content = (content[:insert_pos] + - "\n\nimport eu.darken.capod.R" + - content[insert_pos:]) - - with open(file_path, 'w', encoding='utf-8') as f: - f.write(updated_content) - - print(f" ✓ Added R import to {file_path}") - return True - -def main(): - """Main function to fix all R import issues.""" - project_root = Path.cwd() - app_src = project_root / "app" / "src" - - # Find all Kotlin files that need R import - files_to_fix = [] - for kt_file in app_src.rglob("*.kt"): - if needs_r_import(kt_file): - files_to_fix.append(kt_file) - - print(f"Found {len(files_to_fix)} files that need R imports") - - success_count = 0 - for file_path in files_to_fix: - if add_r_import(file_path): - success_count += 1 - - print(f"\nAdded R imports to {success_count} files") - return 0 - -if __name__ == "__main__": - exit(main()) \ No newline at end of file diff --git a/.claude/tmp/merge_strings.py b/.claude/tmp/merge_strings.py deleted file mode 100644 index d457f5e0..00000000 --- a/.claude/tmp/merge_strings.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 -""" -Script to merge strings.xml files from app-common module to app module. -This script merges all string entries from app-common into the corresponding app module files. -""" - -import os -import re -import xml.etree.ElementTree as ET -from pathlib import Path - -def merge_strings_xml(app_common_file, app_file): - """Merge strings from app-common file into app file.""" - print(f"Processing: {app_common_file.name} -> {app_file.name}") - - # Read the app-common strings.xml content - with open(app_common_file, 'r', encoding='utf-8') as f: - common_content = f.read() - - # Read the app strings.xml content - with open(app_file, 'r', encoding='utf-8') as f: - app_content = f.read() - - # Extract string entries from app-common (including comments) - # Find everything between and tags, excluding the tags themselves - common_match = re.search(r']*>(.*?)', common_content, re.DOTALL) - if not common_match: - print(f" Warning: No resources found in {app_common_file}") - return False - - common_strings = common_match.group(1).strip() - - if not common_strings: - print(f" Warning: No string content found in {app_common_file}") - return False - - # Find the insertion point in the app file (before ) - app_match = re.search(r'(.*?)(\s*)', app_content, re.DOTALL) - if not app_match: - print(f" Error: Invalid XML structure in {app_file}") - return False - - # Merge the content - before_closing = app_match.group(1) - closing_tag = app_match.group(2) - - # Add the common strings with proper spacing - merged_content = f"{before_closing}\n\n \n{common_strings}\n{closing_tag}" - - # Write the merged content back to the app file - with open(app_file, 'w', encoding='utf-8') as f: - f.write(merged_content) - - print(f" ✓ Merged successfully") - return True - -def main(): - """Main function to merge all strings.xml files.""" - project_root = Path.cwd() - app_common_res = project_root / "app-common" / "src" / "main" / "res" - app_res = project_root / "app" / "src" / "main" / "res" - - if not app_common_res.exists(): - print(f"Error: app-common resources directory not found: {app_common_res}") - return 1 - - if not app_res.exists(): - print(f"Error: app resources directory not found: {app_res}") - return 1 - - # Find all strings.xml files in app-common - common_strings_files = list(app_common_res.glob("*/strings.xml")) - - if not common_strings_files: - print("Error: No strings.xml files found in app-common") - return 1 - - print(f"Found {len(common_strings_files)} strings.xml files to merge") - - success_count = 0 - error_count = 0 - - for common_file in sorted(common_strings_files): - # Determine the corresponding app file - locale_dir = common_file.parent.name - app_file = app_res / locale_dir / "strings.xml" - - if not app_file.exists(): - print(f" Error: Corresponding app file does not exist: {app_file}") - error_count += 1 - continue - - if merge_strings_xml(common_file, app_file): - success_count += 1 - else: - error_count += 1 - - print(f"\nMerge complete:") - print(f" ✓ Successfully merged: {success_count}") - print(f" ✗ Errors: {error_count}") - - return 0 if error_count == 0 else 1 - -if __name__ == "__main__": - exit(main()) \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 49711898..00000000 --- a/CLAUDE.md +++ /dev/null @@ -1,158 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Commands - -### Build Commands - -```bash -# Build debug version -./gradlew assembleDebug - -# Build all variants (FOSS and Google Play flavors) -./gradlew assemble - -# Build specific flavor and type -./gradlew assembleFossDebug -./gradlew assembleGplayRelease - -# Build app bundles for Play Store -./gradlew bundleGplayRelease -``` - -### Testing Commands - -```bash -# Run all unit tests -./gradlew test - -# Run unit tests for specific variant -./gradlew testFossDebugUnitTest - -# Run instrumentation tests (requires connected device/emulator) -./gradlew connectedAndroidTest -./gradlew connectedFossDebugAndroidTest - -# Run all checks (lint + tests) -./gradlew check -``` - -### Code Quality Commands - -```bash -# Run lint for all variants -./gradlew lint - -# Run lint for specific variant -./gradlew lintFossDebug - -# Auto-fix lint issues where possible -./gradlew lintFix - -# Update lint baseline -./gradlew updateLintBaseline -``` - -### Release Commands - -```bash -./gradlew assembleFossRelease assembleGplayRelease -``` - -## Architecture Overview - -### Multi-Module Structure - -- **app/**: Main Android application with FOSS and Google Play flavors -- **app-common/**: Shared code between main app - -### Core Architecture Patterns - -- **MVVM**: ViewModels with LiveData/StateFlow for UI state management -- **Dependency Injection**: Hilt/Dagger for dependency management -- **Coroutines**: Extensive use of Kotlin coroutines for async operations -- **Repository Pattern**: Data layer abstraction for monitoring and settings - -### Key Components - -#### PodMonitor System - -- `PodMonitor`: Core service that detects and tracks AirPods via Bluetooth LE -- `MonitorControl`: Manages background monitoring worker lifecycle -- `MonitorWorker`: Background worker that continuously scans for AirPods -- `BluetoothEventReceiver`: Handles system Bluetooth events - -#### Reaction System - -- `ReactionSettingsFragment`: Configuration for popup notifications -- `PopUpWindow`: Displays AirPods status when case is opened -- `PopUpPodViewFactory`: Creates UI components for different pod models - -#### Common Utilities - -- `EdgeToEdgeHelper`: Handles Android edge-to-edge display insets - -### Build Configuration - -#### Flavors - -- **FOSS**: Open-source version without Google Play dependencies -- **Google Play**: Version with billing client for in-app purchases - -#### Build Types - -- **debug**: Unobfuscated, full logging, no minification -- **beta**: Obfuscated, production-ready with strict lint checks -- **release**: Fully optimized for production distribution - -### Data Flow Architecture - -The app follows a unidirectional data flow: - -1. `BluetoothEventReceiver` detects Bluetooth events -2. `MonitorWorker` 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 - -### Testing Strategy - -- **Unit Tests**: Located in `app-common/src/test/` for shared logic -- **Test Flavors**: Separate test configurations for FOSS and Google Play variants - -### Key Dependencies - -- **Hilt**: Dependency injection framework -- **AndroidX Navigation**: Fragment navigation with SafeArgs -- **WorkManager**: Background task scheduling for monitoring -- **Moshi**: JSON serialization for configuration and debugging -- **Material Design**: UI components following Material Design guidelines - -## Development Notes - -### Bluetooth LE Implementation - -The app uses Android's Bluetooth LE APIs to scan for Apple device advertisements. The core scanning logic is in -`MonitorWorker` which runs as a long-lived background task. - -### 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. - -### Localization Guidelines - -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`) -- **Provide context**: String names should indicate usage and location -- **Consider pluralization**: Use Android plural resources (``) when quantities vary - -Examples of correct string naming: -- `profiles_create_title` (screen title) -- `profiles_name_label` (form field label) -- `profiles_delete_confirmation` (dialog message) -- `error_network_unavailable` (error message) \ No newline at end of file