Classic agenda: invert text colour instead of backings at low opacity (#489)
This commit is contained in:
1 parent
88d5614158
commit
06f658917f
6 files changed
+103
-28
No files matched your search
@@ -41,6 +41,8 @@ data class AgendaWidgetOptions(
|
||||
val showToday: Boolean,
|
||||
val pastDisplay: PastEventDisplay,
|
||||
val style: AgendaWidgetStyle = AgendaWidgetStyle.CARDS,
|
||||
/** Classic style only: draw the text in the opposite theme's colours, for a faded fill over the wallpaper. */
|
||||
val invertText: Boolean = false,
|
||||
)
|
||||
|
||||
internal fun WidgetTheme.resolve(app: ThemeMode): WidgetThemeMode = when (this) {
|
||||
@@ -57,6 +59,7 @@ private val BackgroundKey = stringPreferencesKey("cfg_background")
|
||||
private val ThemeKey = stringPreferencesKey("cfg_theme")
|
||||
private val OpacityKey = floatPreferencesKey("cfg_opacity")
|
||||
private val AgendaStyleKey = stringPreferencesKey("cfg_agenda_style")
|
||||
private val InvertTextKey = booleanPreferencesKey("cfg_invert_text")
|
||||
private val AgendaRangeKey = stringPreferencesKey("cfg_agenda_range")
|
||||
private val TextSizeKey = stringPreferencesKey("cfg_text_size")
|
||||
private val ShowTodayKey = booleanPreferencesKey("cfg_show_today")
|
||||
@@ -81,6 +84,7 @@ internal fun Preferences.agendaWidgetOptions(defaults: AgendaWidgetOptions) = Ag
|
||||
showToday = this[ShowTodayKey] ?: defaults.showToday,
|
||||
pastDisplay = this[PastDisplayKey].toEnum(defaults.pastDisplay),
|
||||
style = this[AgendaStyleKey].toEnum(defaults.style),
|
||||
invertText = this[InvertTextKey] ?: defaults.invertText,
|
||||
)
|
||||
|
||||
/** Whether this widget has agenda options of its own, rather than following the app's. */
|
||||
@@ -98,6 +102,7 @@ internal fun MutablePreferences.putAgendaOptions(options: AgendaWidgetOptions) {
|
||||
this[ShowTodayKey] = options.showToday
|
||||
this[PastDisplayKey] = options.pastDisplay.name
|
||||
this[AgendaStyleKey] = options.style.name
|
||||
this[InvertTextKey] = options.invertText
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -127,6 +127,7 @@ sealed interface AgendaWidgetData {
|
||||
val showToday: Boolean,
|
||||
val textSize: WidgetSize,
|
||||
val style: AgendaWidgetStyle = AgendaWidgetStyle.CARDS,
|
||||
val invertText: Boolean = false,
|
||||
/** Snapshot instant the data was read at, for "has this event ended?" tests. */
|
||||
val now: Instant,
|
||||
) : AgendaWidgetData
|
||||
@@ -186,6 +187,7 @@ internal suspend fun Context.loadAgendaWidgetData(
|
||||
showToday = options.showToday,
|
||||
textSize = options.textSize,
|
||||
style = options.style,
|
||||
invertText = options.invertText,
|
||||
now = Clock.System.now(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -60,7 +60,10 @@ import de.jeanlucmakiola.calendula.ui.common.eventFill
|
||||
import de.jeanlucmakiola.calendula.ui.common.eventAccent
|
||||
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
||||
import de.jeanlucmakiola.calendula.widget.AgendaWidgetStyle
|
||||
import de.jeanlucmakiola.floret.glance.LocalWidgetBackground
|
||||
import de.jeanlucmakiola.floret.glance.LocalWidgetSeeThrough
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import de.jeanlucmakiola.floret.glance.WidgetBackground
|
||||
import de.jeanlucmakiola.floret.glance.withAlpha
|
||||
import de.jeanlucmakiola.calendula.widget.WidgetDimFillAlpha
|
||||
import de.jeanlucmakiola.calendula.widget.widgetEventInk
|
||||
@@ -177,16 +180,21 @@ private fun AgendaWidgetBody(data: AgendaWidgetData, preview: Boolean) {
|
||||
val size = (data as? AgendaWidgetData.Ready)?.textSize ?: WidgetSize.SMALL
|
||||
val fontScale = androidx.glance.LocalContext.current.resources.configuration.fontScale
|
||||
val metrics = metricsFor(size).scaledForFont(fontScale)
|
||||
Column(
|
||||
modifier = GlanceModifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||
) {
|
||||
AgendaHeader()
|
||||
Spacer(GlanceModifier.height(6.dp))
|
||||
when (data) {
|
||||
AgendaWidgetData.NeedsPermission -> WidgetMessage(R.string.widget_needs_permission, metrics)
|
||||
is AgendaWidgetData.Ready -> AgendaRows(data = data, metrics = metrics, preview = preview)
|
||||
// The classic style adds no backings for a faded fill, only on a transparent one (#489).
|
||||
val classic = (data as? AgendaWidgetData.Ready)?.style == AgendaWidgetStyle.CLASSIC
|
||||
val seeThrough = if (classic) LocalWidgetBackground.current == WidgetBackground.TRANSPARENT else LocalWidgetSeeThrough.current
|
||||
CompositionLocalProvider(LocalWidgetSeeThrough provides seeThrough) {
|
||||
Column(
|
||||
modifier = GlanceModifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 8.dp, vertical = 6.dp),
|
||||
) {
|
||||
AgendaHeader()
|
||||
Spacer(GlanceModifier.height(6.dp))
|
||||
when (data) {
|
||||
AgendaWidgetData.NeedsPermission -> WidgetMessage(R.string.widget_needs_permission, metrics)
|
||||
is AgendaWidgetData.Ready -> AgendaRows(data = data, metrics = metrics, preview = preview)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,13 +322,15 @@ internal fun classicItems(rows: List<AgendaRow>, limit: Int = MAX_AGENDA_ROWS):
|
||||
|
||||
/**
|
||||
* v2.23's look (#489): a label line per day over plain rows with a colour
|
||||
* stripe. On a see-through root each day sits on its own backing card, so the
|
||||
* text stays readable over the wallpaper.
|
||||
* stripe. On a transparent root each day sits on its own backing card, so the
|
||||
* text stays readable over the wallpaper; a faded fill is left to the opacity
|
||||
* and "Invert text colour" options.
|
||||
*/
|
||||
@Composable
|
||||
private fun ClassicRows(rows: List<AgendaRow>, data: AgendaWidgetData.Ready, metrics: AgendaMetrics, preview: Boolean) {
|
||||
val items = classicItems(rows, if (preview) PREVIEW_ROWS else MAX_AGENDA_ROWS)
|
||||
val backed = LocalWidgetSeeThrough.current
|
||||
val backed = LocalWidgetBackground.current == WidgetBackground.TRANSPARENT
|
||||
val ink = classicInk(inverted = data.invertText && !backed)
|
||||
val positions = if (backed) dayPositions(items.map { it.date }) else emptyList()
|
||||
val cardColor = widgetCardColor()
|
||||
val item: @Composable (Int) -> Unit = { index ->
|
||||
@@ -336,11 +346,12 @@ private fun ClassicRows(rows: List<AgendaRow>, data: AgendaWidgetData.Ready, met
|
||||
index == 0 -> 0.dp
|
||||
else -> metrics.dayHeaderTopPad
|
||||
},
|
||||
ink = ink,
|
||||
metrics = metrics,
|
||||
)
|
||||
is ClassicItem.Entry -> when (val row = entry.row) {
|
||||
is AgendaRow.Placeholder -> ClassicPlaceholderRow(row.date, backed, metrics)
|
||||
is AgendaRow.Event -> ClassicEventRow(row, data, backed, metrics)
|
||||
is AgendaRow.Placeholder -> ClassicPlaceholderRow(row.date, backed, ink, metrics)
|
||||
is AgendaRow.Event -> ClassicEventRow(row, data, backed, ink, metrics)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,15 +371,27 @@ private fun ClassicRows(rows: List<AgendaRow>, data: AgendaWidgetData.Ready, met
|
||||
RowList(items.size, preview, item)
|
||||
}
|
||||
|
||||
/** The classic style's text colours: the theme's, or its inverse for the "Invert text colour" option. */
|
||||
private data class ClassicInk(val title: ColorProvider, val secondary: ColorProvider, val today: ColorProvider)
|
||||
|
||||
@Composable
|
||||
private fun classicInk(inverted: Boolean): ClassicInk = with(GlanceTheme.colors) {
|
||||
if (inverted) {
|
||||
ClassicInk(inverseOnSurface, inverseOnSurface.withAlpha(INVERTED_SECONDARY_INK), inversePrimary)
|
||||
} else {
|
||||
ClassicInk(onSurface, onSurfaceVariant, primary)
|
||||
}
|
||||
}
|
||||
|
||||
/** A classic day label, "Today · Wed, 17 Jun"; opens that day in the app. */
|
||||
@Composable
|
||||
private fun DayHeaderRow(date: LocalDate, today: LocalDate, startPad: Dp, topPad: Dp, metrics: AgendaMetrics) {
|
||||
private fun DayHeaderRow(date: LocalDate, today: LocalDate, startPad: Dp, topPad: Dp, ink: ClassicInk, metrics: AgendaMetrics) {
|
||||
val context = androidx.glance.LocalContext.current
|
||||
Text(
|
||||
text = agendaDayLabel(context, date, today),
|
||||
maxLines = 1,
|
||||
style = TextStyle(
|
||||
color = if (date == today) GlanceTheme.colors.primary else GlanceTheme.colors.onSurfaceVariant,
|
||||
color = if (date == today) ink.today else ink.secondary,
|
||||
fontSize = metrics.dayHeader,
|
||||
fontWeight = FontWeight.Medium,
|
||||
),
|
||||
@@ -381,12 +404,12 @@ private fun DayHeaderRow(date: LocalDate, today: LocalDate, startPad: Dp, topPad
|
||||
|
||||
/** "No more events today" as a muted line under an anchored, event-less today (#35), indented to the titles. */
|
||||
@Composable
|
||||
private fun ClassicPlaceholderRow(date: LocalDate, backed: Boolean, metrics: AgendaMetrics) {
|
||||
private fun ClassicPlaceholderRow(date: LocalDate, backed: Boolean, ink: ClassicInk, metrics: AgendaMetrics) {
|
||||
val context = androidx.glance.LocalContext.current
|
||||
Text(
|
||||
text = context.getString(R.string.agenda_no_more_today),
|
||||
maxLines = 1,
|
||||
style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = metrics.placeholder),
|
||||
style = TextStyle(color = ink.secondary, fontSize = metrics.placeholder),
|
||||
modifier = GlanceModifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = if (backed) CLASSIC_CARD_H_PAD + STRIPE_INSET else TEXT_INDENT, end = 8.dp, top = 2.dp, bottom = metrics.rowVPad + 2.dp)
|
||||
@@ -396,7 +419,7 @@ private fun ClassicPlaceholderRow(date: LocalDate, backed: Boolean, metrics: Age
|
||||
|
||||
/** A classic event row: the calendar's colour stripe beside the title and time; a dimmed one fades both. */
|
||||
@Composable
|
||||
private fun ClassicEventRow(row: AgendaRow.Event, data: AgendaWidgetData.Ready, backed: Boolean, metrics: AgendaMetrics) {
|
||||
private fun ClassicEventRow(row: AgendaRow.Event, data: AgendaWidgetData.Ready, backed: Boolean, ink: ClassicInk, metrics: AgendaMetrics) {
|
||||
val context = androidx.glance.LocalContext.current
|
||||
val event = row.event
|
||||
val dimmed = row.dimmed(data)
|
||||
@@ -423,7 +446,7 @@ private fun ClassicEventRow(row: AgendaRow.Event, data: AgendaWidgetData.Ready,
|
||||
text = event.title.ifBlank { context.getString(R.string.event_untitled) },
|
||||
maxLines = 1,
|
||||
style = TextStyle(
|
||||
color = if (dimmed) GlanceTheme.colors.onSurface.withAlpha(CLASSIC_DIM_INK) else GlanceTheme.colors.onSurface,
|
||||
color = if (dimmed) ink.title.withAlpha(CLASSIC_DIM_INK) else ink.title,
|
||||
fontSize = metrics.eventTitle,
|
||||
textDecoration = glanceDeclinedDecoration(event.isCrossedOut),
|
||||
),
|
||||
@@ -432,11 +455,7 @@ private fun ClassicEventRow(row: AgendaRow.Event, data: AgendaWidgetData.Ready,
|
||||
text = eventTimeSummary(context, event, row.date, data.is24Hour),
|
||||
maxLines = 1,
|
||||
style = TextStyle(
|
||||
color = if (dimmed) {
|
||||
GlanceTheme.colors.onSurfaceVariant.withAlpha(CLASSIC_DIM_INK)
|
||||
} else {
|
||||
GlanceTheme.colors.onSurfaceVariant
|
||||
},
|
||||
color = if (dimmed) ink.secondary.withAlpha(CLASSIC_DIM_INK) else ink.secondary,
|
||||
fontSize = metrics.eventTime,
|
||||
),
|
||||
)
|
||||
@@ -451,6 +470,9 @@ private val CLASSIC_CARD_H_PAD = 10.dp
|
||||
/** How strongly a finished event's text shows in the classic style, on any fill. */
|
||||
private const val CLASSIC_DIM_INK = 0.5f
|
||||
|
||||
/** The inverted secondary text's strength, as `onSurfaceVariant` sits below `onSurface`. */
|
||||
private const val INVERTED_SECONDARY_INK = 0.75f
|
||||
|
||||
/** Where each row sits among the rows of its day, from each row's date, for the grouped-card shapes. */
|
||||
private fun dayPositions(dates: List<LocalDate>): List<Position> = buildList {
|
||||
var start = 0
|
||||
|
||||
@@ -74,6 +74,10 @@ import de.jeanlucmakiola.calendula.widget.AgendaWidgetStyle
|
||||
import java.text.NumberFormat
|
||||
import de.jeanlucmakiola.floret.locale.currentLocale
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.foundation.layout.Row
|
||||
|
||||
private val PreviewMaxHeight = 280.dp
|
||||
|
||||
@@ -241,7 +245,11 @@ private fun ConfigOptions(
|
||||
onSelect = { onAppearance(state.appearance.copy(background = it)) },
|
||||
position = Position.Top,
|
||||
)
|
||||
if (state.appearance.background != WidgetBackground.TRANSPARENT) {
|
||||
var showReadability by rememberSaveable { mutableStateOf(false) }
|
||||
val faded = state.appearance.background != WidgetBackground.TRANSPARENT
|
||||
val content = state.draft.content
|
||||
val classic = state.kind == WidgetKind.AGENDA && content.style == AgendaWidgetStyle.CLASSIC
|
||||
if (faded) {
|
||||
val opacity = state.appearance.opacity
|
||||
GroupedSliderRow(
|
||||
title = stringResource(R.string.widget_config_opacity),
|
||||
@@ -253,6 +261,21 @@ private fun ConfigOptions(
|
||||
},
|
||||
valueRange = MIN_OPACITY..1f,
|
||||
steps = OPACITY_STEPS,
|
||||
trailing = { ReadabilityInfoButton { showReadability = !showReadability } },
|
||||
)
|
||||
}
|
||||
if (faded && classic) {
|
||||
GroupedRow(
|
||||
title = stringResource(R.string.widget_config_invert_text),
|
||||
summary = stringResource(R.string.widget_config_invert_text_summary),
|
||||
position = Position.Middle,
|
||||
trailing = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
ReadabilityInfoButton { showReadability = !showReadability }
|
||||
Switch(checked = content.invertText, onCheckedChange = { onContent(content.copy(invertText = it)) })
|
||||
}
|
||||
},
|
||||
onClick = { onContent(content.copy(invertText = !content.invertText)) },
|
||||
)
|
||||
}
|
||||
GroupedChoiceRow(
|
||||
@@ -263,6 +286,12 @@ private fun ConfigOptions(
|
||||
onSelect = { onAppearance(state.appearance.copy(theme = it)) },
|
||||
position = Position.Bottom,
|
||||
)
|
||||
AnimatedVisibility(visible = faded && showReadability) {
|
||||
GroupedHint(
|
||||
stringResource(if (classic) R.string.widget_config_readability_hint_classic else R.string.widget_config_readability_hint),
|
||||
verticalPadding = 8.dp,
|
||||
)
|
||||
}
|
||||
if (state.kind == WidgetKind.COUNTDOWN) {
|
||||
CountdownOptionsSection(
|
||||
options = state.draft.countdown,
|
||||
@@ -393,6 +422,18 @@ private fun ContentOptions(
|
||||
}
|
||||
}
|
||||
|
||||
/** The "i" beside the opacity and text-colour options; toggles the readability hint below them. */
|
||||
@Composable
|
||||
private fun ReadabilityInfoButton(onClick: () -> Unit) {
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(
|
||||
Icons.Outlined.Info,
|
||||
contentDescription = stringResource(R.string.widget_config_readability_info),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun agendaStyleLabel(style: AgendaWidgetStyle): String = stringResource(
|
||||
when (style) {
|
||||
|
||||
@@ -518,6 +518,11 @@
|
||||
<string name="widget_config_background_transparent">Transparent</string>
|
||||
<string name="widget_config_theme_follow_app">Follow app</string>
|
||||
<string name="widget_config_opacity">Opacity</string>
|
||||
<string name="widget_config_invert_text">Invert text colour</string>
|
||||
<string name="widget_config_invert_text_summary">Light text on a dark theme turns dark, and the other way round</string>
|
||||
<string name="widget_config_readability_info">About readability</string>
|
||||
<string name="widget_config_readability_hint">At low opacity the wallpaper shows through, so some text may be harder to read. Raise the opacity if it is.</string>
|
||||
<string name="widget_config_readability_hint_classic">At low opacity the wallpaper shows through, so some text may be hard to read. If it is, raise the opacity or invert the text colour.</string>
|
||||
<string name="widget_config_style">Style</string>
|
||||
<string name="widget_config_style_cards">Cards</string>
|
||||
<string name="widget_config_style_classic">Classic</string>
|
||||
|
||||
+1
-1
Submodule floret-kit updated: 377fbee065...7105140b8f.
Reference in new issue
Block a user