From 50510a23da3dd74d7319d983b888b2111aad507c Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Wed, 15 Jul 2026 20:26:34 +0200 Subject: [PATCH] feat(colors): optional raw calendar colours + readable title contrast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a "Soften calendar colours" setting (Settings → Design, default on). Turning it off paints calendar and event colours raw, exactly as the sync source (DAVx5/CalDAV) publishes them, instead of the theme-fitting pastels (#36). Event/calendar colours now flow through shared eventFill()/eventInk() helpers gated by a LocalSoftenColors composition local (widgets read the pref directly). Event titles pick black or white text by the fill's WCAG relative luminance, so a dark colour stays legible whether softened or raw (#21) — previously always near-black. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 13 ++++++ .../jeanlucmakiola/calendula/MainActivity.kt | 2 + .../calendula/data/prefs/SettingsPrefs.kt | 16 +++++++ .../calendula/ui/agenda/AgendaScreen.kt | 6 ++- .../calendula/ui/calendars/CalendarsScreen.kt | 6 ++- .../calendula/ui/common/CalendarColors.kt | 9 ++-- .../calendula/ui/common/ColorSwatchRow.kt | 11 +++-- .../calendula/ui/common/EventColors.kt | 46 +++++++++++++++++++ .../calendula/ui/day/DayScreen.kt | 18 +++++--- .../calendula/ui/detail/EventDetailScreen.kt | 5 +- .../calendula/ui/edit/EventEditScreen.kt | 8 ++-- .../calendula/ui/month/MonthScreen.kt | 15 ++++-- .../calendula/ui/search/SearchScreen.kt | 6 ++- .../calendula/ui/settings/SettingsScreen.kt | 14 +++++- .../calendula/ui/settings/SettingsUiState.kt | 2 + .../ui/settings/SettingsViewModel.kt | 35 ++++++++++++-- .../calendula/ui/week/WeekScreen.kt | 18 +++++--- .../calendula/widget/WidgetData.kt | 4 ++ .../calendula/widget/agenda/AgendaWidget.kt | 13 ++++-- .../calendula/widget/month/MonthWidget.kt | 33 +++++++------ app/src/main/res/values/strings.xml | 2 + 21 files changed, 222 insertions(+), 60 deletions(-) create mode 100644 app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventColors.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 2240ec4..cf7261a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- Show raw calendar colours. Calendula normally softens each calendar and event + colour toward a theme-fitting pastel so harsh sync colours read well on both + light and dark; a new **Soften calendar colours** setting (Settings → Design, + on by default) lets you turn that off and paint the exact colours your calendar + source publishes — matching DAVx5/CalDAV and other calendar apps. Thanks to + @leonp5 for the report ([#36]). +- Readable titles on dark event colours. An event bar's title now shows in white + on a dark colour and near-black on a light one, chosen automatically from the + colour's brightness, so a deep blue or purple event is legible at a glance in + the busy Week and Month views instead of dark-on-dark. This applies whether or + not colours are softened. Thanks to @ptab for the suggestion ([#21]). - A custom snooze duration. The **Snooze duration** setting (Settings → Notifications) gains a **Custom…** option next to the minute presets: pick any amount and switch between minutes and hours, so a snoozed reminder comes back @@ -971,10 +982,12 @@ automatically, with zero telemetry and no internet permission. [#25]: https://codeberg.org/jlmakiola/calendula/issues/25 [#27]: https://codeberg.org/jlmakiola/calendula/issues/27 [#29]: https://codeberg.org/jlmakiola/calendula/issues/29 +[#21]: https://codeberg.org/jlmakiola/calendula/issues/21 [#30]: https://codeberg.org/jlmakiola/calendula/issues/30 [#32]: https://codeberg.org/jlmakiola/calendula/issues/32 [#33]: https://codeberg.org/jlmakiola/calendula/issues/33 [#34]: https://codeberg.org/jlmakiola/calendula/issues/34 +[#36]: https://codeberg.org/jlmakiola/calendula/issues/36 [#37]: https://codeberg.org/jlmakiola/calendula/issues/37 [#39]: https://codeberg.org/jlmakiola/calendula/issues/39 [#35]: https://codeberg.org/jlmakiola/calendula/issues/35 diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/MainActivity.kt b/app/src/main/java/de/jeanlucmakiola/calendula/MainActivity.kt index 99ad92a..1177165 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/MainActivity.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/MainActivity.kt @@ -29,6 +29,7 @@ import de.jeanlucmakiola.calendula.domain.EventForm import de.jeanlucmakiola.calendula.domain.buildInsertEventForm import de.jeanlucmakiola.calendula.ui.RootScreen import de.jeanlucmakiola.calendula.ui.common.LocalShowHourLines +import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat import de.jeanlucmakiola.calendula.ui.WidgetNavRequest import de.jeanlucmakiola.calendula.ui.common.CalendarView @@ -140,6 +141,7 @@ class MainActivity : AppCompatActivity() { CompositionLocalProvider( LocalUse24HourFormat provides use24Hour, LocalShowHourLines provides settings.showHourLines, + LocalSoftenColors provides settings.softenColors, ) { RootScreen( modifier = Modifier.fillMaxSize(), diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt b/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt index 7bed479..cb4e610 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt @@ -114,6 +114,21 @@ class SettingsPrefs @Inject constructor( store.edit { it[DYNAMIC_COLOR_KEY] = enabled } } + /** + * Whether raw provider colours are softened toward theme-fitting pastels + * before display (issue #36). Defaults to ON — the historical look, which + * caps harsh sync colours and pins brightness so entries read on both + * themes. Turning it off paints the calendar/event colour exactly as the + * sync source (DAVx5/CalDAV) publishes it. + */ + val softenCalendarColors: Flow = store.data.map { prefs -> + prefs[SOFTEN_COLORS_KEY] ?: true + } + + suspend fun setSoftenCalendarColors(enabled: Boolean) { + store.edit { it[SOFTEN_COLORS_KEY] = enabled } + } + /** * Custom-font tokens per Material typeface role (issue #19). Stored as opaque * strings — "system", "custom", or a bundled font's token — resolved to a @@ -679,6 +694,7 @@ class SettingsPrefs @Inject constructor( companion object { internal val THEME_MODE_KEY = stringPreferencesKey("theme_mode") internal val DYNAMIC_COLOR_KEY = booleanPreferencesKey("dynamic_color") + internal val SOFTEN_COLORS_KEY = booleanPreferencesKey("soften_calendar_colors") internal val BRAND_FONT_KEY = stringPreferencesKey("brand_font") internal val PLAIN_FONT_KEY = stringPreferencesKey("plain_font") internal val BRAND_FONT_STAMP_KEY = intPreferencesKey("brand_font_stamp") diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaScreen.kt index 5671d5a..51aad58 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaScreen.kt @@ -59,6 +59,8 @@ import de.jeanlucmakiola.calendula.data.prefs.PastEventDisplay import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.domain.hasEnded import de.jeanlucmakiola.calendula.ui.common.AgendaRangePicker +import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors +import de.jeanlucmakiola.calendula.ui.common.eventFill import de.jeanlucmakiola.calendula.ui.common.agendaRangeLabel import de.jeanlucmakiola.floret.identity.animateItemMotion import de.jeanlucmakiola.calendula.ui.common.CalendarDrawer @@ -72,7 +74,6 @@ import de.jeanlucmakiola.floret.components.GroupedRow import de.jeanlucmakiola.floret.components.Position import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill import de.jeanlucmakiola.calendula.ui.common.next -import de.jeanlucmakiola.floret.components.pastelize import de.jeanlucmakiola.floret.components.positionOf import de.jeanlucmakiola.calendula.ui.common.rememberCurrentMinute import de.jeanlucmakiola.calendula.ui.common.currentLocale @@ -454,6 +455,7 @@ private fun AgendaEventRow( onClick: () -> Unit, ) { val dark = isSystemInDarkTheme() + val soften = LocalSoftenColors.current val title = event.title.ifBlank { stringResource(R.string.event_untitled) } GroupedRow( modifier = if (dimmed) modifier.alpha(EventDimAlpha) else modifier, @@ -466,7 +468,7 @@ private fun AgendaEventRow( modifier = Modifier .size(width = 6.dp, height = 36.dp) .clip(RoundedCornerShape(3.dp)) - .background(pastelize(event.color, dark)), + .background(eventFill(event.color, dark, soften)), ) }, onClick = onClick, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt index 06be705..1c99d16 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt @@ -93,6 +93,8 @@ import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs import de.jeanlucmakiola.calendula.domain.CalendarSource import de.jeanlucmakiola.calendula.data.calendar.CalendarColorPalette import de.jeanlucmakiola.calendula.ui.common.CalendarColorChip +import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors +import de.jeanlucmakiola.calendula.ui.common.eventFill import de.jeanlucmakiola.calendula.ui.common.LeadingAvatar import de.jeanlucmakiola.calendula.ui.common.SourceLogo import de.jeanlucmakiola.calendula.ui.common.curatedSourcePackage @@ -108,7 +110,6 @@ import de.jeanlucmakiola.calendula.ui.common.ColorSwatchRow import de.jeanlucmakiola.floret.components.GroupedRow import de.jeanlucmakiola.floret.components.InlineTextField import de.jeanlucmakiola.floret.components.Position -import de.jeanlucmakiola.floret.components.pastelize import java.time.LocalDate /** Sentinel [editorId] meaning "the editor is composing a new calendar". */ @@ -565,6 +566,7 @@ private fun CalendarEditor( var description by rememberSaveable(sessionKey) { mutableStateOf(initialDescription) } var confirmDelete by remember { mutableStateOf(false) } val dark = isSystemInDarkTheme() + val soften = LocalSoftenColors.current Scaffold( modifier = Modifier @@ -624,7 +626,7 @@ private fun CalendarEditor( .padding(horizontal = 16.dp, vertical = 8.dp), verticalArrangement = Arrangement.spacedBy(12.dp), ) { - EditorCard(icon = Icons.Default.CalendarMonth, iconTint = pastelize(color, dark)) { + EditorCard(icon = Icons.Default.CalendarMonth, iconTint = eventFill(color, dark, soften)) { InlineTextField( value = name, onValueChange = { name = it }, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarColors.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarColors.kt index 9a64b40..b6b889f 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarColors.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/CalendarColors.kt @@ -14,16 +14,17 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp -import de.jeanlucmakiola.floret.components.pastelize /** * Leading avatar for a calendar: a neutral chip holding a calendar glyph tinted - * in the calendar's (pastelised) colour. Shared by the calendar manager and the - * visibility filter so they read identically. + * in the calendar's colour — softened to a pastel, or raw when the softener is + * off (issue #36). Shared by the calendar manager and the visibility filter so + * they read identically. */ @Composable fun CalendarColorChip(color: Int, modifier: Modifier = Modifier) { val dark = isSystemInDarkTheme() + val soften = LocalSoftenColors.current Box( modifier = modifier .size(40.dp) @@ -34,7 +35,7 @@ fun CalendarColorChip(color: Int, modifier: Modifier = Modifier) { Icon( Icons.Filled.CalendarMonth, contentDescription = null, - tint = pastelize(color, dark), + tint = eventFill(color, dark, soften), modifier = Modifier.size(22.dp), ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/ColorSwatchRow.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/ColorSwatchRow.kt index d0e9a9a..7449e47 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/ColorSwatchRow.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/ColorSwatchRow.kt @@ -18,15 +18,14 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import de.jeanlucmakiola.floret.components.pastelize /** * A wrapping row of round colour swatches; the one matching [selected] is * ringed and checked. Shared by the calendar editor and the event-colour * picker so both pick a colour the same way. Swatches render through - * [pastelize] — the softened colour the app actually paints, not the raw hue. + * [eventFill] — the colour the app actually paints (softened, or raw when the + * softener is off, issue #36), not necessarily the stored hue. */ @OptIn(ExperimentalLayoutApi::class) @Composable @@ -37,16 +36,18 @@ fun ColorSwatchRow( dark: Boolean, modifier: Modifier = Modifier, ) { + val soften = LocalSoftenColors.current FlowRow(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(12.dp)) { colors.forEach { argb -> val isSelected = argb == selected + val fill = eventFill(argb, dark, soften) Box( contentAlignment = Alignment.Center, modifier = Modifier .padding(vertical = 4.dp) .size(40.dp) .clip(CircleShape) - .background(pastelize(argb, dark)) + .background(fill) .then( if (isSelected) { Modifier.border(2.dp, MaterialTheme.colorScheme.onSurface, CircleShape) @@ -60,7 +61,7 @@ fun ColorSwatchRow( Icon( Icons.Default.Check, contentDescription = null, - tint = Color.Black.copy(alpha = 0.7f), + tint = eventInk(fill, alpha = 0.7f), modifier = Modifier.size(20.dp), ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventColors.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventColors.kt new file mode 100644 index 0000000..938c337 --- /dev/null +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/EventColors.kt @@ -0,0 +1,46 @@ +package de.jeanlucmakiola.calendula.ui.common + +import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance +import de.jeanlucmakiola.floret.components.pastelize + +/** + * Whether calendar/event colours are softened toward theme-fitting pastels + * before display (issue #36). Provided app-wide from the "soften colours" + * setting; the default `true` keeps the historical look. When off, the raw + * provider colour is painted verbatim — matching the sync source (DAVx5/CalDAV) + * and other calendar apps. Widgets live outside this composition and read the + * preference directly, then pass the flag to [eventFill] / [eventInk]. + */ +val LocalSoftenColors = staticCompositionLocalOf { true } + +/** + * Display fill for an event chip/bar or a calendar tint: the [pastelize]d colour + * when [soften] is on, else the raw provider ARGB verbatim (forced opaque, since + * pastelize also returns an opaque colour). + */ +fun eventFill(rawArgb: Int, dark: Boolean, soften: Boolean): Color = + if (soften) pastelize(rawArgb, dark) else Color(rawArgb or 0xFF000000.toInt()) + +/** + * Contrast ink (title text / glyph) for a filled chip painted with [eventFill]: + * white on a dark fill, near-black on a light one (issue #21). Applies to both + * softened and raw fills — a saturated hue (deep blue, purple, red) is + * perceptually dark even after softening pins its HSV value, so black text on it + * reads poorly. The choice is objective, not a tuned threshold: white wins only + * when it out-contrasts black against the fill, which (by the WCAG contrast + * ratio) is at relative luminance ≈ 0.18 — so mid and light colours keep + * near-black text. [alpha] carries the caller's soft emphasis. + */ +fun eventInk(fill: Color, alpha: Float = 0.8f): Color { + val useWhite = fill.luminance() < INK_LUMINANCE_CROSSOVER + return (if (useWhite) Color.White else Color.Black).copy(alpha = alpha) +} + +/** + * Relative-luminance crossover where white text starts to out-contrast black. + * Solving `contrast(white, L) = contrast(black, L)` on the WCAG ratio gives + * `L = sqrt(1.05 * 0.05) - 0.05 ≈ 0.179`. + */ +private const val INK_LUMINANCE_CROSSOVER = 0.179f diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt index f802bb1..c1185d0 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt @@ -79,7 +79,9 @@ import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition import de.jeanlucmakiola.calendula.ui.common.rememberCalendarFadeSpec import de.jeanlucmakiola.floret.identity.rememberReduceMotion import de.jeanlucmakiola.calendula.ui.common.next -import de.jeanlucmakiola.floret.components.pastelize +import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors +import de.jeanlucmakiola.calendula.ui.common.eventFill +import de.jeanlucmakiola.calendula.ui.common.eventInk import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec import de.jeanlucmakiola.calendula.ui.common.currentLocale import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat @@ -452,9 +454,11 @@ private fun AllDayBar( modifier: Modifier = Modifier, ) { val title = event.title.ifBlank { stringResource(R.string.event_untitled) } + val soften = LocalSoftenColors.current + val fill = eventFill(event.color, dark, soften) Box( modifier = modifier - .background(pastelize(event.color, dark), RoundedCornerShape(4.dp)) + .background(fill, RoundedCornerShape(4.dp)) .clickable(onClick = onClick) .padding(horizontal = 6.dp, vertical = 2.dp) .semantics { contentDescription = title }, @@ -465,7 +469,7 @@ private fun AllDayBar( style = MaterialTheme.typography.labelSmall, maxLines = 1, overflow = TextOverflow.Ellipsis, - color = Color.Black.copy(alpha = 0.8f), + color = eventInk(fill), ) } } @@ -616,9 +620,11 @@ private fun EventBlock( val timeLabel = "${minToHm(block.startMin, use24Hour, locale)}–" + minToHm(block.endMin, use24Hour, locale) val showTime = block.endMin - block.startMin >= 45 + val soften = LocalSoftenColors.current + val fill = eventFill(block.event.color, dark, soften) Box( modifier = modifier - .background(pastelize(block.event.color, dark), RoundedCornerShape(4.dp)) + .background(fill, RoundedCornerShape(4.dp)) .clickable(onClick = onClick) .padding(horizontal = 4.dp, vertical = 2.dp) .semantics { contentDescription = "$title, $timeLabel" }, @@ -629,7 +635,7 @@ private fun EventBlock( style = MaterialTheme.typography.labelMedium, maxLines = if (showTime) 1 else 2, overflow = TextOverflow.Ellipsis, - color = Color.Black.copy(alpha = 0.85f), + color = eventInk(fill, alpha = 0.85f), ) if (showTime) { Text( @@ -637,7 +643,7 @@ private fun EventBlock( style = MaterialTheme.typography.labelSmall, maxLines = 1, overflow = TextOverflow.Ellipsis, - color = Color.Black.copy(alpha = 0.6f), + color = eventInk(fill, alpha = 0.6f), ) } } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt index 8c694a2..d4073a6 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt @@ -95,11 +95,12 @@ import de.jeanlucmakiola.calendula.domain.RecurringWriteScope import de.jeanlucmakiola.calendula.domain.Reminder import de.jeanlucmakiola.floret.identity.predictiveBack import de.jeanlucmakiola.calendula.ui.common.CalendarFailure +import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors +import de.jeanlucmakiola.calendula.ui.common.eventFill import de.jeanlucmakiola.floret.components.OptionCard import de.jeanlucmakiola.calendula.ui.common.currentLocale import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat import de.jeanlucmakiola.calendula.ui.common.timeOfDayFormatter -import de.jeanlucmakiola.floret.components.pastelize import de.jeanlucmakiola.calendula.ui.common.recurrenceText import de.jeanlucmakiola.calendula.ui.common.reminderLeadTimeLabel import kotlinx.coroutines.launch @@ -381,7 +382,7 @@ private fun EventDetailContent(state: EventDetailUiState.Success, modifier: Modi val instance = detail.instance val dark = isSystemInDarkTheme() val locale = currentDetailLocale() - val accent = pastelize(instance.color, dark) + val accent = eventFill(instance.color, dark, LocalSoftenColors.current) Column( modifier = modifier diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt index 838930f..301cea9 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt @@ -126,6 +126,8 @@ import de.jeanlucmakiola.calendula.data.calendar.CalendarColorPalette import de.jeanlucmakiola.calendula.ui.common.CalendarPickerGroups import de.jeanlucmakiola.calendula.ui.common.CalendarDatePickerDialog import de.jeanlucmakiola.calendula.ui.common.ColorSwatchRow +import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors +import de.jeanlucmakiola.calendula.ui.common.eventFill import de.jeanlucmakiola.floret.components.DialogAmountField import de.jeanlucmakiola.floret.components.DialogUnitDropdown import de.jeanlucmakiola.calendula.ui.common.eventFormFieldIcon @@ -146,7 +148,6 @@ import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat import de.jeanlucmakiola.calendula.ui.common.timeOfDayFormatter import de.jeanlucmakiola.calendula.ui.common.reminderLeadTimeLabel import de.jeanlucmakiola.calendula.ui.common.reminderUnitLabel -import de.jeanlucmakiola.floret.components.pastelize import de.jeanlucmakiola.calendula.ui.common.recurrenceText import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate @@ -541,7 +542,8 @@ private fun EventEditContent( val selectedCalendar = state.calendars.firstOrNull { it.id == form.calendarId } // The accent ties the form to the detail screen's design language: the // bar under the title takes the target calendar's colour. - val accent = selectedCalendar?.let { pastelize(it.color, dark) } + val soften = LocalSoftenColors.current + val accent = selectedCalendar?.let { eventFill(it.color, dark, soften) } ?: MaterialTheme.colorScheme.primary val gap = 12.dp @@ -893,7 +895,7 @@ private fun EventEditContent( icon = Icons.Default.Palette, iconContentDescription = stringResource(R.string.event_edit_color), iconTint = if (colorSupported && swatch != null) { - pastelize(swatch, dark) + eventFill(swatch, dark, soften) } else { MaterialTheme.colorScheme.onSurfaceVariant }, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthScreen.kt index 023a90c..100c71f 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthScreen.kt @@ -71,6 +71,9 @@ import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha import de.jeanlucmakiola.calendula.ui.common.LocalDimCutoff +import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors +import de.jeanlucmakiola.calendula.ui.common.eventFill +import de.jeanlucmakiola.calendula.ui.common.eventInk import de.jeanlucmakiola.calendula.ui.common.rememberCurrentMinute import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition @@ -79,7 +82,6 @@ import de.jeanlucmakiola.floret.identity.rememberReduceMotion import de.jeanlucmakiola.calendula.ui.common.currentLocale import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec import de.jeanlucmakiola.calendula.ui.common.next -import de.jeanlucmakiola.floret.components.pastelize import de.jeanlucmakiola.floret.time.isoWeekNumber import kotlinx.coroutines.launch import kotlinx.datetime.DayOfWeek @@ -630,7 +632,7 @@ private fun DayNumberCell( } } -/** A filled event pill/bar — pastelized fill, title clipped to one line. */ +/** A filled event pill/bar — softened (or raw) fill, title clipped to one line. */ @Composable private fun MonthBar( event: de.jeanlucmakiola.calendula.domain.EventInstance, @@ -642,6 +644,8 @@ private fun MonthBar( val title = event.title.ifBlank { stringResource(R.string.event_untitled) } val dimCutoff = LocalDimCutoff.current val dimmed = dimCutoff != null && event.hasEnded(dimCutoff) + val soften = LocalSoftenColors.current + val fill = eventFill(event.color, dark, soften) val shape = RoundedCornerShape( topStart = if (continuesLeft) 0.dp else 4.dp, bottomStart = if (continuesLeft) 0.dp else 4.dp, @@ -650,7 +654,7 @@ private fun MonthBar( ) Box( modifier = (if (dimmed) modifier.alpha(EventDimAlpha) else modifier) - .background(pastelize(event.color, dark), shape) + .background(fill, shape) .padding(horizontal = 4.dp) .semantics { contentDescription = title }, contentAlignment = Alignment.CenterStart, @@ -660,7 +664,7 @@ private fun MonthBar( style = MaterialTheme.typography.labelSmall, maxLines = 1, overflow = TextOverflow.Ellipsis, - color = Color.Black.copy(alpha = 0.8f), + color = eventInk(fill), ) } } @@ -673,6 +677,7 @@ private fun OverflowDots( dark: Boolean, modifier: Modifier = Modifier, ) { + val soften = LocalSoftenColors.current Row( modifier = modifier.height(EVENT_ROW_HEIGHT), horizontalArrangement = Arrangement.spacedBy(2.dp), @@ -682,7 +687,7 @@ private fun OverflowDots( Box( modifier = Modifier .size(6.dp) - .background(pastelize(argb, dark), CircleShape), + .background(eventFill(argb, dark, soften), CircleShape), ) } if (extra > 0) { diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/search/SearchScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/search/SearchScreen.kt index 738fd8d..9eec23e 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/search/SearchScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/search/SearchScreen.kt @@ -55,9 +55,10 @@ import de.jeanlucmakiola.floret.components.GroupedRow import de.jeanlucmakiola.floret.components.InlineTextField import de.jeanlucmakiola.floret.components.Position import de.jeanlucmakiola.calendula.ui.common.currentLocale +import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors +import de.jeanlucmakiola.calendula.ui.common.eventFill import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat import de.jeanlucmakiola.calendula.ui.common.timeOfDayFormatter -import de.jeanlucmakiola.floret.components.pastelize import de.jeanlucmakiola.floret.components.positionOf import java.time.Instant as JavaInstant import java.time.ZoneId @@ -192,6 +193,7 @@ private fun SearchResultRow( onClick: () -> Unit, ) { val dark = isSystemInDarkTheme() + val soften = LocalSoftenColors.current GroupedRow( modifier = modifier, title = event.title, @@ -203,7 +205,7 @@ private fun SearchResultRow( modifier = Modifier .size(width = 6.dp, height = 36.dp) .clip(RoundedCornerShape(3.dp)) - .background(pastelize(event.color, dark)), + .background(eventFill(event.color, dark, soften)), ) }, onClick = onClick, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt index c3a5f10..19f798b 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt @@ -517,7 +517,7 @@ private fun AppearanceScreen( } else { stringResource(R.string.settings_dynamic_color_unavailable) }, - position = Position.Bottom, + position = Position.Middle, trailing = { Switch( checked = state.dynamicColor, @@ -531,6 +531,18 @@ private fun AppearanceScreen( null }, ) + GroupedRow( + title = stringResource(R.string.settings_soften_colors), + summary = stringResource(R.string.settings_soften_colors_summary), + position = Position.Bottom, + trailing = { + Switch( + checked = state.softenColors, + onCheckedChange = viewModel::setSoftenColors, + ) + }, + onClick = { viewModel.setSoftenColors(!state.softenColors) }, + ) Spacer(Modifier.height(16.dp)) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt index b2f4bf0..1bbc6c2 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt @@ -24,6 +24,8 @@ data class SettingsUiState( val themeMode: ThemeMode = ThemeMode.SYSTEM, val dynamicColor: Boolean = true, val dynamicColorAvailable: Boolean = true, + /** Whether raw provider colours are softened to theme-fitting pastels (#36). */ + val softenColors: Boolean = true, val weekStart: WeekStartPref = WeekStartPref.Auto, /** Clock convention for time labels (v2.11). AUTO follows the system setting. */ val timeFormat: TimeFormatPref = TimeFormatPref.AUTO, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt index 17cbfce..22d1a6b 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt @@ -123,14 +123,17 @@ class SettingsViewModel @Inject constructor( prefs.showHourLines, prefs.showWeekNumbers, prefs.agendaShowToday, - ::Triple, - ), + prefs.softenCalendarColors, + ) { hourLines, weekNumbers, showToday, soften -> + DisplayToggles(hourLines, weekNumbers, showToday, soften) + }, ) { view, screenRange, widgetRange, timeFormat, toggles -> ViewSettings( view, screenRange, widgetRange, timeFormat, - showHourLines = toggles.first, - showWeekNumbers = toggles.second, - agendaShowToday = toggles.third, + showHourLines = toggles.showHourLines, + showWeekNumbers = toggles.showWeekNumbers, + agendaShowToday = toggles.agendaShowToday, + softenColors = toggles.softenColors, ) }, combine( @@ -155,6 +158,7 @@ class SettingsViewModel @Inject constructor( showHourLines = views.showHourLines, showWeekNumbers = views.showWeekNumbers, agendaShowToday = views.agendaShowToday, + softenColors = views.softenColors, agendaShowRangeBar = misc.showRangeBar, autofocusEventTitle = misc.autofocusEventTitle, pastEventDisplay = misc.pastEventDisplay, @@ -229,6 +233,14 @@ class SettingsViewModel @Inject constructor( val showHourLines: Boolean, val showWeekNumbers: Boolean, val agendaShowToday: Boolean, + val softenColors: Boolean, + ) + + private data class DisplayToggles( + val showHourLines: Boolean, + val showWeekNumbers: Boolean, + val agendaShowToday: Boolean, + val softenColors: Boolean, ) private data class MiscSettings( @@ -341,6 +353,19 @@ class SettingsViewModel @Inject constructor( viewModelScope.launch { prefs.setDynamicColor(enabled) } } + fun setSoftenColors(enabled: Boolean) { + viewModelScope.launch { + prefs.setSoftenCalendarColors(enabled) + // Both widgets paint event colours through the same softener, so a + // change has to redraw them too (they read the flag in their data + // preamble, like is24Hour). + widgetRefreshMutex.withLock { + AgendaWidget().updateAll(appContext) + MonthWidget().updateAll(appContext) + } + } + } + fun setFont(role: FontRole, token: String) { viewModelScope.launch { when (role) { diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt index 57bd153..251e3b4 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt @@ -84,6 +84,9 @@ import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.ui.common.IMPLEMENTED_VIEWS import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha import de.jeanlucmakiola.calendula.ui.common.LocalDimCutoff +import de.jeanlucmakiola.calendula.ui.common.LocalSoftenColors +import de.jeanlucmakiola.calendula.ui.common.eventFill +import de.jeanlucmakiola.calendula.ui.common.eventInk import de.jeanlucmakiola.calendula.ui.common.NowLine import de.jeanlucmakiola.calendula.ui.common.rememberCurrentMinute import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill @@ -98,7 +101,6 @@ import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay import de.jeanlucmakiola.calendula.ui.common.hourSeparatorLines import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec import de.jeanlucmakiola.calendula.ui.common.next -import de.jeanlucmakiola.floret.components.pastelize import de.jeanlucmakiola.floret.time.isoWeekNumber import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -589,9 +591,11 @@ private fun AllDayBar( val title = event.title.ifBlank { stringResource(R.string.event_untitled) } val dimCutoff = LocalDimCutoff.current val dimmed = dimCutoff != null && event.hasEnded(dimCutoff) + val soften = LocalSoftenColors.current + val fill = eventFill(event.color, dark, soften) Box( modifier = (if (dimmed) modifier.alpha(EventDimAlpha) else modifier) - .background(pastelize(event.color, dark), RoundedCornerShape(4.dp)) + .background(fill, RoundedCornerShape(4.dp)) .clickable(onClick = onClick) .padding(horizontal = 6.dp, vertical = 2.dp) .semantics { contentDescription = title }, @@ -602,7 +606,7 @@ private fun AllDayBar( style = MaterialTheme.typography.labelSmall, maxLines = 1, overflow = TextOverflow.Ellipsis, - color = Color.Black.copy(alpha = 0.8f), + color = eventInk(fill), ) } } @@ -781,9 +785,11 @@ private fun EventBlock( val titleMaxLines = (contentHeight / titleLineHeight).toInt().coerceAtLeast(1) val dimCutoff = LocalDimCutoff.current val dimmed = dimCutoff != null && block.event.hasEnded(dimCutoff) + val soften = LocalSoftenColors.current + val fill = eventFill(block.event.color, dark, soften) Box( modifier = (if (dimmed) modifier.alpha(EventDimAlpha) else modifier) - .background(pastelize(block.event.color, dark), RoundedCornerShape(4.dp)) + .background(fill, RoundedCornerShape(4.dp)) .clickable(onClick = onClick) .padding(horizontal = 4.dp, vertical = 2.dp) .semantics { contentDescription = "$title, $timeLabel" }, @@ -794,7 +800,7 @@ private fun EventBlock( style = MaterialTheme.typography.labelMedium, maxLines = titleMaxLines, overflow = TextOverflow.Ellipsis, - color = Color.Black.copy(alpha = 0.85f), + color = eventInk(fill, alpha = 0.85f), ) if (showTime) { Text( @@ -802,7 +808,7 @@ private fun EventBlock( style = MaterialTheme.typography.labelSmall, maxLines = 1, overflow = TextOverflow.Ellipsis, - color = Color.Black.copy(alpha = 0.6f), + color = eventInk(fill, alpha = 0.6f), ) } } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetData.kt b/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetData.kt index a1b06be..0d4f8c3 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetData.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetData.kt @@ -60,6 +60,8 @@ sealed interface AgendaWidgetData { val days: List, /** Resolved clock convention for event time labels (the time-format pref). */ val is24Hour: Boolean, + /** Whether event colours are softened to pastels, or shown raw (#36). */ + val soften: Boolean, /** First day of the week, for the calendar-aligned "this week" range. */ val weekStart: DayOfWeek, /** Saved range pref — the fallback when an instance has no Glance state yet. */ @@ -132,10 +134,12 @@ internal suspend fun Context.loadAgendaWidgetData(): AgendaWidgetData { val instances = ep.calendarRepository().instances(window).first() val is24Hour = prefs.timeFormat.first() .is24Hour(android.text.format.DateFormat.is24HourFormat(this)) + val soften = prefs.softenCalendarColors.first() return AgendaWidgetData.Ready( today = anchor, days = groupAgendaDays(anchor, instances, zone), is24Hour = is24Hour, + soften = soften, weekStart = weekStart, savedRange = savedRange, savedPastDisplay = savedPastDisplay, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/widget/agenda/AgendaWidget.kt b/app/src/main/java/de/jeanlucmakiola/calendula/widget/agenda/AgendaWidget.kt index 9ea5311..039b4dc 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/widget/agenda/AgendaWidget.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/widget/agenda/AgendaWidget.kt @@ -56,7 +56,7 @@ import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha import de.jeanlucmakiola.calendula.ui.common.formatTimeOfDay import de.jeanlucmakiola.calendula.ui.common.localizedDateFormatter -import de.jeanlucmakiola.floret.components.pastelize +import de.jeanlucmakiola.calendula.ui.common.eventFill import de.jeanlucmakiola.calendula.widget.AgendaWidgetData import de.jeanlucmakiola.calendula.widget.CalendulaGlanceTheme import de.jeanlucmakiola.calendula.widget.loadAgendaWidgetData @@ -197,6 +197,7 @@ private fun AgendaWidgetBody(data: AgendaWidgetData, dark: Boolean) { is AgendaRow.Event -> EventRow( event = row.event, dark = dark, + soften = data.soften, is24Hour = data.is24Hour, dimmed = pastDisplay == PastEventDisplay.DIM && row.event.hasEnded(data.now), @@ -309,12 +310,18 @@ private fun PlaceholderRow(date: LocalDate) { } @Composable -private fun EventRow(event: EventInstance, dark: Boolean, is24Hour: Boolean, dimmed: Boolean) { +private fun EventRow( + event: EventInstance, + dark: Boolean, + soften: Boolean, + is24Hour: Boolean, + dimmed: Boolean, +) { val context = androidx.glance.LocalContext.current val title = event.title.ifBlank { context.getString(R.string.event_untitled) } // Glance has no generic alpha modifier, so dim by fading the colour stripe and // dropping both text lines to the lower-emphasis on-surface-variant tone. - val stripeColor = pastelize(event.color, dark).let { + val stripeColor = eventFill(event.color, dark, soften).let { if (dimmed) it.copy(alpha = EventDimAlpha) else it } val titleColor = if (dimmed) GlanceTheme.colors.onSurfaceVariant else GlanceTheme.colors.onSurface diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthWidget.kt b/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthWidget.kt index f9fe54a..68b788a 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthWidget.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/widget/month/MonthWidget.kt @@ -3,7 +3,6 @@ package de.jeanlucmakiola.calendula.widget.month import android.content.Context import android.content.res.Configuration import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.datastore.preferences.core.intPreferencesKey @@ -50,14 +49,17 @@ import de.jeanlucmakiola.calendula.MainActivity import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.domain.EventInstance import de.jeanlucmakiola.calendula.ui.common.CalendarView -import de.jeanlucmakiola.floret.components.pastelize import de.jeanlucmakiola.calendula.ui.month.MonthWeek import de.jeanlucmakiola.calendula.ui.month.layoutMonthWeeks +import de.jeanlucmakiola.calendula.ui.common.eventFill +import de.jeanlucmakiola.calendula.ui.common.eventInk import de.jeanlucmakiola.calendula.widget.CalendulaGlanceTheme import de.jeanlucmakiola.calendula.widget.MonthWidgetSource import de.jeanlucmakiola.calendula.widget.loadMonthWidgetSource import de.jeanlucmakiola.calendula.widget.systemZone import de.jeanlucmakiola.calendula.widget.today +import de.jeanlucmakiola.calendula.widget.widgetEntryPoint +import kotlinx.coroutines.flow.first import androidx.compose.ui.unit.Dp import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate @@ -76,9 +78,6 @@ private val LANE_HEIGHT = 14.dp private val DAY_NUMBER_HEIGHT = 18.dp private val GRID_HPADDING = 8.dp -/** Dark ink that reads on the pastelized event fills, like the in-app MonthBar. */ -private val EventInk = ColorProvider(Color(0xDE000000)) - private fun currentMonthIndex(zone: TimeZone): Int { val t = today(zone) return t.year * 12 + t.month.ordinal @@ -107,9 +106,12 @@ class MonthWidget : GlanceAppWidget() { val source = context.loadMonthWidgetSource() val dark = (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES + // Read fresh (not through the cached source) so toggling the softener + // redraws with the new choice; it's one cheap DataStore read. + val soften = context.widgetEntryPoint().settingsPrefs().softenCalendarColors.first() provideContent { CalendulaGlanceTheme { - MonthWidgetBody(source = source, dark = dark) + MonthWidgetBody(source = source, dark = dark, soften = soften) } } } @@ -140,7 +142,7 @@ class ResetMonthAction : ActionCallback { } @Composable -private fun MonthWidgetBody(source: MonthWidgetSource, dark: Boolean) { +private fun MonthWidgetBody(source: MonthWidgetSource, dark: Boolean, soften: Boolean) { Column( modifier = GlanceModifier .fillMaxSize() @@ -169,6 +171,7 @@ private fun MonthWidgetBody(source: MonthWidgetSource, dark: Boolean) { currentMonth = ym.month, today = source.today, dark = dark, + soften = soften, colW = colW, modifier = GlanceModifier.defaultWeight(), ) @@ -278,6 +281,7 @@ private fun WeekRow( currentMonth: Month, today: LocalDate, dark: Boolean, + soften: Boolean, colW: Dp, modifier: GlanceModifier, ) { @@ -297,7 +301,7 @@ private fun WeekRow( // One lane row per event row. A multi-day span is a single Box spanning // its columns (colW * n) so it's connected with no seam and rounded ends. repeat(MAX_LANES) { lane -> - LaneRow(week = week, lane = lane, dark = dark, colW = colW) + LaneRow(week = week, lane = lane, dark = dark, soften = soften, colW = colW) Spacer(GlanceModifier.height(1.dp)) } OverflowRow(week = week, colW = colW) @@ -346,7 +350,7 @@ private fun DayNumber(date: LocalDate, isToday: Boolean, inMonth: Boolean, colW: } @Composable -private fun LaneRow(week: MonthWeek, lane: Int, dark: Boolean, colW: Dp) { +private fun LaneRow(week: MonthWeek, lane: Int, dark: Boolean, soften: Boolean, colW: Dp) { val context = LocalContext.current Row(modifier = GlanceModifier.fillMaxWidth()) { var col = 0 @@ -354,12 +358,12 @@ private fun LaneRow(week: MonthWeek, lane: Int, dark: Boolean, colW: Dp) { val span = week.spans.firstOrNull { it.lane == lane && col in it.startCol..it.endCol } if (span != null) { val cols = span.endCol - col + 1 - SpanBar(event = span.event, dark = dark, width = colW * cols) + SpanBar(event = span.event, dark = dark, soften = soften, width = colW * cols) col = span.endCol + 1 } else { val timed = timedEventAt(week, lane, col, week.days[col]) if (timed != null) { - SpanBar(event = timed, dark = dark, width = colW) + SpanBar(event = timed, dark = dark, soften = soften, width = colW) } else { // Empty lane cell: a tap opens that day, so blank space in a // day column is a day-open target just like the number is. @@ -378,8 +382,9 @@ private fun LaneRow(week: MonthWeek, lane: Int, dark: Boolean, colW: Dp) { /** A single connected, rounded event bar [width] wide with its clipped title. */ @Composable -private fun SpanBar(event: EventInstance, dark: Boolean, width: Dp) { +private fun SpanBar(event: EventInstance, dark: Boolean, soften: Boolean, width: Dp) { val context = LocalContext.current + val fill = eventFill(event.color, dark, soften) Box( modifier = GlanceModifier .width(width) @@ -402,13 +407,13 @@ private fun SpanBar(event: EventInstance, dark: Boolean, width: Dp) { modifier = GlanceModifier .fillMaxSize() .cornerRadius(4.dp) - .background(pastelize(event.color, dark)), + .background(fill), contentAlignment = Alignment.CenterStart, ) { Text( text = event.title.ifBlank { context.getString(R.string.event_untitled) }, maxLines = 1, - style = TextStyle(color = EventInk, fontSize = 9.sp), + style = TextStyle(color = ColorProvider(eventInk(fill)), fontSize = 9.sp), modifier = GlanceModifier.padding(horizontal = 3.dp), ) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 52a99b7..06f995d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -293,6 +293,8 @@ Default view Dynamic colour Requires Android 12 or newer + Soften calendar colours + Tone calendar and event colours down to fit the theme. Turn off to show the raw colours from the calendar source. Headings font Body font System default