Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f9e4fd3
feat(core-data): add system reduce-motion detection utility
Xitee1 Apr 20, 2026
1d3275d
feat(core-data): add launchAnimationEnabled setting with reduce-motio…
Xitee1 Apr 20, 2026
c67f8c6
feat(settings): add launch animation toggle
Xitee1 Apr 20, 2026
c42b934
feat(timer-ui): extend PlayButton with crouch + launching parameters
Xitee1 Apr 20, 2026
50a51c4
feat(timer-ui): add impact pulse rendering to CircularDial
Xitee1 Apr 20, 2026
7400626
perf(timer-ui): hoist impact ripple offsets to file-level constant
Xitee1 Apr 20, 2026
17fd9eb
feat(timer-ui): add LaunchAnimationController and overlay composable
Xitee1 Apr 20, 2026
91b6833
refactor(timer-ui): drive PlayButton scale externally instead of deri…
Xitee1 Apr 20, 2026
9f7a568
feat(timer-ui): wire launch animation into TimerScreen
Xitee1 Apr 20, 2026
cb6633b
refactor(timer-ui): move play icon entirely to overlay and widen impa…
Xitee1 Apr 20, 2026
e07429e
polish(timer-ui): add windup phase and punchier impact
Xitee1 Apr 20, 2026
71d5e87
feat(timer-ui): add rocket trail and move shockwaves to ring
Xitee1 Apr 20, 2026
e10f1da
polish(timer-ui): split rocket travel into three eased segments
Xitee1 Apr 20, 2026
0c0b687
polish(timer-ui): smooth speed transitions and hide stop icon during …
Xitee1 Apr 20, 2026
3e130cc
polish(timer-ui): add explicit hold between lift-off and cruise
Xitee1 Apr 20, 2026
08db9db
revert(timer-ui): restore 3-chained ease-in-out pacing for launch
Xitee1 Apr 20, 2026
e110501
docs: add launch animation design spec and implementation plan
Xitee1 Apr 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ data class UserSettings(
val starsEnabled: Boolean = true,
val stepMinutes: Int = 5,
val presetMinutes: Int = 15,
val launchAnimationEnabled: Boolean = true,
)
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ interface SettingsRepository {
suspend fun updateStarsEnabled(enabled: Boolean)
suspend fun updateStepMinutes(minutes: Int)
suspend fun updatePresetMinutes(minutes: Int)
suspend fun updateLaunchAnimationEnabled(enabled: Boolean)
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
package dev.xitee.sleeptimer.core.data.repository

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import dagger.hilt.android.qualifiers.ApplicationContext
import dev.xitee.sleeptimer.core.data.model.ThemeId
import dev.xitee.sleeptimer.core.data.model.UserSettings
import dev.xitee.sleeptimer.core.data.util.isSystemReduceMotionEnabled
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class SettingsRepositoryImpl @Inject constructor(
private val dataStore: DataStore<Preferences>,
@ApplicationContext private val context: Context,
) : SettingsRepository {

private companion object {
Expand All @@ -30,6 +38,27 @@ class SettingsRepositoryImpl @Inject constructor(
val STARS_ENABLED = booleanPreferencesKey("stars_enabled")
val STEP_MINUTES = intPreferencesKey("step_minutes")
val PRESET_MINUTES = intPreferencesKey("preset_minutes")
val LAUNCH_ANIMATION_ENABLED = booleanPreferencesKey("launch_animation_enabled")
val LAUNCH_ANIMATION_SEEDED = booleanPreferencesKey("launch_animation_seeded")
}

// Einmaliger Init-Scope. IO-Dispatcher ist angemessen für DataStore-Writes,
// SupervisorJob verhindert dass eine Child-Exception weitere Writes stoppt.
private val initScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

init {
// Seed-on-first-install: ist der „seeded"-Flag nicht gesetzt, wird das
// launchAnimationEnabled-Feld einmalig basierend auf der System-Reduce-Motion-
// Präferenz persistiert. Danach gewinnen User-Overrides. Spätere System-Änderungen
// werden bewusst nicht reflektiert (siehe Spec, Out-of-Scope).
initScope.launch {
dataStore.edit { prefs ->
if (prefs[LAUNCH_ANIMATION_SEEDED] != true) {
prefs[LAUNCH_ANIMATION_ENABLED] = !isSystemReduceMotionEnabled(context)
prefs[LAUNCH_ANIMATION_SEEDED] = true
}
}
}
}

override val settings: Flow<UserSettings> = dataStore.data.map { prefs ->
Expand All @@ -48,6 +77,7 @@ class SettingsRepositoryImpl @Inject constructor(
starsEnabled = prefs[STARS_ENABLED] ?: d.starsEnabled,
stepMinutes = prefs[STEP_MINUTES] ?: d.stepMinutes,
presetMinutes = prefs[PRESET_MINUTES] ?: d.presetMinutes,
launchAnimationEnabled = prefs[LAUNCH_ANIMATION_ENABLED] ?: d.launchAnimationEnabled,
)
}

Expand Down Expand Up @@ -94,4 +124,8 @@ class SettingsRepositoryImpl @Inject constructor(
override suspend fun updatePresetMinutes(minutes: Int) {
dataStore.edit { it[PRESET_MINUTES] = minutes.coerceIn(1, 300) }
}

override suspend fun updateLaunchAnimationEnabled(enabled: Boolean) {
dataStore.edit { it[LAUNCH_ANIMATION_ENABLED] = enabled }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.xitee.sleeptimer.core.data.util

import android.content.Context
import android.provider.Settings

/**
* Gibt true zurück, wenn der Nutzer in den System-Einstellungen „Animationen entfernen"
* aktiviert hat. Erkennung über `Settings.Global.ANIMATOR_DURATION_SCALE == 0f`, was
* von Accessibility-Settings und den Developer-Options identisch gesetzt wird.
*/
fun isSystemReduceMotionEnabled(context: Context): Boolean {
val scale = Settings.Global.getFloat(
context.contentResolver,
Settings.Global.ANIMATOR_DURATION_SCALE,
1f,
)
return scale == 0f
}
Loading
Loading