Imported from aaalaniz/compose-lightsaber (
AGENTS.md). Install upstream withnpx skills add aaalaniz/compose-lightsaber. Copyright stays with the author.
AGENTS.md
Project Overview
Compose Lightsaber is a toy lightsaber app built for kids using Compose Multiplatform and Circuit architecture. The app simulates a lightsaber with visual blade animations, authentic sound effects, motion-based swing detection, and customizable blade colors.
Key Features
- ✨ Animated lightsaber blade with activation/deactivation sequences
- 🎵 Authentic Star Wars lightsaber sound effects (activation, idle hum, swing sounds, clashing)
- 📱 Motion detection for swing interactions using device accelerometer
- 🎨 Customizable blade colors (Green, Red, Yellow, Blue, Purple)
- 💾 Persistent user settings using DataStore
- 🔄 Cross-platform support (Android & iOS)
Architecture
Tech Stack
- UI Framework: Compose Multiplatform
- Architecture: Circuit
- Dependency Injection: Metro
- State Management: Kotlin StateFlow + DataStore for persistence
- Audio: Platform-specific sound players (MediaPlayer on Android, AVAudioPlayer on iOS)
- Motion Detection: Platform-specific accelerometer implementations
- Build System: Kotlin Multiplatform with Gradle
Project Structure
compose-lightsaber/
├── androidApp/ # Android-specific application module
│ └── src/androidMain/
│ ├── kotlin/xyz/alaniz/aaron/lightsaber/
│ │ └── MainActivity.kt # Android entry point
│ └── AndroidManifest.xml
├── iosApp/ # iOS-specific application
│ ├── iosApp/
│ │ ├── ContentView.swift # SwiftUI wrapper for Compose
│ │ └── iOSApp.swift # iOS app entry point
│ └── Podfile # CocoaPods dependencies
├── shared/ # Umbrella multiplatform module
├── core/ # Core utility modules
│ ├── ui/
│ ├── data/
│ ├── audio/
│ └── motion/
├── feature/ # Feature modules
│ ├── lightsaber/
│ └── settings/
│ ├── src/
│ │ ├── androidMain/ # Android-specific implementations
│ │ ├── iosMain/ # iOS-specific implementations
│ │ ├── commonMain/ # Shared code
│ │ └── commonTest/ # Shared tests
│ └── build.gradle.kts
├── gradle/libs.versions.toml # Version catalog
└── settings.gradle.kts
Core Components
1. UI Layer (core:ui, feature:lightsaber, feature:settings)
Screens
- LightsaberScreen: Main lightsaber interface with blade animation
- SettingsScreen: Color customization and app settings
Key UI Components
- LightsaberUi.kt: Main lightsaber interface with animated blade
- SettingsUi.kt: Settings interface with color picker
- Theme.kt: App theming with Star Wars-inspired colors
Presenters (Circuit Architecture)
- LightsaberPresenter: Manages lightsaber state, sound effects, and motion detection
- SettingsPresenter: Handles settings state and persistence
2. Data Layer (core:data)
Models
data class LightsaberSettings(
val bladeColor: Color
)
Repository
- SettingsRepository: Manages persistent user settings using DataStore
- RealSettingsRepository: Implementation with reactive StateFlow
3. Audio System (core:audio)
Platform Abstractions
- SoundPlayer: Common interface for audio playback
- SoundResource: Represents audio files with metadata
Platform Implementations
- AndroidSoundPlayer: Uses Android MediaPlayer for audio
- IosSoundPlayer: Uses iOS AVAudioPlayer for audio
Sound Effects Included
lightsaber_activating.m4a- Blade ignition soundlightsaber_deactivating.m4a- Blade shutdown soundlightsaber_idle.m4a- Continuous humming while activelightsaber_hum1/2/3.m4a- Swing movement soundslightsaber_clash1/2/3.m4a- Combat clash sounds
4. Motion Detection (core:motion)
Common Interface
- SwingDetector: Detects device motion for swing interactions
- SwingEvent: Represents motion events
Platform Implementations
- AndroidSwingDetector: Uses Android SensorManager and accelerometer
- IosSwingDetector: Uses iOS CoreMotion framework
5. Dependency Injection (shared/src/commonMain/kotlin/xyz/alaniz/aaron/lightsaber/di/)
Metro Architecture
The project uses Metro as the core DI framework. Metro provides a Dagger-like API for Kotlin Multiplatform with compile-time safety and Anvil-like contribution capabilities.
Common Components (shared/src/commonMain/kotlin/xyz/alaniz/aaron/lightsaber/di/metro/)
- CircuitProvider: Provides Circuit framework setup
- DatastoreProvider: Configures persistent storage with DataStore
Platform-Specific Components
Android (shared/src/androidMain/kotlin/xyz/alaniz/aaron/lightsaber/di/metro/):
- AndroidApplicationGraph: Main Android DI graph using
@DependencyGraph - AndroidSoundProvider: Audio system bindings for Android
- AndroidMotionProvider: Motion detection bindings for Android
iOS (shared/src/iosMain/kotlin/xyz/alaniz/aaron/lightsaber/di/metro/):
- IosApplicationGraph: Main iOS DI graph using
@DependencyGraph - IosSoundProvider: Audio system bindings for iOS
- IosMotionProvider: Motion detection bindings for iOS
Key Annotations Used
@Inject: Marks classes for dependency injection@Provides: Defines a provider function for a dependency@ContributesTo(AppScope::class): Contributes providers or subcomponents to the application scope@ContributesBinding(AppScope::class): Binds implementations to interfaces@DependencyGraph: Defines the root of a dependency graph@SingleIn(AppScope::class): Ensures singleton instances within app scope@Assisted,@AssistedFactory,@AssistedInject: For assisted injection (used with Circuit)
State Management
Circuit Architecture Pattern
The app uses Circuit's unidirectional data flow:
Screen → Presenter → State → UI → Events → Presenter
Circuit Integration with Metro
Circuit code generation is configured to work with Metro:
ksp {
arg("circuit.codegen.mode", "metro")
}
Lightsaber State Flow
sealed interface LightsaberEvent : CircuitUiEvent {
data object LightsaberActivating
data object LightsaberActivated
data object LightsaberDeactivating
data object LightsaberDeactivated
data object SettingsSelected
}
enum class BladeState {
Initializing, Deactivated, Activating, Activated, Deactivating
}
data class LightsaberState(
val bladeState: BladeState,
val bladeColor: Color,
val onEvent: (LightsaberEvent) -> Unit
) : CircuitUiState
Data Persistence
- DataStore Preferences: Stores user settings (blade color)
- Reactive Updates: StateFlow provides real-time UI updates
- Platform-specific paths: Different storage locations per platform
Platform-Specific Features
Android (shared/src/androidMain/)
- MainActivity: Entry point using ComponentActivity
- AndroidSoundPlayer: MediaPlayer-based audio implementation
- AndroidSwingDetector: SensorManager-based motion detection
- Splash Screen: Core splash screen support
iOS (shared/src/iosMain/)
- ContentView.swift: SwiftUI wrapper for Compose UI
- IosSoundPlayer: AVAudioPlayer-based audio implementation
- IosSwingDetector: CoreMotion-based accelerometer access
- Exception Handling: Unhandled exception logging
Resources
Audio Assets (shared/src/commonMain/composeResources/files/raw/)
- 9 M4A audio files for various lightsaber sound effects
- Organized for cross-platform resource access
Drawable Assets (shared/src/commonMain/composeResources/drawable/)
- lightsaber_handle.xml: Vector drawable for lightsaber hilt
- Platform-specific app icons and launcher resources
String Resources (shared/src/commonMain/composeResources/values/)
- Localized strings for UI text elements
Testing
Test Structure (shared/src/commonTest/)
- LightsaberUiTest: UI interaction tests using Compose testing
- LightsaberRobot: Test robot pattern for UI interactions
- LightsaberColor: Test fixtures for color validation
- Burst Testing: Property-based testing support
Testing Tools
- Kotlin Test framework
- Compose UI Test utilities
- Circuit testing support
- Robot pattern for clean test interactions
End-to-End (E2E) Testing
This project uses maestro.dev for E2E testing. The tests are located in the
.maestro directory.
To run the tests locally, use the following command:
maestro test .maestro
These tests are also run automatically on pull requests in the e2e-test-androidApp and
e2e-test-iosApp GitHub Actions jobs.
Note: The Maestro tests rely on accessibility labels to find UI elements.
Build Configuration
Gradle Setup
- Kotlin 2.3.21 with Compose compiler plugin
- KSP 2.2.20-2.0.4 for Circuit and Metro code generation
- Metro 1.1.1 for dependency injection
- CocoaPods integration for iOS dependencies
Metro Configuration
The build is configured to use Metro for DI:
plugins {
alias(libs.plugins.metro)
}
ksp {
arg("circuit.codegen.mode", "metro")
}
Version Catalog (gradle/libs.versions.toml)
Centralized dependency management for:
- Compose Multiplatform versions
- Circuit framework versions
- Metro versions
- Android/iOS specific dependencies
- Testing framework versions
Development Workflow
Key Commands
# Android build and run
./gradlew :androidApp:assembleDebug
./gradlew :androidApp:installDebug
# iOS build (requires Xcode)
./gradlew :shared:podInstall
# Then open iosApp/iosApp.xcworkspace in Xcode
# Run tests
./gradlew :shared:testDebugUnitTest
# Clean build
./gradlew clean
Code Generation
- Circuit: Auto-generates presenter and UI factories via KSP
- Metro: Generates dependency injection components and bindings
- KMP Resources: Processes multiplatform resources
Dependency Injection Benefits
Why Metro?
Metro provides:
- ✅ Compile-time safety with Dagger-like verification
- ✅ Excellent Kotlin Multiplatform support
- ✅ Clean, annotation-based API
- ✅ Full type safety with assisted injection support
- ✅ Modular design with automatic binding contribution (
@ContributesBinding) - ✅ Efficient code generation using KSP
DI Architecture Highlights
- Modular Design: Each platform has its own providers that contribute to the merged application graph
- Scope Management:
AppScopeensures proper singleton behavior across the application - Platform Abstraction: Common interfaces with platform-specific implementations
- Circuit Integration: Seamless integration with Circuit's factory-based presenter system
- Type Safety: Full compile-time verification of dependency graphs
This documentation was generated to help AI agents understand the Compose Lightsaber project structure, architecture, and implementation details.