mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
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/.
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
description: Git commit message format and conventions
|
||||
globs:
|
||||
- "**"
|
||||
---
|
||||
|
||||
# Commit Guidelines
|
||||
|
||||
## Format
|
||||
|
||||
```
|
||||
<prefix>: <Short summary>
|
||||
```
|
||||
|
||||
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
|
||||
@@ -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 (`<plurals>`) 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)
|
||||
@@ -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)",
|
||||
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
@@ -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 <resources> and </resources> tags, excluding the tags themselves
|
||||
common_match = re.search(r'<resources[^>]*>(.*?)</resources>', 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 </resources>)
|
||||
app_match = re.search(r'(.*?)(\s*</resources>)', 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 <!-- Strings from app-common -->\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())
|
||||
@@ -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 (`<plurals>`) 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)
|
||||
Reference in New Issue
Block a user