OpenFlip is a Braun-inspired minimalist flip clock application for Android, focusing on aesthetic purity and realistic mechanical animations. It is a tribute project inspired by the Fliqlo design.
For detailed AI assistant skills, guides, and architectural documentation, see
.agent/.
- Language: Kotlin
- UI Frameworks:
- Jetpack Compose: Settings interface and modern UI components.
- Custom Views (XML/Canvas): High-performance flip clock engine and interactive 3D elements (Knob).
- Architecture: MVVM with reactive StateFlow updates + Hilt Dependency Injection.
- Dependencies:
Cloudy: Liquid glass blur effects.Material Components: Standard M3 design tokens.Hilt 2.55: Google's compile-time DI framework for Android
- JDK: Version 17+ recommended.
- Android Studio: Ladybug or newer.
- Gradle: Use included wrapper (
./gradlew). - Hilt Code Generation: After modifying DI modules, run
./gradlew clean buildto regenerate Hilt components.
- Install Debug:
./gradlew installDebug - Run Tests:
./gradlew test - Lint:
./gradlew lint
- Unit Tests: Located in
app/src/test. Cover ViewModel logic and state transitions. - Hilt Testing: Use
RobolectricTestRunnerfor ViewModel tests requiring Android Context - DI Verification: Run
./gradlew buildto validate dependency graph at compile time - Manual Verification:
- Verify flip animations in various orientations.
- Test theme transitions (Dark/Light) in Settings.
- Verify OLED protection shifting (subtle pixel movement).
- Test Light effect with OLED protection enabled (no edge gaps).
- Verify haptic feedback on all buttons (only Light button plays sound).
The light overlay uses canvas translation compensation to prevent edge gaps when OLED burn-in protection shifts the clock view. Key files:
FullscreenFlipClockView.onDraw(): Applies-translationX/Ybefore drawing overlayDisplayBurnInProtectionManager: AcceptsshiftAppliercallback for custom shift handlingLightOverlayRenderer: Uses 20% radius buffer for complete coverage
OpenFlip migrated from manual dependency construction to Hilt (Dagger 2) for compile-time dependency injection. This refactoring reduced ~30% of boilerplate code and improved testability.
di/
├── module/
│ ├── CoreModule.kt # ApplicationContext, CoroutineScope, ElapsedTimeSource
│ ├── ManagerModule.kt # Vibrator, interface bindings
│ └── ControllerModule.kt # Activity-scoped bindings
OpenFlipApplication.kt # @HiltAndroidApp entry point
| Component | Scope | Notes |
|---|---|---|
AppSettingsManager |
@Singleton |
Settings persistence |
HapticFeedbackManager |
@Singleton |
Haptic feedback effects |
FeedbackSoundManager |
@Singleton |
Sound effects |
TimeProvider |
@Singleton |
Time source with Flow |
FullscreenClockViewModel |
@HiltViewModel |
Auto SavedStateHandle injection |
SettingsViewModel |
@HiltViewModel |
Simple settings ViewModel |
LightToggleController |
Activity | AssistedInject with Factory |
Activity Injection:
@AndroidEntryPoint
class FullscreenClockActivity : AppCompatActivity() {
@Inject lateinit var appSettingsManager: AppSettingsManager
@Inject lateinit var haptics: HapticsProvider
}ViewModel Injection:
@HiltViewModel
class FullscreenClockViewModel @Inject constructor(
private val settingsStore: SettingsStore,
@ApplicationContext appContext: Context,
private val savedStateHandle: SavedStateHandle
) : ViewModel()AssistedInject for Views:
class LightToggleController @AssistedInject constructor(
private val settingsStore: SettingsStore,
@Assisted private val button: StateToggleGlowView
) {
@AssistedFactory
interface Factory { fun create(button: StateToggleGlowView): LightToggleController }
}- Before: Manual construction in Activity.onCreate() - 571 lines
- After: Field injection with Hilt - ~400 lines
- Deleted: FullscreenClockViewModelFactory.kt (48 lines)
- Performance: SharedPreferences batch write optimization (12 writes → 1 write)
- Compile-Time Safety: DI graph validated at build time, catching circular dependencies early
- Reduced Boilerplate: No manual factory classes or service locators
- Improved Testability: Easy to inject mock dependencies in tests
- Lifecycle Management: Hilt handles scope lifecycle automatically (@Singleton, @HiltViewModel, etc.)
- SavedStateHandle Integration: ViewModels automatically receive SavedStateHandle without factory boilerplate
- Replaced flip sound asset (
app/src/main/res/raw/flip_sound.mp3) and rebalanced volumes (flip softer, chime slightly louder). - Cached theme background color in
FullscreenFlipClockViewto avoid per-frame resolve. - Accessibility: clock announces current time; light button announces on/off and is clickable.
- Text rendering optimizations: LRU cache for ink center (0-9/AM/PM).
- Noise shader caching in
FlipCardRendererto reduce GC on theme changes. - Gesture safety: brightness dim only on single-finger scroll; pinch (multi-finger) no longer triggers dim.
- Light overlay: removed PorterDuff ADD to avoid GPU→software fallback.