Audit 9b: Dynamic color (Material You) + 48dp min touch target helpers

Dynamic color opt-in on Android 12+ via expect/actual DynamicColor:
- commonMain: expect isDynamicColorSupported() + rememberDynamicColorScheme()
- androidMain: SDK>=31 gate, dynamicLight/DarkColorScheme(context)
- iOS/JVM/JS/WASM: no-op actuals
- ThemeManager gains useDynamicColor StateFlow, persisted via ThemeStorage
- App.kt wires both currentTheme + useDynamicColor into HoneyDueTheme
- ThemeSelectionScreen exposes the "Use system colors" toggle

Touch target helpers:
- Modifier.minTouchTarget(48.dp) + Modifier.clickableWithRipple
- Applied at audit-flagged sites in CompleteTaskScreen

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-04-18 17:39:22 -05:00
parent 214908cd5c
commit ba1ec2a69b
15 changed files with 314 additions and 19 deletions

View File

@@ -21,9 +21,18 @@ actual class ThemeStorageManager(context: Context) {
prefs.edit().remove(KEY_THEME_ID).apply()
}
actual fun saveUseDynamicColor(enabled: Boolean) {
prefs.edit().putBoolean(KEY_USE_DYNAMIC_COLOR, enabled).apply()
}
actual fun getUseDynamicColor(): Boolean {
return prefs.getBoolean(KEY_USE_DYNAMIC_COLOR, false)
}
companion object {
private const val PREFS_NAME = "honeydue_theme_prefs"
private const val KEY_THEME_ID = "theme_id"
private const val KEY_USE_DYNAMIC_COLOR = "use_dynamic_color"
@Volatile
private var instance: ThemeStorageManager? = null

View File

@@ -0,0 +1,22 @@
package com.tt.honeyDue.ui.theme
import android.os.Build
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
/**
* Android actual: dynamic color (Material You) is available on Android 12+
* (API 31, [Build.VERSION_CODES.S]).
*/
actual fun isDynamicColorSupported(): Boolean =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
@Composable
actual fun rememberDynamicColorScheme(darkTheme: Boolean): ColorScheme? {
if (!isDynamicColorSupported()) return null
val context = LocalContext.current
return if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}