diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetConfig.kt b/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetConfig.kt index 32258b8..d50243b 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetConfig.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetConfig.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.datastore.preferences.core.MutablePreferences import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.floatPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.core.stringSetPreferencesKey import androidx.glance.GlanceId @@ -26,14 +27,20 @@ enum class WidgetTheme { FOLLOW_APP, LIGHT, DARK } data class WidgetAppearance( val background: WidgetBackground = WidgetBackground.TINTED, val theme: WidgetTheme = WidgetTheme.FOLLOW_APP, + /** How opaque a Solid or Tinted fill is (#489). */ + val opacity: Float = 1f, ) +/** The agenda widget's look: the 2.24 day cards, or v2.23's plain day headers and stripes (#489). */ +enum class AgendaWidgetStyle { CARDS, CLASSIC } + /** What the agenda widget lists, per placed widget. */ data class AgendaWidgetOptions( val range: AgendaRange, val textSize: WidgetSize, val showToday: Boolean, val pastDisplay: PastEventDisplay, + val style: AgendaWidgetStyle = AgendaWidgetStyle.CARDS, ) internal fun WidgetTheme.resolve(app: ThemeMode): WidgetThemeMode = when (this) { @@ -48,6 +55,8 @@ internal fun WidgetTheme.resolve(app: ThemeMode): WidgetThemeMode = when (this) 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 AgendaRangeKey = stringPreferencesKey("cfg_agenda_range") private val TextSizeKey = stringPreferencesKey("cfg_text_size") private val ShowTodayKey = booleanPreferencesKey("cfg_show_today") @@ -58,6 +67,7 @@ internal val Preferences.widgetAppearance: WidgetAppearance get() = WidgetAppearance( background = this[BackgroundKey].toEnum(WidgetBackground.TINTED), theme = this[ThemeKey].toEnum(WidgetTheme.FOLLOW_APP), + opacity = this[OpacityKey] ?: 1f, ) /** @@ -70,6 +80,7 @@ internal fun Preferences.agendaWidgetOptions(defaults: AgendaWidgetOptions) = Ag textSize = this[TextSizeKey].toEnum(defaults.textSize), showToday = this[ShowTodayKey] ?: defaults.showToday, pastDisplay = this[PastDisplayKey].toEnum(defaults.pastDisplay), + style = this[AgendaStyleKey].toEnum(defaults.style), ) /** Whether this widget has agenda options of its own, rather than following the app's. */ @@ -78,6 +89,7 @@ internal val Preferences.hasAgendaOptions: Boolean get() = this[AgendaRangeKey] internal fun MutablePreferences.putAppearance(appearance: WidgetAppearance) { this[BackgroundKey] = appearance.background.name this[ThemeKey] = appearance.theme.name + this[OpacityKey] = appearance.opacity } internal fun MutablePreferences.putAgendaOptions(options: AgendaWidgetOptions) { @@ -85,6 +97,7 @@ internal fun MutablePreferences.putAgendaOptions(options: AgendaWidgetOptions) { this[TextSizeKey] = options.textSize.name this[ShowTodayKey] = options.showToday this[PastDisplayKey] = options.pastDisplay.name + this[AgendaStyleKey] = options.style.name } /** 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 86be0e9..2052135 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetData.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetData.kt @@ -126,6 +126,7 @@ sealed interface AgendaWidgetData { /** "Always show today" (#35): anchor today at the top even with no events left. */ val showToday: Boolean, val textSize: WidgetSize, + val style: AgendaWidgetStyle = AgendaWidgetStyle.CARDS, /** Snapshot instant the data was read at, for "has this event ended?" tests. */ val now: Instant, ) : AgendaWidgetData @@ -184,6 +185,7 @@ internal suspend fun Context.loadAgendaWidgetData( pastDisplay = options.pastDisplay, showToday = options.showToday, textSize = options.textSize, + style = options.style, now = Clock.System.now(), ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetKind.kt b/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetKind.kt index 1fb5ac2..11af1fd 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetKind.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetKind.kt @@ -26,7 +26,7 @@ import de.jeanlucmakiola.calendula.widget.split.MonthDayWidget import de.jeanlucmakiola.calendula.widget.split.MonthDayWidgetReceiver /** A content option a widget's config screen offers, besides background and theme. */ -enum class WidgetOption { RANGE, TEXT_SIZE, PAST_DISPLAY, SHOW_TODAY, CALENDARS } +enum class WidgetOption { STYLE, RANGE, TEXT_SIZE, PAST_DISPLAY, SHOW_TODAY, CALENDARS } /** * Every widget the app ships, told apart by their receiver: what redraws, @@ -42,7 +42,14 @@ enum class WidgetKind( AGENDA( AgendaWidgetReceiver::class.java, R.string.widget_agenda_label, - setOf(WidgetOption.RANGE, WidgetOption.TEXT_SIZE, WidgetOption.PAST_DISPLAY, WidgetOption.SHOW_TODAY, WidgetOption.CALENDARS), + setOf( + WidgetOption.STYLE, + WidgetOption.RANGE, + WidgetOption.TEXT_SIZE, + WidgetOption.PAST_DISPLAY, + WidgetOption.SHOW_TODAY, + WidgetOption.CALENDARS, + ), DpSize(260.dp, 280.dp), ), MONTH( diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetTheme.kt b/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetTheme.kt index 6dd319e..88b9302 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetTheme.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/widget/WidgetTheme.kt @@ -34,7 +34,7 @@ fun CalendulaGlanceTheme(mode: WidgetThemeMode, content: @Composable () -> Unit) @Composable internal fun CalendulaWidgetRoot(appearance: WidgetAppearance, appTheme: ThemeMode, content: @Composable () -> Unit) { CalendulaGlanceTheme(appearance.theme.resolve(appTheme)) { - WidgetSurface(background = appearance.background, content = content) + WidgetSurface(background = appearance.background, opacity = appearance.opacity, content = content) } } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/widget/agenda/AgendaScale.kt b/app/src/main/java/de/jeanlucmakiola/calendula/widget/agenda/AgendaScale.kt index 8b07067..049d6b3 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/widget/agenda/AgendaScale.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/widget/agenda/AgendaScale.kt @@ -12,6 +12,14 @@ internal val ROW_H_PAD = 12.dp /** Space between the date-pill gutter and a day's card. */ internal val GUTTER_GAP = 6.dp +/** The classic style's row inset, colour stripe and the gap after it (#489). */ +internal val CLASSIC_ROW_H_PAD = 4.dp +internal val STRIPE_WIDTH = 5.dp +internal val STRIPE_GAP = 10.dp + +/** Where the classic style's event text starts, so its placeholder lines up with the titles. */ +internal val TEXT_INDENT = CLASSIC_ROW_H_PAD + STRIPE_WIDTH + STRIPE_GAP + /** * Every size the agenda widget varies by [WidgetSize]. Values that genuinely * shouldn't grow (the horizontal row constants above, corner radii) stay @@ -29,6 +37,8 @@ internal data class AgendaMetrics( val rowVPad: Dp, // event row vertical padding val dayGap: Dp, // space between two days' cards val monthLabel: TextUnit, // month name above a new month's first day + val dayHeader: TextUnit, // classic style's day label ("Today · …") + val dayHeaderTopPad: Dp, // classic style's space above a day label ) { /** One event row's height, shared by the date pill beside it and the placeholder row. */ val rowHeight: Dp get() = contentH + rowVPad * 2 @@ -61,6 +71,8 @@ private val SMALL_METRICS = AgendaMetrics( rowVPad = 4.dp, dayGap = 8.dp, monthLabel = 12.sp, + dayHeader = 13.sp, + dayHeaderTopPad = 10.dp, ) private val MEDIUM_METRICS = AgendaMetrics( @@ -75,6 +87,8 @@ private val MEDIUM_METRICS = AgendaMetrics( rowVPad = 5.dp, dayGap = 9.dp, monthLabel = 14.sp, + dayHeader = 14.sp, // M3 Title Small + dayHeaderTopPad = 11.dp, ) private val LARGE_METRICS = AgendaMetrics( @@ -89,6 +103,8 @@ private val LARGE_METRICS = AgendaMetrics( rowVPad = 6.dp, dayGap = 10.dp, monthLabel = 14.sp, + dayHeader = 16.sp, // M3 Title Medium + dayHeaderTopPad = 12.dp, ) private val EXTRA_LARGE_METRICS = AgendaMetrics( @@ -103,6 +119,8 @@ private val EXTRA_LARGE_METRICS = AgendaMetrics( rowVPad = 8.dp, dayGap = 12.dp, monthLabel = 16.sp, + dayHeader = 18.sp, // † + dayHeaderTopPad = 14.dp, ) /** Indexed by [WidgetSize.ordinal] so lookup allocates nothing per recomposition. */ 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 03dde16..a3a3169 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 @@ -11,6 +11,7 @@ import de.jeanlucmakiola.calendula.widget.glanceDeclinedDecoration import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.glance.GlanceId import androidx.glance.GlanceModifier @@ -56,6 +57,11 @@ import de.jeanlucmakiola.calendula.ui.common.CalendarView import de.jeanlucmakiola.calendula.ui.common.formatCalendarTitle import de.jeanlucmakiola.floret.locale.localizedDateFormatter 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.WidgetBackground import de.jeanlucmakiola.calendula.widget.WidgetDimFillAlpha import de.jeanlucmakiola.calendula.widget.widgetEventInk import de.jeanlucmakiola.calendula.ui.common.SECONDARY_INK_ALPHA @@ -220,59 +226,216 @@ private fun AgendaRows( } } }.take(MAX_AGENDA_ROWS) - // Each day is one card of grouped rows (#71), with its date pill - // beside the first row. - val positions = dayPositions(rows) - val cardColor = widgetCardColor() - val item: @Composable (Int) -> Unit = { index -> - val row = rows[index] - val position = positions[index] - val first = position == Position.Top || position == Position.Alone - val dayRow: @Composable (GlanceModifier) -> Unit = { modifier -> - Row(modifier = modifier.fillMaxWidth()) { - Box(modifier = GlanceModifier.width(metrics.pillWidth)) { - if (first) DatePill(row.date, data.today, cardColor, metrics) - } - Spacer(GlanceModifier.width(GUTTER_GAP)) - Box(modifier = GlanceModifier.defaultWeight()) { - val rowColor = when (row) { - is AgendaRow.Event -> row.fill(data).let { themedColor(day = it.day, night = it.night) } - is AgendaRow.Placeholder -> cardColor - } - WidgetGroupedRow(position = position, color = rowColor) { - AgendaRowContent(row, data, metrics) - } - } - } - } - val gap = GlanceModifier.padding(top = if (first && index > 0) metrics.dayGap else 0.dp) - // The pills carry no month, so a new month gets its name above its first day. - if (first && index > 0 && row.date.month != rows[index - 1].date.month) { - Column(modifier = gap.fillMaxWidth()) { - MonthLabel(row.date, data.today, metrics) - dayRow(GlanceModifier) - } - } else { - dayRow(gap) - } - } - if (preview) { - Column(modifier = GlanceModifier.fillMaxSize()) { - repeat(minOf(rows.size, PREVIEW_ROWS)) { item(it) } - } - } else { - LazyColumn(modifier = GlanceModifier.fillMaxSize()) { - items(rows.size) { item(it) } - } + when (data.style) { + AgendaWidgetStyle.CARDS -> CardRows(rows, data, metrics, preview) + AgendaWidgetStyle.CLASSIC -> ClassicRows(rows, data, metrics, preview) } } } -/** Where each row sits among the rows of its day, for the grouped-card shapes. */ -private fun dayPositions(rows: List): List = buildList { +/** Each day is one card of grouped rows (#71), with its date pill beside the first row. */ +@Composable +private fun CardRows(rows: List, data: AgendaWidgetData.Ready, metrics: AgendaMetrics, preview: Boolean) { + val positions = dayPositions(rows.map { it.date }) + val cardColor = widgetCardColor() + val item: @Composable (Int) -> Unit = { index -> + val row = rows[index] + val position = positions[index] + val first = position == Position.Top || position == Position.Alone + val dayRow: @Composable (GlanceModifier) -> Unit = { modifier -> + Row(modifier = modifier.fillMaxWidth()) { + Box(modifier = GlanceModifier.width(metrics.pillWidth)) { + if (first) DatePill(row.date, data.today, cardColor, metrics) + } + Spacer(GlanceModifier.width(GUTTER_GAP)) + Box(modifier = GlanceModifier.defaultWeight()) { + val rowColor = when (row) { + is AgendaRow.Event -> row.fill(data).let { themedColor(day = it.day, night = it.night) } + is AgendaRow.Placeholder -> cardColor + } + WidgetGroupedRow(position = position, color = rowColor) { + AgendaRowContent(row, data, metrics) + } + } + } + } + val gap = GlanceModifier.padding(top = if (first && index > 0) metrics.dayGap else 0.dp) + // The pills carry no month, so a new month gets its name above its first day. + if (first && index > 0 && row.date.month != rows[index - 1].date.month) { + Column(modifier = gap.fillMaxWidth()) { + MonthLabel(row.date, data.today, metrics) + dayRow(GlanceModifier) + } + } else { + dayRow(gap) + } + } + RowList(rows.size, preview, item) +} + +/** [count] rows of [item]: a plain column in a [preview], else a [LazyColumn]. */ +@Composable +private fun RowList(count: Int, preview: Boolean, item: @Composable (Int) -> Unit) { + if (preview) { + Column(modifier = GlanceModifier.fillMaxSize()) { + repeat(minOf(count, PREVIEW_ROWS)) { item(it) } + } + } else { + LazyColumn(modifier = GlanceModifier.fillMaxSize()) { + items(count) { item(it) } + } + } +} + +/** A classic list entry: a day's label line, or one of its rows. */ +private sealed interface ClassicItem { + val date: LocalDate + + data class Header(override val date: LocalDate) : ClassicItem + data class Entry(val row: AgendaRow) : ClassicItem { + override val date: LocalDate get() = row.date + } +} + +/** + * v2.23's look (#489): a label line per day over plain rows with a colour + * stripe. On a transparent root each day sits on its own backing card, so the + * text stays readable over the wallpaper. + */ +@Composable +private fun ClassicRows(rows: List, data: AgendaWidgetData.Ready, metrics: AgendaMetrics, preview: Boolean) { + val items = buildList { + rows.forEachIndexed { index, row -> + if (index == 0 || row.date != rows[index - 1].date) add(ClassicItem.Header(row.date)) + add(ClassicItem.Entry(row)) + } + } + val backed = LocalWidgetBackground.current == WidgetBackground.TRANSPARENT + val positions = dayPositions(items.map { it.date }) + val item: @Composable (Int) -> Unit = { index -> + val entry = items[index] + val content: @Composable () -> Unit = { + when (entry) { + is ClassicItem.Header -> DayHeaderRow( + date = entry.date, + today = data.today, + topPad = if (backed) CLASSIC_CARD_TOP_PAD else metrics.dayHeaderTopPad, + metrics = metrics, + ) + is ClassicItem.Entry -> when (val row = entry.row) { + is AgendaRow.Placeholder -> ClassicPlaceholderRow(row.date, metrics) + is AgendaRow.Event -> ClassicEventRow(row, data, metrics) + } + } + } + if (backed) { + val gap = if (entry is ClassicItem.Header && index > 0) metrics.dayGap else 0.dp + WidgetGroupedRow( + position = positions[index], + modifier = GlanceModifier.padding(top = gap), + color = GlanceTheme.colors.widgetBackground, + content = content, + ) + } else { + content() + } + } + RowList(items.size, preview, item) +} + +/** A classic day label, "Today · Wed, 17 Jun"; opens that day in the app. */ +@Composable +private fun DayHeaderRow(date: LocalDate, today: LocalDate, topPad: Dp, 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, + fontSize = metrics.dayHeader, + fontWeight = FontWeight.Medium, + ), + modifier = GlanceModifier + .fillMaxWidth() + .padding(start = 8.dp, end = 8.dp, top = topPad, bottom = metrics.rowVPad) + .clickable(actionStartActivity(MainActivity.openDateIntent(context, date, CalendarView.Agenda))), + ) +} + +/** "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, 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), + modifier = GlanceModifier + .fillMaxWidth() + .padding(start = TEXT_INDENT, end = 8.dp, top = 2.dp, bottom = metrics.rowVPad + 2.dp) + .clickable(actionStartActivity(MainActivity.openDateIntent(context, date, CalendarView.Agenda))), + ) +} + +/** 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, metrics: AgendaMetrics) { + val context = androidx.glance.LocalContext.current + val event = row.event + val dimmed = data.pastDisplay == PastEventDisplay.DIM && event.hasEnded(data.now) + fun accent(dark: Boolean) = eventAccent(event.color, dark, data.soften).let { + if (dimmed) it.copy(alpha = EventDimAlpha) else it + } + Row( + modifier = GlanceModifier + .fillMaxWidth() + .padding(horizontal = CLASSIC_ROW_H_PAD, vertical = metrics.rowVPad) + .clickable(actionStartActivity(event.openIntent(context))), + verticalAlignment = Alignment.CenterVertically, + ) { + Box( + modifier = GlanceModifier + .width(STRIPE_WIDTH) + .height(metrics.contentH) + .cornerRadius(3.dp) + .background(themedColor(day = accent(dark = false), night = accent(dark = true))), + ) {} + Spacer(GlanceModifier.width(STRIPE_GAP)) + Column(modifier = GlanceModifier.defaultWeight()) { + Text( + text = event.title.ifBlank { context.getString(R.string.event_untitled) }, + maxLines = 1, + style = TextStyle( + color = if (dimmed) GlanceTheme.colors.onSurfaceVariant else GlanceTheme.colors.onSurface, + fontSize = metrics.eventTitle, + textDecoration = glanceDeclinedDecoration(event.isCrossedOut), + ), + ) + Text( + text = eventTimeSummary(context, event, row.date, data.is24Hour), + maxLines = 1, + style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = metrics.eventTime), + ) + } + } +} + +/** Space above a day's label on its own backing card. */ +private val CLASSIC_CARD_TOP_PAD = 8.dp + +private fun EventInstance.openIntent(context: Context) = MainActivity.openEventIntent( + context = context, + eventId = eventId, + beginMillis = start.toEpochMilliseconds(), + endMillis = end.toEpochMilliseconds(), + source = CalendarView.Agenda, +) + +/** 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): List = buildList { var start = 0 - for (end in 1..rows.size) { - if (end == rows.size || rows[end].date != rows[start].date) { + for (end in 1..dates.size) { + if (end == dates.size || dates[end] != dates[start]) { for (i in start until end) add(positionOf(i - start, end - start)) start = end } @@ -421,17 +584,7 @@ private fun EventRow( .fillMaxWidth() .height(metrics.rowHeight) .padding(horizontal = ROW_H_PAD) - .clickable( - actionStartActivity( - MainActivity.openEventIntent( - context = context, - eventId = event.eventId, - beginMillis = event.start.toEpochMilliseconds(), - endMillis = event.end.toEpochMilliseconds(), - source = CalendarView.Agenda, - ), - ), - ), + .clickable(actionStartActivity(event.openIntent(context))), verticalAlignment = Alignment.CenterVertically, ) { Text( diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/widget/config/WidgetConfigScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/widget/config/WidgetConfigScreen.kt index f410e6e..e16200d 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/widget/config/WidgetConfigScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/widget/config/WidgetConfigScreen.kt @@ -69,6 +69,8 @@ import de.jeanlucmakiola.floret.glance.GlanceWidgetPreview import de.jeanlucmakiola.floret.glance.WallpaperWindow import de.jeanlucmakiola.floret.glance.WidgetBackground import kotlin.math.roundToInt +import de.jeanlucmakiola.floret.components.GroupedSliderRow +import de.jeanlucmakiola.calendula.widget.AgendaWidgetStyle private val PreviewMaxHeight = 280.dp @@ -236,6 +238,18 @@ private fun ConfigOptions( onSelect = { onAppearance(state.appearance.copy(background = it)) }, position = Position.Top, ) + if (state.appearance.background != WidgetBackground.TRANSPARENT) { + val opacity = state.appearance.opacity + GroupedSliderRow( + title = stringResource(R.string.widget_config_opacity), + value = opacity, + onValueChange = { onAppearance(state.appearance.copy(opacity = it)) }, + position = Position.Middle, + valueLabel = stringResource(R.string.widget_config_opacity_value, (opacity * 100).roundToInt()), + valueRange = MIN_OPACITY..1f, + steps = OPACITY_STEPS, + ) + } GroupedChoiceRow( title = stringResource(R.string.settings_theme), options = WidgetTheme.entries, @@ -265,6 +279,7 @@ private fun ContentOptions( onCalendarShown: (Long, Boolean) -> Unit, ) { val options = state.draft.content + var showStyle by rememberSaveable { mutableStateOf(false) } var showRange by rememberSaveable { mutableStateOf(false) } var showSize by rememberSaveable { mutableStateOf(false) } var showPast by rememberSaveable { mutableStateOf(false) } @@ -277,6 +292,12 @@ private fun ContentOptions( GroupedSectionHeader(stringResource(R.string.widget_config_content)) rows.forEach { option -> when (option) { + WidgetOption.STYLE -> GroupedRow( + title = stringResource(R.string.widget_config_style), + summary = agendaStyleLabel(options.style), + position = position(option), + onClick = { showStyle = true }, + ) WidgetOption.RANGE -> GroupedRow( title = stringResource(R.string.widget_config_range), summary = agendaRangeLabel(options.range), @@ -319,6 +340,17 @@ private fun ContentOptions( ) } + if (showStyle) { + OptionPicker( + title = stringResource(R.string.widget_config_style), + predictiveBack = true, + options = AgendaWidgetStyle.entries, + selected = options.style, + label = { agendaStyleLabel(it) }, + onSelect = { onChange(options.copy(style = it)) }, + onDismiss = { showStyle = false }, + ) + } if (showRange) { AgendaRangePicker( title = stringResource(R.string.widget_config_range), @@ -356,6 +388,18 @@ private fun ContentOptions( } } +@Composable +private fun agendaStyleLabel(style: AgendaWidgetStyle): String = stringResource( + when (style) { + AgendaWidgetStyle.CARDS -> R.string.widget_config_style_cards + AgendaWidgetStyle.CLASSIC -> R.string.widget_config_style_classic + }, +) + +/** The faintest fill the opacity slider allows, and its 10% steps between that and opaque. */ +private const val MIN_OPACITY = 0.2f +private const val OPACITY_STEPS = 7 + @Composable internal fun backgroundLabel(background: WidgetBackground): String = stringResource( when (background) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c5b8f79..a2503ad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -517,6 +517,11 @@ Tinted Transparent Follow app + Opacity + %1$d%% + Style + Cards + Classic Content Time range Text size diff --git a/floret-kit b/floret-kit index 1ff6702..e62730d 160000 --- a/floret-kit +++ b/floret-kit @@ -1 +1 @@ -Subproject commit 1ff6702fe6ff1ab695157e6559ec3761ba9f01b9 +Subproject commit e62730dd18e0a3e3fd50cd327ad11fc9a80d421f