Author SHA1 Message Date
makiolaj a390296dc2 chore(release): 1.0.0-beta.6
Beta — Codeberg pre-release / detect (push) Successful in 5s
Beta — Codeberg pre-release / beta (push) Successful in 12m8s
2026-10-28 14:46:02 +01:00
makiolaj c4fd0476e4 Merge branch 'feat/pure-black-theme' into release/v1.0.0 2026-10-28 14:46:02 +01:00
makiolaj b03b23da30 feat(appearance): a Black theme for AMOLED screens
Adds Black next to Light and Dark in the theme picker: the dark theme
with pure black backgrounds, applied wherever the app theme is used.
Moves floret-kit to main, which brings ThemeMode.BLACK and the theme's
pureBlack, and has the submodule track main instead of feat/clockula.
2026-10-28 14:46:02 +01:00
12 changed files with 45 additions and 8 deletions

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
[submodule "floret-kit"]
path = floret-kit
url = https://codeberg.org/jlmakiola/floret-kit.git
branch = feat/clockula
branch = main
+2
View File
@@ -87,6 +87,8 @@ The first release of Clockula: alarms, timers, a stopwatch and a world clock.
with Clockula. A voice-set alarm cleans up after itself once dismissed.
- Settings: theme, dynamic colour, language, and the defaults every new alarm
and timer inherits.
- A Black theme next to Light and Dark: dark with pure black backgrounds,
for AMOLED screens.
- Choose how long a ringing alarm or a finished timer keeps going before it
stops on its own: 1, 5, 10, 15, 20 or 25 minutes, or Never, separately for
alarms and timers. Ten minutes stays the default, and Never still stops
+2 -2
View File
@@ -32,8 +32,8 @@ android {
// versionCode is derived from versionName by scripts/version_info.sh
// (1.0.0-beta.1 -> 1000001, 1.0.0 -> 1000099), and CI fails if the
// committed one doesn't match. See docs/RELEASING.md.
versionCode = 1000005
versionName = "1.0.0-beta.5"
versionCode = 1000006
versionName = "1.0.0-beta.6"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -30,6 +30,7 @@ import de.jeanlucmakiola.floret.crash.CrashReporter
import de.jeanlucmakiola.floret.crash.submitCrashReport
import de.jeanlucmakiola.floret.prefs.Appearance
import de.jeanlucmakiola.floret.prefs.isDark
import de.jeanlucmakiola.floret.prefs.isPureBlack
import javax.inject.Inject
/**
@@ -105,6 +106,7 @@ class MainActivity : ComponentActivity() {
ClockulaTheme(
darkTheme = darkTheme,
dynamicColor = appearance.dynamicColor,
pureBlack = appearance.themeMode.isPureBlack,
) {
ClockulaShell(
modifier = Modifier.fillMaxSize(),
@@ -18,6 +18,7 @@ import de.jeanlucmakiola.clockula.ui.theme.ClockulaTheme
import de.jeanlucmakiola.floret.components.CollapsingScaffold
import de.jeanlucmakiola.floret.prefs.Appearance
import de.jeanlucmakiola.floret.prefs.isDark
import de.jeanlucmakiola.floret.prefs.isPureBlack
import javax.inject.Inject
/**
@@ -47,7 +48,11 @@ class ScreenSaverSettingsActivity : ComponentActivity() {
navigationBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT) { darkTheme },
)
}
ClockulaTheme(darkTheme = darkTheme, dynamicColor = appearance.dynamicColor) {
ClockulaTheme(
darkTheme = darkTheme,
dynamicColor = appearance.dynamicColor,
pureBlack = appearance.themeMode.isPureBlack,
) {
CollapsingScaffold(title = stringResource(R.string.screen_saver_title), onBack = ::finish) {
ScreenSaverSettingsSection(inApp = false)
}
@@ -140,6 +140,9 @@ internal fun AppearanceScreen(
options = ThemeMode.entries.toList(),
selected = state.appearance.themeMode,
label = { themeModeLabel(it) },
summary = { mode ->
if (mode == ThemeMode.BLACK) stringResource(R.string.settings_theme_black_summary) else null
},
onSelect = viewModel::onThemeModeSelected,
onDismiss = { showTheme = false },
)
@@ -152,6 +155,7 @@ private fun themeModeLabel(mode: ThemeMode): String = stringResource(
ThemeMode.SYSTEM -> R.string.settings_theme_system
ThemeMode.LIGHT -> R.string.settings_theme_light
ThemeMode.DARK -> R.string.settings_theme_dark
ThemeMode.BLACK -> R.string.settings_theme_black
},
)
@@ -9,12 +9,14 @@ import de.jeanlucmakiola.floret.identity.FloretExpressiveTheme
* colour on API 31+, system light/dark, the standard motion scheme) fed with
* Clockula's own identity — its seed-derived fallback schemes
* ([ClockulaLightFallback] / [ClockulaDarkFallback]) and [ClockulaTypography].
* The mechanics live in floret-kit; the look stays here.
* The mechanics live in floret-kit; the look stays here. [pureBlack] is the
* AMOLED take on the dark theme (`ThemeMode.BLACK`).
*/
@Composable
fun ClockulaTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
pureBlack: Boolean = false,
content: @Composable () -> Unit,
) {
FloretExpressiveTheme(
@@ -22,6 +24,7 @@ fun ClockulaTheme(
darkScheme = ClockulaDarkFallback,
darkTheme = darkTheme,
dynamicColor = dynamicColor,
pureBlack = pureBlack,
typography = ClockulaTypography,
content = content,
)
@@ -27,6 +27,7 @@ import de.jeanlucmakiola.clockula.ui.widget.config.QuickTimerPicker
import de.jeanlucmakiola.clockula.ui.widget.openAppIntent
import de.jeanlucmakiola.floret.prefs.Appearance
import de.jeanlucmakiola.floret.prefs.isDark
import de.jeanlucmakiola.floret.prefs.isPureBlack
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -78,7 +79,11 @@ class TilePreferencesActivity : ComponentActivity() {
// before the stored duration arrives.
val duration by settingsPrefs.quickTileDuration.collectAsState(initial = null)
val darkTheme = appearance.themeMode.isDark(isSystemInDarkTheme())
ClockulaTheme(darkTheme = darkTheme, dynamicColor = appearance.dynamicColor) {
ClockulaTheme(
darkTheme = darkTheme,
dynamicColor = appearance.dynamicColor,
pureBlack = appearance.themeMode.isPureBlack,
) {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.surface) {}
val current = duration ?: return@ClockulaTheme
QuickTimerPicker(
@@ -21,6 +21,7 @@ import de.jeanlucmakiola.clockula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.clockula.ui.theme.ClockulaTheme
import de.jeanlucmakiola.floret.prefs.Appearance
import de.jeanlucmakiola.floret.prefs.isDark
import de.jeanlucmakiola.floret.prefs.isPureBlack
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -94,7 +95,11 @@ abstract class WidgetConfigActivity : ComponentActivity() {
private fun ConfigTheme(content: @Composable () -> Unit) {
val appearance by settingsPrefs.appearance.collectAsState(initial = Appearance())
val darkTheme = appearance.themeMode.isDark(isSystemInDarkTheme())
ClockulaTheme(darkTheme = darkTheme, dynamicColor = appearance.dynamicColor) {
ClockulaTheme(
darkTheme = darkTheme,
dynamicColor = appearance.dynamicColor,
pureBlack = appearance.themeMode.isPureBlack,
) {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.surface) {}
content()
}
+2
View File
@@ -329,6 +329,8 @@
<string name="settings_theme_system">Follow system</string>
<string name="settings_theme_light">Light</string>
<string name="settings_theme_dark">Dark</string>
<string name="settings_theme_black">Black</string>
<string name="settings_theme_black_summary">Dark with pure black backgrounds, for AMOLED screens</string>
<string name="settings_dynamic_color">Dynamic colour</string>
<string name="settings_dynamic_color_hint">Use colours from your wallpaper</string>
<string name="settings_dynamic_color_unavailable">Needs Android 12 or newer</string>
@@ -77,6 +77,15 @@ class SettingsPrefsTest {
assertThat(prefs.themeMode.first()).isEqualTo(ThemeMode.DARK)
}
@Test
fun `the pure-black theme mode round-trips`(@TempDir tempDir: Path) = runTest {
val prefs = SettingsPrefs(PrefStore(newDataStore(tempDir)))
prefs.setThemeMode(ThemeMode.BLACK)
assertThat(prefs.themeMode.first()).isEqualTo(ThemeMode.BLACK)
}
@Test
fun `backup settings are applied together using normal preference clamps`(@TempDir tempDir: Path) = runTest {
val prefs = SettingsPrefs(PrefStore(newDataStore(tempDir)))