Compare commits
17 Commits
a19772e3a7
...
v2.15.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 5be5ff77d0 | |||
| 6519cbca79 | |||
| 50510a23da | |||
| 2279738371 | |||
| 3d59962112 | |||
| a96d8b6de9 | |||
| 94b9eeaa78 | |||
|
|
85fb091e22 | ||
| 2139f8729c | |||
| 98a48aa795 | |||
| d255d232b9 | |||
| 9999fcdd2a | |||
| 953ffdff97 | |||
| 5377a2b466 | |||
| e73148dc6c | |||
|
|
8cd75716ae | ||
|
|
4feccf2008 |
34
CHANGELOG.md
34
CHANGELOG.md
@@ -5,9 +5,20 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [2.15.0] — 2026-07-15
|
||||||
|
|
||||||
### Added
|
### 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 →
|
- A custom snooze duration. The **Snooze duration** setting (Settings →
|
||||||
Notifications) gains a **Custom…** option next to the minute presets: pick any
|
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
|
amount and switch between minutes and hours, so a snoozed reminder comes back
|
||||||
@@ -32,7 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Keep today at the top of the agenda. A new **Always show today** setting
|
- Keep today at the top of the agenda. A new **Always show today** setting
|
||||||
(Settings → Agenda, on by default) anchors today as the first entry in both the
|
(Settings → Agenda, on by default) anchors today as the first entry in both the
|
||||||
Agenda screen and its home-screen widget even once nothing is left today —
|
Agenda screen and its home-screen widget even once nothing is left today —
|
||||||
under today's header a small "No more events today" card appears — so the first
|
under today's header a "No more events today" note appears — so the first
|
||||||
events you see are clearly today's rather than a future day's. Turn it off to
|
events you see are clearly today's rather than a future day's. Turn it off to
|
||||||
keep the agenda purely upcoming. Thanks to @ptab for the suggestion ([#35]).
|
keep the agenda purely upcoming. Thanks to @ptab for the suggestion ([#35]).
|
||||||
- Duplicate an event. The event details now carry a **Duplicate** action that
|
- Duplicate an event. The event details now carry a **Duplicate** action that
|
||||||
@@ -44,6 +55,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
editor if you want it). Duplicate works from read-only calendars too, dropping
|
editor if you want it). Duplicate works from read-only calendars too, dropping
|
||||||
the copy into a writable one. Thanks to @internet-rando for the suggestion
|
the copy into a writable one. Thanks to @internet-rando for the suggestion
|
||||||
([#52]).
|
([#52]).
|
||||||
|
- French and Polish, in early form. Calendula has started speaking French and
|
||||||
|
Polish, both contributed as community translations through
|
||||||
|
[Calendula's Weblate](https://weblate.dev.jeanlucmakiola.de/projects/calendula/).
|
||||||
|
They are partway there, so untranslated parts still show in English until they
|
||||||
|
fill out — you can already pick either under Settings → Language or in Android's
|
||||||
|
per-app language settings. Thanks to Thomas Tref (French) and Bazyli Cyran
|
||||||
|
(Polish) for getting them started; help finishing them is very welcome.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Reminders for events on another day no longer read as if they were today. A
|
- Reminders for events on another day no longer read as if they were today. A
|
||||||
@@ -52,6 +70,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
says which day: **Tomorrow** or **Yesterday**, the weekday for another day this
|
says which day: **Tomorrow** or **Yesterday**, the weekday for another day this
|
||||||
week, or the date for anything further out. Thanks to @moonj for the report
|
week, or the date for anything further out. Thanks to @moonj for the report
|
||||||
([#46]).
|
([#46]).
|
||||||
|
- Agenda dates now read in your locale's format. The agenda's range bar and its
|
||||||
|
day headers used a fixed day-month-year layout — and the range span even mixed
|
||||||
|
two orders (e.g. "15 Jul – Aug 13, 2026") — instead of following your language's
|
||||||
|
conventions. Dates across the agenda and its widget now use your locale's own
|
||||||
|
field order, matching the rest of the app. The range bar also no longer repeats
|
||||||
|
the range's name from the selector button beside it, showing just the dates.
|
||||||
|
- Calendar gutters line up with the menu button. The Month view's week-number
|
||||||
|
column, and the Week and Day views' hour labels, sat a few pixels left of the
|
||||||
|
hamburger menu above them; they now line up with it. In Month view the day
|
||||||
|
cells also sit squarely under their weekday letters.
|
||||||
|
|
||||||
## [2.14.1] — 2026-07-13
|
## [2.14.1] — 2026-07-13
|
||||||
|
|
||||||
@@ -954,10 +982,12 @@ automatically, with zero telemetry and no internet permission.
|
|||||||
[#25]: https://codeberg.org/jlmakiola/calendula/issues/25
|
[#25]: https://codeberg.org/jlmakiola/calendula/issues/25
|
||||||
[#27]: https://codeberg.org/jlmakiola/calendula/issues/27
|
[#27]: https://codeberg.org/jlmakiola/calendula/issues/27
|
||||||
[#29]: https://codeberg.org/jlmakiola/calendula/issues/29
|
[#29]: https://codeberg.org/jlmakiola/calendula/issues/29
|
||||||
|
[#21]: https://codeberg.org/jlmakiola/calendula/issues/21
|
||||||
[#30]: https://codeberg.org/jlmakiola/calendula/issues/30
|
[#30]: https://codeberg.org/jlmakiola/calendula/issues/30
|
||||||
[#32]: https://codeberg.org/jlmakiola/calendula/issues/32
|
[#32]: https://codeberg.org/jlmakiola/calendula/issues/32
|
||||||
[#33]: https://codeberg.org/jlmakiola/calendula/issues/33
|
[#33]: https://codeberg.org/jlmakiola/calendula/issues/33
|
||||||
[#34]: https://codeberg.org/jlmakiola/calendula/issues/34
|
[#34]: https://codeberg.org/jlmakiola/calendula/issues/34
|
||||||
|
[#36]: https://codeberg.org/jlmakiola/calendula/issues/36
|
||||||
[#37]: https://codeberg.org/jlmakiola/calendula/issues/37
|
[#37]: https://codeberg.org/jlmakiola/calendula/issues/37
|
||||||
[#39]: https://codeberg.org/jlmakiola/calendula/issues/39
|
[#39]: https://codeberg.org/jlmakiola/calendula/issues/39
|
||||||
[#35]: https://codeberg.org/jlmakiola/calendula/issues/35
|
[#35]: https://codeberg.org/jlmakiola/calendula/issues/35
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ android {
|
|||||||
// which builds this version and then creates the matching vX.Y.Z tag +
|
// which builds this version and then creates the matching vX.Y.Z tag +
|
||||||
// release itself (versionCode is pinned to MAJOR*10000 + MINOR*100 +
|
// release itself (versionCode is pinned to MAJOR*10000 + MINOR*100 +
|
||||||
// PATCH from versionName, e.g. 2.7.2 -> 20702). See docs/RELEASING.md.
|
// PATCH from versionName, e.g. 2.7.2 -> 20702). See docs/RELEASING.md.
|
||||||
versionCode = 21401
|
versionCode = 21500
|
||||||
versionName = "2.14.1"
|
versionName = "2.15.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import de.jeanlucmakiola.calendula.domain.EventForm
|
|||||||
import de.jeanlucmakiola.calendula.domain.buildInsertEventForm
|
import de.jeanlucmakiola.calendula.domain.buildInsertEventForm
|
||||||
import de.jeanlucmakiola.calendula.ui.RootScreen
|
import de.jeanlucmakiola.calendula.ui.RootScreen
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalShowHourLines
|
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.common.LocalUse24HourFormat
|
||||||
import de.jeanlucmakiola.calendula.ui.WidgetNavRequest
|
import de.jeanlucmakiola.calendula.ui.WidgetNavRequest
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
||||||
@@ -140,6 +141,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalUse24HourFormat provides use24Hour,
|
LocalUse24HourFormat provides use24Hour,
|
||||||
LocalShowHourLines provides settings.showHourLines,
|
LocalShowHourLines provides settings.showHourLines,
|
||||||
|
LocalSoftenColors provides settings.softenColors,
|
||||||
) {
|
) {
|
||||||
RootScreen(
|
RootScreen(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
|||||||
@@ -114,6 +114,21 @@ class SettingsPrefs @Inject constructor(
|
|||||||
store.edit { it[DYNAMIC_COLOR_KEY] = enabled }
|
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<Boolean> = 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
|
* Custom-font tokens per Material typeface role (issue #19). Stored as opaque
|
||||||
* strings — "system", "custom", or a bundled font's token — resolved to a
|
* strings — "system", "custom", or a bundled font's token — resolved to a
|
||||||
@@ -679,6 +694,7 @@ class SettingsPrefs @Inject constructor(
|
|||||||
companion object {
|
companion object {
|
||||||
internal val THEME_MODE_KEY = stringPreferencesKey("theme_mode")
|
internal val THEME_MODE_KEY = stringPreferencesKey("theme_mode")
|
||||||
internal val DYNAMIC_COLOR_KEY = booleanPreferencesKey("dynamic_color")
|
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 BRAND_FONT_KEY = stringPreferencesKey("brand_font")
|
||||||
internal val PLAIN_FONT_KEY = stringPreferencesKey("plain_font")
|
internal val PLAIN_FONT_KEY = stringPreferencesKey("plain_font")
|
||||||
internal val BRAND_FONT_STAMP_KEY = intPreferencesKey("brand_font_stamp")
|
internal val BRAND_FONT_STAMP_KEY = intPreferencesKey("brand_font_stamp")
|
||||||
|
|||||||
@@ -59,6 +59,8 @@ import de.jeanlucmakiola.calendula.data.prefs.PastEventDisplay
|
|||||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||||
import de.jeanlucmakiola.calendula.domain.hasEnded
|
import de.jeanlucmakiola.calendula.domain.hasEnded
|
||||||
import de.jeanlucmakiola.calendula.ui.common.AgendaRangePicker
|
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.calendula.ui.common.agendaRangeLabel
|
||||||
import de.jeanlucmakiola.floret.identity.animateItemMotion
|
import de.jeanlucmakiola.floret.identity.animateItemMotion
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarDrawer
|
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.floret.components.Position
|
||||||
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
||||||
import de.jeanlucmakiola.calendula.ui.common.next
|
import de.jeanlucmakiola.calendula.ui.common.next
|
||||||
import de.jeanlucmakiola.floret.components.pastelize
|
|
||||||
import de.jeanlucmakiola.floret.components.positionOf
|
import de.jeanlucmakiola.floret.components.positionOf
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCurrentMinute
|
import de.jeanlucmakiola.calendula.ui.common.rememberCurrentMinute
|
||||||
import de.jeanlucmakiola.calendula.ui.common.currentLocale
|
import de.jeanlucmakiola.calendula.ui.common.currentLocale
|
||||||
@@ -454,6 +455,7 @@ private fun AgendaEventRow(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val dark = isSystemInDarkTheme()
|
val dark = isSystemInDarkTheme()
|
||||||
|
val soften = LocalSoftenColors.current
|
||||||
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
|
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
modifier = if (dimmed) modifier.alpha(EventDimAlpha) else modifier,
|
modifier = if (dimmed) modifier.alpha(EventDimAlpha) else modifier,
|
||||||
@@ -466,7 +468,7 @@ private fun AgendaEventRow(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(width = 6.dp, height = 36.dp)
|
.size(width = 6.dp, height = 36.dp)
|
||||||
.clip(RoundedCornerShape(3.dp))
|
.clip(RoundedCornerShape(3.dp))
|
||||||
.background(pastelize(event.color, dark)),
|
.background(eventFill(event.color, dark, soften)),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ import de.jeanlucmakiola.calendula.data.prefs.SettingsPrefs
|
|||||||
import de.jeanlucmakiola.calendula.domain.CalendarSource
|
import de.jeanlucmakiola.calendula.domain.CalendarSource
|
||||||
import de.jeanlucmakiola.calendula.data.calendar.CalendarColorPalette
|
import de.jeanlucmakiola.calendula.data.calendar.CalendarColorPalette
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarColorChip
|
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.LeadingAvatar
|
||||||
import de.jeanlucmakiola.calendula.ui.common.SourceLogo
|
import de.jeanlucmakiola.calendula.ui.common.SourceLogo
|
||||||
import de.jeanlucmakiola.calendula.ui.common.curatedSourcePackage
|
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.GroupedRow
|
||||||
import de.jeanlucmakiola.floret.components.InlineTextField
|
import de.jeanlucmakiola.floret.components.InlineTextField
|
||||||
import de.jeanlucmakiola.floret.components.Position
|
import de.jeanlucmakiola.floret.components.Position
|
||||||
import de.jeanlucmakiola.floret.components.pastelize
|
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
|
|
||||||
/** Sentinel [editorId] meaning "the editor is composing a new calendar". */
|
/** 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 description by rememberSaveable(sessionKey) { mutableStateOf(initialDescription) }
|
||||||
var confirmDelete by remember { mutableStateOf(false) }
|
var confirmDelete by remember { mutableStateOf(false) }
|
||||||
val dark = isSystemInDarkTheme()
|
val dark = isSystemInDarkTheme()
|
||||||
|
val soften = LocalSoftenColors.current
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -624,7 +626,7 @@ private fun CalendarEditor(
|
|||||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(12.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(
|
InlineTextField(
|
||||||
value = name,
|
value = name,
|
||||||
onValueChange = { name = it },
|
onValueChange = { name = it },
|
||||||
|
|||||||
@@ -14,16 +14,17 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.unit.dp
|
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
|
* 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
|
* in the calendar's colour — softened to a pastel, or raw when the softener is
|
||||||
* visibility filter so they read identically.
|
* off (issue #36). Shared by the calendar manager and the visibility filter so
|
||||||
|
* they read identically.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun CalendarColorChip(color: Int, modifier: Modifier = Modifier) {
|
fun CalendarColorChip(color: Int, modifier: Modifier = Modifier) {
|
||||||
val dark = isSystemInDarkTheme()
|
val dark = isSystemInDarkTheme()
|
||||||
|
val soften = LocalSoftenColors.current
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(40.dp)
|
.size(40.dp)
|
||||||
@@ -34,7 +35,7 @@ fun CalendarColorChip(color: Int, modifier: Modifier = Modifier) {
|
|||||||
Icon(
|
Icon(
|
||||||
Icons.Filled.CalendarMonth,
|
Icons.Filled.CalendarMonth,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = pastelize(color, dark),
|
tint = eventFill(color, dark, soften),
|
||||||
modifier = Modifier.size(22.dp),
|
modifier = Modifier.size(22.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,15 +18,14 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import de.jeanlucmakiola.floret.components.pastelize
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapping row of round colour swatches; the one matching [selected] is
|
* A wrapping row of round colour swatches; the one matching [selected] is
|
||||||
* ringed and checked. Shared by the calendar editor and the event-colour
|
* ringed and checked. Shared by the calendar editor and the event-colour
|
||||||
* picker so both pick a colour the same way. Swatches render through
|
* 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)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -37,16 +36,18 @@ fun ColorSwatchRow(
|
|||||||
dark: Boolean,
|
dark: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
val soften = LocalSoftenColors.current
|
||||||
FlowRow(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
FlowRow(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
colors.forEach { argb ->
|
colors.forEach { argb ->
|
||||||
val isSelected = argb == selected
|
val isSelected = argb == selected
|
||||||
|
val fill = eventFill(argb, dark, soften)
|
||||||
Box(
|
Box(
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(vertical = 4.dp)
|
.padding(vertical = 4.dp)
|
||||||
.size(40.dp)
|
.size(40.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(pastelize(argb, dark))
|
.background(fill)
|
||||||
.then(
|
.then(
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
Modifier.border(2.dp, MaterialTheme.colorScheme.onSurface, CircleShape)
|
Modifier.border(2.dp, MaterialTheme.colorScheme.onSurface, CircleShape)
|
||||||
@@ -60,7 +61,7 @@ fun ColorSwatchRow(
|
|||||||
Icon(
|
Icon(
|
||||||
Icons.Default.Check,
|
Icons.Default.Check,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = Color.Black.copy(alpha = 0.7f),
|
tint = eventInk(fill, alpha = 0.7f),
|
||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(20.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -79,7 +79,9 @@ import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarFadeSpec
|
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarFadeSpec
|
||||||
import de.jeanlucmakiola.floret.identity.rememberReduceMotion
|
import de.jeanlucmakiola.floret.identity.rememberReduceMotion
|
||||||
import de.jeanlucmakiola.calendula.ui.common.next
|
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.rememberCalendarSlideSpec
|
||||||
import de.jeanlucmakiola.calendula.ui.common.currentLocale
|
import de.jeanlucmakiola.calendula.ui.common.currentLocale
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
|
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
|
||||||
@@ -97,6 +99,10 @@ import kotlin.math.roundToInt
|
|||||||
|
|
||||||
private val HOUR_HEIGHT = 56.dp
|
private val HOUR_HEIGHT = 56.dp
|
||||||
private val GUTTER_WIDTH = 48.dp
|
private val GUTTER_WIDTH = 48.dp
|
||||||
|
/** Start inset for the gutter's hour labels so they centre on the top bar's
|
||||||
|
* hamburger: with a 48dp gutter, 8dp lands the centre at 28dp (the app bar's
|
||||||
|
* 4dp inset + 24dp half icon button), matching the week view. */
|
||||||
|
private val GUTTER_CONTENT_START_INSET = 8.dp
|
||||||
private val MIN_EVENT_HEIGHT = 24.dp
|
private val MIN_EVENT_HEIGHT = 24.dp
|
||||||
private val ALL_DAY_ROW_HEIGHT = 24.dp
|
private val ALL_DAY_ROW_HEIGHT = 24.dp
|
||||||
private val ALL_DAY_VERTICAL_PADDING = 6.dp
|
private val ALL_DAY_VERTICAL_PADDING = 6.dp
|
||||||
@@ -448,9 +454,11 @@ private fun AllDayBar(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
|
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
|
||||||
|
val soften = LocalSoftenColors.current
|
||||||
|
val fill = eventFill(event.color, dark, soften)
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.background(pastelize(event.color, dark), RoundedCornerShape(4.dp))
|
.background(fill, RoundedCornerShape(4.dp))
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.padding(horizontal = 6.dp, vertical = 2.dp)
|
.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||||
.semantics { contentDescription = title },
|
.semantics { contentDescription = title },
|
||||||
@@ -461,7 +469,7 @@ private fun AllDayBar(
|
|||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
color = Color.Black.copy(alpha = 0.8f),
|
color = eventInk(fill),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -484,10 +492,12 @@ private fun Timeline(
|
|||||||
// static, rounded-clipped window — the content scrolls inside it, so the
|
// static, rounded-clipped window — the content scrolls inside it, so the
|
||||||
// soft corners are permanent at any scroll position.
|
// soft corners are permanent at any scroll position.
|
||||||
Row(modifier = Modifier.fillMaxSize()) {
|
Row(modifier = Modifier.fillMaxSize()) {
|
||||||
// Hour gutter (scrolls in sync with the day column)
|
// Hour gutter (scrolls in sync with the day column). Start inset so the
|
||||||
|
// labels centre on the top bar hamburger, matching the week view.
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(GUTTER_WIDTH)
|
.width(GUTTER_WIDTH)
|
||||||
|
.padding(start = GUTTER_CONTENT_START_INSET)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.verticalScroll(scrollState),
|
.verticalScroll(scrollState),
|
||||||
) {
|
) {
|
||||||
@@ -610,9 +620,11 @@ private fun EventBlock(
|
|||||||
val timeLabel = "${minToHm(block.startMin, use24Hour, locale)}–" +
|
val timeLabel = "${minToHm(block.startMin, use24Hour, locale)}–" +
|
||||||
minToHm(block.endMin, use24Hour, locale)
|
minToHm(block.endMin, use24Hour, locale)
|
||||||
val showTime = block.endMin - block.startMin >= 45
|
val showTime = block.endMin - block.startMin >= 45
|
||||||
|
val soften = LocalSoftenColors.current
|
||||||
|
val fill = eventFill(block.event.color, dark, soften)
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.background(pastelize(block.event.color, dark), RoundedCornerShape(4.dp))
|
.background(fill, RoundedCornerShape(4.dp))
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.padding(horizontal = 4.dp, vertical = 2.dp)
|
.padding(horizontal = 4.dp, vertical = 2.dp)
|
||||||
.semantics { contentDescription = "$title, $timeLabel" },
|
.semantics { contentDescription = "$title, $timeLabel" },
|
||||||
@@ -623,7 +635,7 @@ private fun EventBlock(
|
|||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
maxLines = if (showTime) 1 else 2,
|
maxLines = if (showTime) 1 else 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
color = Color.Black.copy(alpha = 0.85f),
|
color = eventInk(fill, alpha = 0.85f),
|
||||||
)
|
)
|
||||||
if (showTime) {
|
if (showTime) {
|
||||||
Text(
|
Text(
|
||||||
@@ -631,7 +643,7 @@ private fun EventBlock(
|
|||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
color = Color.Black.copy(alpha = 0.6f),
|
color = eventInk(fill, alpha = 0.6f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,11 +95,12 @@ import de.jeanlucmakiola.calendula.domain.RecurringWriteScope
|
|||||||
import de.jeanlucmakiola.calendula.domain.Reminder
|
import de.jeanlucmakiola.calendula.domain.Reminder
|
||||||
import de.jeanlucmakiola.floret.identity.predictiveBack
|
import de.jeanlucmakiola.floret.identity.predictiveBack
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarFailure
|
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.floret.components.OptionCard
|
||||||
import de.jeanlucmakiola.calendula.ui.common.currentLocale
|
import de.jeanlucmakiola.calendula.ui.common.currentLocale
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
|
import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat
|
||||||
import de.jeanlucmakiola.calendula.ui.common.timeOfDayFormatter
|
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.recurrenceText
|
||||||
import de.jeanlucmakiola.calendula.ui.common.reminderLeadTimeLabel
|
import de.jeanlucmakiola.calendula.ui.common.reminderLeadTimeLabel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -381,7 +382,7 @@ private fun EventDetailContent(state: EventDetailUiState.Success, modifier: Modi
|
|||||||
val instance = detail.instance
|
val instance = detail.instance
|
||||||
val dark = isSystemInDarkTheme()
|
val dark = isSystemInDarkTheme()
|
||||||
val locale = currentDetailLocale()
|
val locale = currentDetailLocale()
|
||||||
val accent = pastelize(instance.color, dark)
|
val accent = eventFill(instance.color, dark, LocalSoftenColors.current)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|||||||
@@ -126,6 +126,8 @@ import de.jeanlucmakiola.calendula.data.calendar.CalendarColorPalette
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarPickerGroups
|
import de.jeanlucmakiola.calendula.ui.common.CalendarPickerGroups
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarDatePickerDialog
|
import de.jeanlucmakiola.calendula.ui.common.CalendarDatePickerDialog
|
||||||
import de.jeanlucmakiola.calendula.ui.common.ColorSwatchRow
|
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.DialogAmountField
|
||||||
import de.jeanlucmakiola.floret.components.DialogUnitDropdown
|
import de.jeanlucmakiola.floret.components.DialogUnitDropdown
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventFormFieldIcon
|
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.timeOfDayFormatter
|
||||||
import de.jeanlucmakiola.calendula.ui.common.reminderLeadTimeLabel
|
import de.jeanlucmakiola.calendula.ui.common.reminderLeadTimeLabel
|
||||||
import de.jeanlucmakiola.calendula.ui.common.reminderUnitLabel
|
import de.jeanlucmakiola.calendula.ui.common.reminderUnitLabel
|
||||||
import de.jeanlucmakiola.floret.components.pastelize
|
|
||||||
import de.jeanlucmakiola.calendula.ui.common.recurrenceText
|
import de.jeanlucmakiola.calendula.ui.common.recurrenceText
|
||||||
import kotlinx.datetime.DayOfWeek
|
import kotlinx.datetime.DayOfWeek
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
@@ -541,7 +542,8 @@ private fun EventEditContent(
|
|||||||
val selectedCalendar = state.calendars.firstOrNull { it.id == form.calendarId }
|
val selectedCalendar = state.calendars.firstOrNull { it.id == form.calendarId }
|
||||||
// The accent ties the form to the detail screen's design language: the
|
// The accent ties the form to the detail screen's design language: the
|
||||||
// bar under the title takes the target calendar's colour.
|
// 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
|
?: MaterialTheme.colorScheme.primary
|
||||||
val gap = 12.dp
|
val gap = 12.dp
|
||||||
|
|
||||||
@@ -893,7 +895,7 @@ private fun EventEditContent(
|
|||||||
icon = Icons.Default.Palette,
|
icon = Icons.Default.Palette,
|
||||||
iconContentDescription = stringResource(R.string.event_edit_color),
|
iconContentDescription = stringResource(R.string.event_edit_color),
|
||||||
iconTint = if (colorSupported && swatch != null) {
|
iconTint = if (colorSupported && swatch != null) {
|
||||||
pastelize(swatch, dark)
|
eventFill(swatch, dark, soften)
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.onSurfaceVariant
|
MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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.IMPLEMENTED_VIEWS
|
||||||
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalDimCutoff
|
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.rememberCurrentMinute
|
||||||
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
||||||
import de.jeanlucmakiola.calendula.ui.common.calendarSlideTransition
|
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.currentLocale
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
||||||
import de.jeanlucmakiola.calendula.ui.common.next
|
import de.jeanlucmakiola.calendula.ui.common.next
|
||||||
import de.jeanlucmakiola.floret.components.pastelize
|
|
||||||
import de.jeanlucmakiola.floret.time.isoWeekNumber
|
import de.jeanlucmakiola.floret.time.isoWeekNumber
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.DayOfWeek
|
import kotlinx.datetime.DayOfWeek
|
||||||
@@ -380,7 +382,10 @@ private fun MonthGrid(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(horizontal = 4.dp, vertical = 4.dp),
|
// Match the weekday header's 8dp inset so day cells sit under their
|
||||||
|
// labels, and so the week-number gutter's centre lines up with the
|
||||||
|
// top bar's hamburger (4dp bar inset + 24dp half icon button).
|
||||||
|
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
) {
|
) {
|
||||||
state.weeks.forEach { week ->
|
state.weeks.forEach { week ->
|
||||||
@@ -627,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
|
@Composable
|
||||||
private fun MonthBar(
|
private fun MonthBar(
|
||||||
event: de.jeanlucmakiola.calendula.domain.EventInstance,
|
event: de.jeanlucmakiola.calendula.domain.EventInstance,
|
||||||
@@ -639,6 +644,8 @@ private fun MonthBar(
|
|||||||
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
|
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
|
||||||
val dimCutoff = LocalDimCutoff.current
|
val dimCutoff = LocalDimCutoff.current
|
||||||
val dimmed = dimCutoff != null && event.hasEnded(dimCutoff)
|
val dimmed = dimCutoff != null && event.hasEnded(dimCutoff)
|
||||||
|
val soften = LocalSoftenColors.current
|
||||||
|
val fill = eventFill(event.color, dark, soften)
|
||||||
val shape = RoundedCornerShape(
|
val shape = RoundedCornerShape(
|
||||||
topStart = if (continuesLeft) 0.dp else 4.dp,
|
topStart = if (continuesLeft) 0.dp else 4.dp,
|
||||||
bottomStart = if (continuesLeft) 0.dp else 4.dp,
|
bottomStart = if (continuesLeft) 0.dp else 4.dp,
|
||||||
@@ -647,7 +654,7 @@ private fun MonthBar(
|
|||||||
)
|
)
|
||||||
Box(
|
Box(
|
||||||
modifier = (if (dimmed) modifier.alpha(EventDimAlpha) else modifier)
|
modifier = (if (dimmed) modifier.alpha(EventDimAlpha) else modifier)
|
||||||
.background(pastelize(event.color, dark), shape)
|
.background(fill, shape)
|
||||||
.padding(horizontal = 4.dp)
|
.padding(horizontal = 4.dp)
|
||||||
.semantics { contentDescription = title },
|
.semantics { contentDescription = title },
|
||||||
contentAlignment = Alignment.CenterStart,
|
contentAlignment = Alignment.CenterStart,
|
||||||
@@ -657,7 +664,7 @@ private fun MonthBar(
|
|||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
color = Color.Black.copy(alpha = 0.8f),
|
color = eventInk(fill),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -670,6 +677,7 @@ private fun OverflowDots(
|
|||||||
dark: Boolean,
|
dark: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
val soften = LocalSoftenColors.current
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.height(EVENT_ROW_HEIGHT),
|
modifier = modifier.height(EVENT_ROW_HEIGHT),
|
||||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
@@ -679,7 +687,7 @@ private fun OverflowDots(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(6.dp)
|
.size(6.dp)
|
||||||
.background(pastelize(argb, dark), CircleShape),
|
.background(eventFill(argb, dark, soften), CircleShape),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (extra > 0) {
|
if (extra > 0) {
|
||||||
|
|||||||
@@ -55,9 +55,10 @@ import de.jeanlucmakiola.floret.components.GroupedRow
|
|||||||
import de.jeanlucmakiola.floret.components.InlineTextField
|
import de.jeanlucmakiola.floret.components.InlineTextField
|
||||||
import de.jeanlucmakiola.floret.components.Position
|
import de.jeanlucmakiola.floret.components.Position
|
||||||
import de.jeanlucmakiola.calendula.ui.common.currentLocale
|
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.LocalUse24HourFormat
|
||||||
import de.jeanlucmakiola.calendula.ui.common.timeOfDayFormatter
|
import de.jeanlucmakiola.calendula.ui.common.timeOfDayFormatter
|
||||||
import de.jeanlucmakiola.floret.components.pastelize
|
|
||||||
import de.jeanlucmakiola.floret.components.positionOf
|
import de.jeanlucmakiola.floret.components.positionOf
|
||||||
import java.time.Instant as JavaInstant
|
import java.time.Instant as JavaInstant
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
@@ -192,6 +193,7 @@ private fun SearchResultRow(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val dark = isSystemInDarkTheme()
|
val dark = isSystemInDarkTheme()
|
||||||
|
val soften = LocalSoftenColors.current
|
||||||
GroupedRow(
|
GroupedRow(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
title = event.title,
|
title = event.title,
|
||||||
@@ -203,7 +205,7 @@ private fun SearchResultRow(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(width = 6.dp, height = 36.dp)
|
.size(width = 6.dp, height = 36.dp)
|
||||||
.clip(RoundedCornerShape(3.dp))
|
.clip(RoundedCornerShape(3.dp))
|
||||||
.background(pastelize(event.color, dark)),
|
.background(eventFill(event.color, dark, soften)),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ private fun AppearanceScreen(
|
|||||||
} else {
|
} else {
|
||||||
stringResource(R.string.settings_dynamic_color_unavailable)
|
stringResource(R.string.settings_dynamic_color_unavailable)
|
||||||
},
|
},
|
||||||
position = Position.Bottom,
|
position = Position.Middle,
|
||||||
trailing = {
|
trailing = {
|
||||||
Switch(
|
Switch(
|
||||||
checked = state.dynamicColor,
|
checked = state.dynamicColor,
|
||||||
@@ -531,6 +531,18 @@ private fun AppearanceScreen(
|
|||||||
null
|
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))
|
Spacer(Modifier.height(16.dp))
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ data class SettingsUiState(
|
|||||||
val themeMode: ThemeMode = ThemeMode.SYSTEM,
|
val themeMode: ThemeMode = ThemeMode.SYSTEM,
|
||||||
val dynamicColor: Boolean = true,
|
val dynamicColor: Boolean = true,
|
||||||
val dynamicColorAvailable: 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,
|
val weekStart: WeekStartPref = WeekStartPref.Auto,
|
||||||
/** Clock convention for time labels (v2.11). AUTO follows the system setting. */
|
/** Clock convention for time labels (v2.11). AUTO follows the system setting. */
|
||||||
val timeFormat: TimeFormatPref = TimeFormatPref.AUTO,
|
val timeFormat: TimeFormatPref = TimeFormatPref.AUTO,
|
||||||
|
|||||||
@@ -123,14 +123,17 @@ class SettingsViewModel @Inject constructor(
|
|||||||
prefs.showHourLines,
|
prefs.showHourLines,
|
||||||
prefs.showWeekNumbers,
|
prefs.showWeekNumbers,
|
||||||
prefs.agendaShowToday,
|
prefs.agendaShowToday,
|
||||||
::Triple,
|
prefs.softenCalendarColors,
|
||||||
),
|
) { hourLines, weekNumbers, showToday, soften ->
|
||||||
|
DisplayToggles(hourLines, weekNumbers, showToday, soften)
|
||||||
|
},
|
||||||
) { view, screenRange, widgetRange, timeFormat, toggles ->
|
) { view, screenRange, widgetRange, timeFormat, toggles ->
|
||||||
ViewSettings(
|
ViewSettings(
|
||||||
view, screenRange, widgetRange, timeFormat,
|
view, screenRange, widgetRange, timeFormat,
|
||||||
showHourLines = toggles.first,
|
showHourLines = toggles.showHourLines,
|
||||||
showWeekNumbers = toggles.second,
|
showWeekNumbers = toggles.showWeekNumbers,
|
||||||
agendaShowToday = toggles.third,
|
agendaShowToday = toggles.agendaShowToday,
|
||||||
|
softenColors = toggles.softenColors,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
combine(
|
combine(
|
||||||
@@ -155,6 +158,7 @@ class SettingsViewModel @Inject constructor(
|
|||||||
showHourLines = views.showHourLines,
|
showHourLines = views.showHourLines,
|
||||||
showWeekNumbers = views.showWeekNumbers,
|
showWeekNumbers = views.showWeekNumbers,
|
||||||
agendaShowToday = views.agendaShowToday,
|
agendaShowToday = views.agendaShowToday,
|
||||||
|
softenColors = views.softenColors,
|
||||||
agendaShowRangeBar = misc.showRangeBar,
|
agendaShowRangeBar = misc.showRangeBar,
|
||||||
autofocusEventTitle = misc.autofocusEventTitle,
|
autofocusEventTitle = misc.autofocusEventTitle,
|
||||||
pastEventDisplay = misc.pastEventDisplay,
|
pastEventDisplay = misc.pastEventDisplay,
|
||||||
@@ -229,6 +233,14 @@ class SettingsViewModel @Inject constructor(
|
|||||||
val showHourLines: Boolean,
|
val showHourLines: Boolean,
|
||||||
val showWeekNumbers: Boolean,
|
val showWeekNumbers: Boolean,
|
||||||
val agendaShowToday: 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(
|
private data class MiscSettings(
|
||||||
@@ -341,6 +353,19 @@ class SettingsViewModel @Inject constructor(
|
|||||||
viewModelScope.launch { prefs.setDynamicColor(enabled) }
|
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) {
|
fun setFont(role: FontRole, token: String) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
when (role) {
|
when (role) {
|
||||||
|
|||||||
@@ -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.IMPLEMENTED_VIEWS
|
||||||
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalDimCutoff
|
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.NowLine
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCurrentMinute
|
import de.jeanlucmakiola.calendula.ui.common.rememberCurrentMinute
|
||||||
import de.jeanlucmakiola.calendula.ui.common.ViewSwitcherPill
|
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.hourSeparatorLines
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
import de.jeanlucmakiola.calendula.ui.common.rememberCalendarSlideSpec
|
||||||
import de.jeanlucmakiola.calendula.ui.common.next
|
import de.jeanlucmakiola.calendula.ui.common.next
|
||||||
import de.jeanlucmakiola.floret.components.pastelize
|
|
||||||
import de.jeanlucmakiola.floret.time.isoWeekNumber
|
import de.jeanlucmakiola.floret.time.isoWeekNumber
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -114,6 +116,10 @@ import kotlin.math.roundToInt
|
|||||||
|
|
||||||
private val HOUR_HEIGHT = 56.dp
|
private val HOUR_HEIGHT = 56.dp
|
||||||
private val GUTTER_WIDTH = 48.dp
|
private val GUTTER_WIDTH = 48.dp
|
||||||
|
/** Start inset for the gutter's content (week badge + hour labels) so it centres
|
||||||
|
* on the top bar's hamburger: with a 48dp gutter, 8dp lands the centre at 28dp
|
||||||
|
* (the app bar's 4dp inset + 24dp half icon button). */
|
||||||
|
private val GUTTER_CONTENT_START_INSET = 8.dp
|
||||||
private val MIN_EVENT_HEIGHT = 24.dp
|
private val MIN_EVENT_HEIGHT = 24.dp
|
||||||
private val ALL_DAY_ROW_HEIGHT = 24.dp
|
private val ALL_DAY_ROW_HEIGHT = 24.dp
|
||||||
private val ALL_DAY_VERTICAL_PADDING = 6.dp
|
private val ALL_DAY_VERTICAL_PADDING = 6.dp
|
||||||
@@ -448,9 +454,10 @@ private fun WeekDayHeader(
|
|||||||
.padding(top = 4.dp, bottom = 8.dp),
|
.padding(top = 4.dp, bottom = 8.dp),
|
||||||
) {
|
) {
|
||||||
// Mirror the day-column layout (empty weekday line + spacer) so the
|
// Mirror the day-column layout (empty weekday line + spacer) so the
|
||||||
// badge lines up vertically with the date numbers.
|
// badge lines up vertically with the date numbers. The start inset centres
|
||||||
|
// the badge on the top bar's hamburger (see GUTTER_CONTENT_START_INSET).
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.width(GUTTER_WIDTH),
|
modifier = Modifier.width(GUTTER_WIDTH).padding(start = GUTTER_CONTENT_START_INSET),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
Text(text = " ", style = MaterialTheme.typography.labelSmall)
|
Text(text = " ", style = MaterialTheme.typography.labelSmall)
|
||||||
@@ -584,9 +591,11 @@ private fun AllDayBar(
|
|||||||
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
|
val title = event.title.ifBlank { stringResource(R.string.event_untitled) }
|
||||||
val dimCutoff = LocalDimCutoff.current
|
val dimCutoff = LocalDimCutoff.current
|
||||||
val dimmed = dimCutoff != null && event.hasEnded(dimCutoff)
|
val dimmed = dimCutoff != null && event.hasEnded(dimCutoff)
|
||||||
|
val soften = LocalSoftenColors.current
|
||||||
|
val fill = eventFill(event.color, dark, soften)
|
||||||
Box(
|
Box(
|
||||||
modifier = (if (dimmed) modifier.alpha(EventDimAlpha) else modifier)
|
modifier = (if (dimmed) modifier.alpha(EventDimAlpha) else modifier)
|
||||||
.background(pastelize(event.color, dark), RoundedCornerShape(4.dp))
|
.background(fill, RoundedCornerShape(4.dp))
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.padding(horizontal = 6.dp, vertical = 2.dp)
|
.padding(horizontal = 6.dp, vertical = 2.dp)
|
||||||
.semantics { contentDescription = title },
|
.semantics { contentDescription = title },
|
||||||
@@ -597,7 +606,7 @@ private fun AllDayBar(
|
|||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
color = Color.Black.copy(alpha = 0.8f),
|
color = eventInk(fill),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -621,10 +630,12 @@ private fun Timeline(
|
|||||||
// soft corners are permanent at any scroll position (not just at the
|
// soft corners are permanent at any scroll position (not just at the
|
||||||
// day's start/end).
|
// day's start/end).
|
||||||
Row(modifier = Modifier.fillMaxSize()) {
|
Row(modifier = Modifier.fillMaxSize()) {
|
||||||
// Hour gutter (scrolls in sync with the day columns)
|
// Hour gutter (scrolls in sync with the day columns). Same start inset
|
||||||
|
// as the header badge so the labels sit under it and on the hamburger.
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(GUTTER_WIDTH)
|
.width(GUTTER_WIDTH)
|
||||||
|
.padding(start = GUTTER_CONTENT_START_INSET)
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
.verticalScroll(scrollState),
|
.verticalScroll(scrollState),
|
||||||
) {
|
) {
|
||||||
@@ -774,9 +785,11 @@ private fun EventBlock(
|
|||||||
val titleMaxLines = (contentHeight / titleLineHeight).toInt().coerceAtLeast(1)
|
val titleMaxLines = (contentHeight / titleLineHeight).toInt().coerceAtLeast(1)
|
||||||
val dimCutoff = LocalDimCutoff.current
|
val dimCutoff = LocalDimCutoff.current
|
||||||
val dimmed = dimCutoff != null && block.event.hasEnded(dimCutoff)
|
val dimmed = dimCutoff != null && block.event.hasEnded(dimCutoff)
|
||||||
|
val soften = LocalSoftenColors.current
|
||||||
|
val fill = eventFill(block.event.color, dark, soften)
|
||||||
Box(
|
Box(
|
||||||
modifier = (if (dimmed) modifier.alpha(EventDimAlpha) else modifier)
|
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)
|
.clickable(onClick = onClick)
|
||||||
.padding(horizontal = 4.dp, vertical = 2.dp)
|
.padding(horizontal = 4.dp, vertical = 2.dp)
|
||||||
.semantics { contentDescription = "$title, $timeLabel" },
|
.semantics { contentDescription = "$title, $timeLabel" },
|
||||||
@@ -787,7 +800,7 @@ private fun EventBlock(
|
|||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
maxLines = titleMaxLines,
|
maxLines = titleMaxLines,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
color = Color.Black.copy(alpha = 0.85f),
|
color = eventInk(fill, alpha = 0.85f),
|
||||||
)
|
)
|
||||||
if (showTime) {
|
if (showTime) {
|
||||||
Text(
|
Text(
|
||||||
@@ -795,7 +808,7 @@ private fun EventBlock(
|
|||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
color = Color.Black.copy(alpha = 0.6f),
|
color = eventInk(fill, alpha = 0.6f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ sealed interface AgendaWidgetData {
|
|||||||
val days: List<AgendaDay>,
|
val days: List<AgendaDay>,
|
||||||
/** Resolved clock convention for event time labels (the time-format pref). */
|
/** Resolved clock convention for event time labels (the time-format pref). */
|
||||||
val is24Hour: Boolean,
|
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. */
|
/** First day of the week, for the calendar-aligned "this week" range. */
|
||||||
val weekStart: DayOfWeek,
|
val weekStart: DayOfWeek,
|
||||||
/** Saved range pref — the fallback when an instance has no Glance state yet. */
|
/** 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 instances = ep.calendarRepository().instances(window).first()
|
||||||
val is24Hour = prefs.timeFormat.first()
|
val is24Hour = prefs.timeFormat.first()
|
||||||
.is24Hour(android.text.format.DateFormat.is24HourFormat(this))
|
.is24Hour(android.text.format.DateFormat.is24HourFormat(this))
|
||||||
|
val soften = prefs.softenCalendarColors.first()
|
||||||
return AgendaWidgetData.Ready(
|
return AgendaWidgetData.Ready(
|
||||||
today = anchor,
|
today = anchor,
|
||||||
days = groupAgendaDays(anchor, instances, zone),
|
days = groupAgendaDays(anchor, instances, zone),
|
||||||
is24Hour = is24Hour,
|
is24Hour = is24Hour,
|
||||||
|
soften = soften,
|
||||||
weekStart = weekStart,
|
weekStart = weekStart,
|
||||||
savedRange = savedRange,
|
savedRange = savedRange,
|
||||||
savedPastDisplay = savedPastDisplay,
|
savedPastDisplay = savedPastDisplay,
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
import de.jeanlucmakiola.calendula.ui.common.EventDimAlpha
|
||||||
import de.jeanlucmakiola.calendula.ui.common.formatTimeOfDay
|
import de.jeanlucmakiola.calendula.ui.common.formatTimeOfDay
|
||||||
import de.jeanlucmakiola.calendula.ui.common.localizedDateFormatter
|
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.AgendaWidgetData
|
||||||
import de.jeanlucmakiola.calendula.widget.CalendulaGlanceTheme
|
import de.jeanlucmakiola.calendula.widget.CalendulaGlanceTheme
|
||||||
import de.jeanlucmakiola.calendula.widget.loadAgendaWidgetData
|
import de.jeanlucmakiola.calendula.widget.loadAgendaWidgetData
|
||||||
@@ -197,6 +197,7 @@ private fun AgendaWidgetBody(data: AgendaWidgetData, dark: Boolean) {
|
|||||||
is AgendaRow.Event -> EventRow(
|
is AgendaRow.Event -> EventRow(
|
||||||
event = row.event,
|
event = row.event,
|
||||||
dark = dark,
|
dark = dark,
|
||||||
|
soften = data.soften,
|
||||||
is24Hour = data.is24Hour,
|
is24Hour = data.is24Hour,
|
||||||
dimmed = pastDisplay == PastEventDisplay.DIM &&
|
dimmed = pastDisplay == PastEventDisplay.DIM &&
|
||||||
row.event.hasEnded(data.now),
|
row.event.hasEnded(data.now),
|
||||||
@@ -287,50 +288,40 @@ private fun DayHeaderRow(date: LocalDate, today: LocalDate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A card under an anchored, event-less today (#35) — echoing the widget's
|
* "No more events today" under an anchored, event-less today (#35). Kept plain —
|
||||||
* full-empty message ("You're all caught up" here becomes "No more events
|
* a muted line indented to the event titles — since the widget's event rows are
|
||||||
* today") in a rounded surface so today's slot never looks like a bare header.
|
* plain too (a stripe + text, not cards), so a card here would look out of place.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun PlaceholderRow(date: LocalDate) {
|
private fun PlaceholderRow(date: LocalDate) {
|
||||||
val context = androidx.glance.LocalContext.current
|
val context = androidx.glance.LocalContext.current
|
||||||
Box(
|
Text(
|
||||||
|
text = context.getString(R.string.agenda_no_more_today),
|
||||||
|
style = TextStyle(color = GlanceTheme.colors.onSurfaceVariant, fontSize = 14.sp),
|
||||||
modifier = GlanceModifier
|
modifier = GlanceModifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 4.dp, vertical = 4.dp)
|
.padding(start = 19.dp, end = 8.dp, top = 2.dp, bottom = 6.dp)
|
||||||
.clickable(
|
.clickable(
|
||||||
actionStartActivity(
|
actionStartActivity(
|
||||||
MainActivity.openDateIntent(context, date, CalendarView.Agenda),
|
MainActivity.openDateIntent(context, date, CalendarView.Agenda),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = GlanceModifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(GlanceTheme.colors.surfaceVariant)
|
|
||||||
.cornerRadius(16.dp)
|
|
||||||
.padding(vertical = 18.dp, horizontal = 12.dp),
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = context.getString(R.string.agenda_no_more_today),
|
|
||||||
style = TextStyle(
|
|
||||||
color = GlanceTheme.colors.onSurfaceVariant,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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 context = androidx.glance.LocalContext.current
|
||||||
val title = event.title.ifBlank { context.getString(R.string.event_untitled) }
|
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
|
// 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.
|
// 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
|
if (dimmed) it.copy(alpha = EventDimAlpha) else it
|
||||||
}
|
}
|
||||||
val titleColor = if (dimmed) GlanceTheme.colors.onSurfaceVariant else GlanceTheme.colors.onSurface
|
val titleColor = if (dimmed) GlanceTheme.colors.onSurfaceVariant else GlanceTheme.colors.onSurface
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package de.jeanlucmakiola.calendula.widget.month
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.runtime.Composable
|
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.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.datastore.preferences.core.intPreferencesKey
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
@@ -50,14 +49,17 @@ import de.jeanlucmakiola.calendula.MainActivity
|
|||||||
import de.jeanlucmakiola.calendula.R
|
import de.jeanlucmakiola.calendula.R
|
||||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
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.MonthWeek
|
||||||
import de.jeanlucmakiola.calendula.ui.month.layoutMonthWeeks
|
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.CalendulaGlanceTheme
|
||||||
import de.jeanlucmakiola.calendula.widget.MonthWidgetSource
|
import de.jeanlucmakiola.calendula.widget.MonthWidgetSource
|
||||||
import de.jeanlucmakiola.calendula.widget.loadMonthWidgetSource
|
import de.jeanlucmakiola.calendula.widget.loadMonthWidgetSource
|
||||||
import de.jeanlucmakiola.calendula.widget.systemZone
|
import de.jeanlucmakiola.calendula.widget.systemZone
|
||||||
import de.jeanlucmakiola.calendula.widget.today
|
import de.jeanlucmakiola.calendula.widget.today
|
||||||
|
import de.jeanlucmakiola.calendula.widget.widgetEntryPoint
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import kotlinx.datetime.DayOfWeek
|
import kotlinx.datetime.DayOfWeek
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
@@ -76,9 +78,6 @@ private val LANE_HEIGHT = 14.dp
|
|||||||
private val DAY_NUMBER_HEIGHT = 18.dp
|
private val DAY_NUMBER_HEIGHT = 18.dp
|
||||||
private val GRID_HPADDING = 8.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 {
|
private fun currentMonthIndex(zone: TimeZone): Int {
|
||||||
val t = today(zone)
|
val t = today(zone)
|
||||||
return t.year * 12 + t.month.ordinal
|
return t.year * 12 + t.month.ordinal
|
||||||
@@ -107,9 +106,12 @@ class MonthWidget : GlanceAppWidget() {
|
|||||||
val source = context.loadMonthWidgetSource()
|
val source = context.loadMonthWidgetSource()
|
||||||
val dark = (context.resources.configuration.uiMode and
|
val dark = (context.resources.configuration.uiMode and
|
||||||
Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
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 {
|
provideContent {
|
||||||
CalendulaGlanceTheme {
|
CalendulaGlanceTheme {
|
||||||
MonthWidgetBody(source = source, dark = dark)
|
MonthWidgetBody(source = source, dark = dark, soften = soften)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,7 +142,7 @@ class ResetMonthAction : ActionCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MonthWidgetBody(source: MonthWidgetSource, dark: Boolean) {
|
private fun MonthWidgetBody(source: MonthWidgetSource, dark: Boolean, soften: Boolean) {
|
||||||
Column(
|
Column(
|
||||||
modifier = GlanceModifier
|
modifier = GlanceModifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -169,6 +171,7 @@ private fun MonthWidgetBody(source: MonthWidgetSource, dark: Boolean) {
|
|||||||
currentMonth = ym.month,
|
currentMonth = ym.month,
|
||||||
today = source.today,
|
today = source.today,
|
||||||
dark = dark,
|
dark = dark,
|
||||||
|
soften = soften,
|
||||||
colW = colW,
|
colW = colW,
|
||||||
modifier = GlanceModifier.defaultWeight(),
|
modifier = GlanceModifier.defaultWeight(),
|
||||||
)
|
)
|
||||||
@@ -278,6 +281,7 @@ private fun WeekRow(
|
|||||||
currentMonth: Month,
|
currentMonth: Month,
|
||||||
today: LocalDate,
|
today: LocalDate,
|
||||||
dark: Boolean,
|
dark: Boolean,
|
||||||
|
soften: Boolean,
|
||||||
colW: Dp,
|
colW: Dp,
|
||||||
modifier: GlanceModifier,
|
modifier: GlanceModifier,
|
||||||
) {
|
) {
|
||||||
@@ -297,7 +301,7 @@ private fun WeekRow(
|
|||||||
// One lane row per event row. A multi-day span is a single Box spanning
|
// 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.
|
// its columns (colW * n) so it's connected with no seam and rounded ends.
|
||||||
repeat(MAX_LANES) { lane ->
|
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))
|
Spacer(GlanceModifier.height(1.dp))
|
||||||
}
|
}
|
||||||
OverflowRow(week = week, colW = colW)
|
OverflowRow(week = week, colW = colW)
|
||||||
@@ -346,7 +350,7 @@ private fun DayNumber(date: LocalDate, isToday: Boolean, inMonth: Boolean, colW:
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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
|
val context = LocalContext.current
|
||||||
Row(modifier = GlanceModifier.fillMaxWidth()) {
|
Row(modifier = GlanceModifier.fillMaxWidth()) {
|
||||||
var col = 0
|
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 }
|
val span = week.spans.firstOrNull { it.lane == lane && col in it.startCol..it.endCol }
|
||||||
if (span != null) {
|
if (span != null) {
|
||||||
val cols = span.endCol - col + 1
|
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
|
col = span.endCol + 1
|
||||||
} else {
|
} else {
|
||||||
val timed = timedEventAt(week, lane, col, week.days[col])
|
val timed = timedEventAt(week, lane, col, week.days[col])
|
||||||
if (timed != null) {
|
if (timed != null) {
|
||||||
SpanBar(event = timed, dark = dark, width = colW)
|
SpanBar(event = timed, dark = dark, soften = soften, width = colW)
|
||||||
} else {
|
} else {
|
||||||
// Empty lane cell: a tap opens that day, so blank space in a
|
// 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.
|
// 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. */
|
/** A single connected, rounded event bar [width] wide with its clipped title. */
|
||||||
@Composable
|
@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 context = LocalContext.current
|
||||||
|
val fill = eventFill(event.color, dark, soften)
|
||||||
Box(
|
Box(
|
||||||
modifier = GlanceModifier
|
modifier = GlanceModifier
|
||||||
.width(width)
|
.width(width)
|
||||||
@@ -402,13 +407,13 @@ private fun SpanBar(event: EventInstance, dark: Boolean, width: Dp) {
|
|||||||
modifier = GlanceModifier
|
modifier = GlanceModifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.cornerRadius(4.dp)
|
.cornerRadius(4.dp)
|
||||||
.background(pastelize(event.color, dark)),
|
.background(fill),
|
||||||
contentAlignment = Alignment.CenterStart,
|
contentAlignment = Alignment.CenterStart,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = event.title.ifBlank { context.getString(R.string.event_untitled) },
|
text = event.title.ifBlank { context.getString(R.string.event_untitled) },
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
style = TextStyle(color = EventInk, fontSize = 9.sp),
|
style = TextStyle(color = ColorProvider(eventInk(fill)), fontSize = 9.sp),
|
||||||
modifier = GlanceModifier.padding(horizontal = 3.dp),
|
modifier = GlanceModifier.padding(horizontal = 3.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -403,15 +403,15 @@
|
|||||||
<string name="shortcut_new_event_long">Crear un nuevo evento</string>
|
<string name="shortcut_new_event_long">Crear un nuevo evento</string>
|
||||||
<string name="settings_qs_tile">Añadir mosaico de ajustes rápidos</string>
|
<string name="settings_qs_tile">Añadir mosaico de ajustes rápidos</string>
|
||||||
<string name="settings_qs_tile_hint">Añadir un recuadro de \"nuevo evento\" al panel de ajustes rápidos.</string>
|
<string name="settings_qs_tile_hint">Añadir un recuadro de \"nuevo evento\" al panel de ajustes rápidos.</string>
|
||||||
<string name="crash_dialog_title">Calendula ha fallado</string>
|
<string name="crash_dialog_title">%1$s ha fallado</string>
|
||||||
<string name="crash_dialog_message">Calendula se cerro de forma inesperada la ultima vez. Puedes ayudar a solucionarlo enviando un reporte del problema. Este permanece en tu dispositivo hasta que decidas enviarlo, y no contiene información personal o contenido de tu calendario — solo los detalles técnicos de mas abajo.</string>
|
<string name="crash_dialog_message">%1$s se cerro de forma inesperada la ultima vez. Puedes ayudar a solucionarlo enviando un reporte del problema. Este permanece en tu dispositivo hasta que decidas enviarlo, y no contiene información personal o contenido de tu calendario — solo los detalles técnicos de mas abajo.</string>
|
||||||
<string name="crash_dialog_report">Reportar</string>
|
<string name="crash_dialog_report">Reportar</string>
|
||||||
<string name="crash_dialog_dismiss">No por ahora</string>
|
<string name="crash_dialog_dismiss">No por ahora</string>
|
||||||
<string name="crash_report_issue_title">Reporte de error</string>
|
<string name="crash_report_issue_title">Reporte de error</string>
|
||||||
<string name="crash_report_clip_label">Reporte de error de Calendula</string>
|
<string name="crash_report_clip_label">Reporte de error de %1$s</string>
|
||||||
<string name="crash_report_copied">Reporte copiado a tu porta papeles</string>
|
<string name="crash_report_copied">Reporte copiado a tu porta papeles</string>
|
||||||
<string name="crash_report_open_failed">No se pudo abrir el sistema de seguimiento de errores. El reporte es en tu portapapeles.</string>
|
<string name="crash_report_open_failed">No se pudo abrir el sistema de seguimiento de errores. El reporte es en tu portapapeles.</string>
|
||||||
<string name="crash_report_body_template">Gracias por reportar un error en Calendula. Por favor añade todo lo que recuerdas sobre lo que estabas haciendo y luego publicalo.\n\n### que sucedió\n\n\n### reporte de error\n%1$s\n</string>
|
<string name="crash_report_body_template">Gracias por reportar un error en %1$s. Por favor añade todo lo que recuerdas sobre lo que estabas haciendo y luego publicalo.\n\n### que sucedió\n\n\n### reporte de error\n%2$s\n</string>
|
||||||
<string name="crash_report_body_paste">_(el reporte es demasiado largo para este link — pegalo aquí desde su portapeles.)_</string>
|
<string name="crash_report_body_paste">_(el reporte es demasiado largo para este link — pegalo aquí desde su portapeles.)_</string>
|
||||||
<string name="event_attendee_resource">Recurso</string>
|
<string name="event_attendee_resource">Recurso</string>
|
||||||
<string name="settings_section_about">Acerca de</string>
|
<string name="settings_section_about">Acerca de</string>
|
||||||
@@ -466,4 +466,31 @@
|
|||||||
<string name="settings_special_dates_grant">Permitir acceso</string>
|
<string name="settings_special_dates_grant">Permitir acceso</string>
|
||||||
<string name="dialog_save">Guardar</string>
|
<string name="dialog_save">Guardar</string>
|
||||||
<string name="settings_font_system">Igual que el sistema</string>
|
<string name="settings_font_system">Igual que el sistema</string>
|
||||||
|
<string name="import_reminder_prompt_title">¿Aplicar tus recordatorios por defecto?</string>
|
||||||
|
<string name="import_reminder_prompt_body_none">Este evento fue importado sin ningún recordatorio.</string>
|
||||||
|
<plurals name="import_reminder_prompt_body_existing">
|
||||||
|
<item quantity="one">Este evento fue importado con %1$d recordatorio.</item>
|
||||||
|
<item quantity="many">Este evento fue portado con %1$d recordatorios.</item>
|
||||||
|
<item quantity="other">Este evento fue importado con %1$d recordatorios.</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="import_reminder_prompt_apply">Aplicar por defecto</string>
|
||||||
|
<string name="import_reminder_prompt_keep">Mantener como esta</string>
|
||||||
|
<string name="back">Atras</string>
|
||||||
|
<string name="settings_week_numbers">Números de semana</string>
|
||||||
|
<string name="settings_week_numbers_summary">Muestra los números de las semanas en la vista mensual</string>
|
||||||
|
<string name="calendars_export_title">Exportar calendarios</string>
|
||||||
|
<string name="calendars_export_hint">Escoge que calendarios incluir en el archivo .ics</string>
|
||||||
|
<string name="calendars_export_action">Exportar</string>
|
||||||
|
<string name="calendars_restore_header">Restaurar</string>
|
||||||
|
<string name="calendars_restore_action">Restaurar por archivo .ics</string>
|
||||||
|
<string name="calendars_restore_hint">Importar eventos desde un respaldo u otra aplicación de calendario.</string>
|
||||||
|
<string name="import_done_dedup_note">Se omitieron los eventos ya presentes en el calendario.</string>
|
||||||
|
<string name="import_done_added_label">Añadido</string>
|
||||||
|
<string name="import_done_skipped_label">Duplicados</string>
|
||||||
|
<string name="import_button">Importar</string>
|
||||||
|
<plurals name="import_title_count">
|
||||||
|
<item quantity="one">Importando %d evento</item>
|
||||||
|
<item quantity="many">Importando %d eventos</item>
|
||||||
|
<item quantity="other">Importando %d eventos</item>
|
||||||
|
</plurals>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -6,4 +6,151 @@
|
|||||||
<item>Days</item>
|
<item>Days</item>
|
||||||
<item>Weeks</item>
|
<item>Weeks</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
<string name="app_name">Calendrier</string>
|
||||||
|
<string name="app_tagline">Un calendrier moderne.</string>
|
||||||
|
<string name="state_loading">Chargement…</string>
|
||||||
|
<string name="state_retry">Réessayer</string>
|
||||||
|
<string name="state_failure_unknown">Une erreur s\'est produit.</string>
|
||||||
|
<string name="state_failure_permission">Un accès au Calendrier est nécessaire.</string>
|
||||||
|
<string name="state_failure_permission_action">Autoriser l\'accès</string>
|
||||||
|
<string name="state_failure_no_calendars">Aucun calendrier configuré.</string>
|
||||||
|
<string name="state_failure_no_calendars_action">Ouvrir les paramètres du calendrier.</string>
|
||||||
|
<string name="state_failure_provider">Ne peut pas lire le calendrier.</string>
|
||||||
|
<string name="permission_rationale_title">Tous les évènements ont été trouvés.</string>
|
||||||
|
<string name="permission_rationale_body">Calendula a besoin d\'avoir accès à votre calendrier pour afficher et accéder aux événements. C\'est tout ce dont nous avons besoin, aucune données ne sort de votre appareil.</string>
|
||||||
|
<string name="permission_request_button">Autoriser l\'accès au calendrier</string>
|
||||||
|
<string name="permission_denied_title">Accès au calendrier refusé.</string>
|
||||||
|
<string name="permission_denied_body">Calendula ne peut pas afficher les évènements si elle ne peut pas accéder au calendrier. Vous pouvez l\'autoriser à tout moment dans les paramètres.</string>
|
||||||
|
<string name="permission_open_settings_button">Ouvrir les paramètres.</string>
|
||||||
|
<string name="permission_retry_button">Essayer à nouveau</string>
|
||||||
|
<string name="permission_benefit_private_title">Reste sur votre appareil</string>
|
||||||
|
<string name="permission_benefit_private_body">Vos calendriers sont lus localement et ne quittent jamais le téléphone.</string>
|
||||||
|
<string name="permission_benefit_sync_title">Tous vos calendriers, réunis</string>
|
||||||
|
<string name="permission_benefit_privacy_title">Aucun suivi, jamais</string>
|
||||||
|
<string name="permission_benefit_privacy_body">Aucune collecte de données, aucune analyse, pas de pubs</string>
|
||||||
|
<string name="permission_privacy_footnote">Reste sur votre appareil - Pas d\'utilisation d\'Internet</string>
|
||||||
|
<string name="month_prev">Mois précédent</string>
|
||||||
|
<string name="month_next">Prochain mois</string>
|
||||||
|
<string name="month_today_action">Aujourd\'hui</string>
|
||||||
|
<string name="month_more_actions">Plus d\'actions</string>
|
||||||
|
<string name="month_open_menu">Ouvrir le menu</string>
|
||||||
|
<string name="month_action_settings">Paramètres</string>
|
||||||
|
<string name="month_a11y_today_prefix">Aujourd\'hui</string>
|
||||||
|
<string name="week_today_action">Cette semaine</string>
|
||||||
|
<string name="day_today_action">Aujourd\'hui</string>
|
||||||
|
<string name="event_detail_back">Retour</string>
|
||||||
|
<string name="event_detail_edit">Modifier</string>
|
||||||
|
<string name="event_detail_delete">Supprimer</string>
|
||||||
|
<string name="event_detail_share">Partager</string>
|
||||||
|
<string name="event_share_chooser_title">Evénement partagé</string>
|
||||||
|
<string name="event_share_failed">Impossible de partager cet événement.</string>
|
||||||
|
<string name="event_delete_title">Evénement supprimé ?</string>
|
||||||
|
<string name="event_delete_body">Cet événement est retiré de votre calendrier et de chaque appareil auquel il est synchronisé.</string>
|
||||||
|
<string name="event_delete_recurring_title">Supprimer l\'événement récurrent</string>
|
||||||
|
<string name="event_delete_option_occurrence">Seulement cet événement</string>
|
||||||
|
<string name="event_delete_option_following">Cet événement et tous ceux qui suivent</string>
|
||||||
|
<string name="event_delete_option_series">Tous les événements de la série</string>
|
||||||
|
<string name="event_edit_recurring_title">Modifier l\'événement récurrent</string>
|
||||||
|
<string name="event_delete_failed">Impossible de supprimer l\'événement</string>
|
||||||
|
<string name="event_delete_write_denied">Calendula a besoin des droits d\'écriture pour supprimer des événements</string>
|
||||||
|
<string name="dialog_cancel">Annuler</string>
|
||||||
|
<string name="dialog_ok">OK</string>
|
||||||
|
<string name="event_edit_new_title">Nouvel événement</string>
|
||||||
|
<string name="event_edit_close">Fermer</string>
|
||||||
|
<string name="event_edit_save">Sauvegarder</string>
|
||||||
|
<string name="event_edit_title_hint">Ajouter un titre</string>
|
||||||
|
<string name="event_edit_managed_hint">dirigé par \"%1$s\" - titre, date, et répétitions sont synchronisés avec les contacts. Pour rappel, vous pouvez modifier la localisation et les notes à tout instant.</string>
|
||||||
|
<string name="event_edit_starts">Début</string>
|
||||||
|
<string name="event_edit_ends">Fin</string>
|
||||||
|
<string name="event_edit_error_end_before_start">La fin commence avant le début</string>
|
||||||
|
<string name="event_edit_error_no_calendar">Aucun calendrier n\'est disponible en écriture</string>
|
||||||
|
<string name="event_edit_save_failed">Impossible d\'enregistrer l\'événement</string>
|
||||||
|
<string name="event_edit_write_denied">Calendula a besoin des droits d\'écriture pour créer un événement</string>
|
||||||
|
<string name="event_edit_more_fields">Plus de champs</string>
|
||||||
|
<string name="event_edit_add">Ajouter</string>
|
||||||
|
<string name="event_edit_add_reminder">Ajouter un rappel</string>
|
||||||
|
<string name="event_edit_remove_reminder">Supprimer le rappel</string>
|
||||||
|
<string name="event_edit_attendees">Invités</string>
|
||||||
|
<string name="event_edit_add_guest">Ajouter un invité</string>
|
||||||
|
<string name="event_edit_add_guest_hint">Ajouter un invité à partir d\'un email…</string>
|
||||||
|
<string name="event_edit_add_guest_from_contacts">Ajouter à partir des contacts</string>
|
||||||
|
<string name="event_edit_location_from_contacts">Sélectionner une adresse dans les contacts</string>
|
||||||
|
<string name="event_edit_remove_guest">Retirer un invité</string>
|
||||||
|
<string name="event_edit_attendee_required">Requis</string>
|
||||||
|
<string name="event_edit_attendee_optional">Optionnel</string>
|
||||||
|
<string name="event_edit_attendees_note_synced">Calendula n\'envoie pas d\'invitations. Votre calendrier par défaut peut envoyer des e-mails aux invités lors de la synchronisation.</string>
|
||||||
|
<string name="event_edit_attendees_note_local">Stocké sur cet appareil. Personne d\'autres n\'y a accès.</string>
|
||||||
|
<string name="event_edit_reminder_custom">Personnaliser</string>
|
||||||
|
<string name="reminder_unit_minutes">minutes</string>
|
||||||
|
<string name="reminder_unit_hours">heures</string>
|
||||||
|
<string name="reminder_unit_days">jours</string>
|
||||||
|
<string name="reminder_unit_weeks">semaines</string>
|
||||||
|
<string name="event_edit_availability">Disponibilité</string>
|
||||||
|
<string name="event_edit_visibility">Visibilité</string>
|
||||||
|
<string name="event_edit_color">Couleur</string>
|
||||||
|
<string name="event_edit_color_default">Couleur du calendrier</string>
|
||||||
|
<string name="event_edit_color_custom">Personnaliser la couleur</string>
|
||||||
|
<string name="event_edit_color_reset">Réinitialiser</string>
|
||||||
|
<string name="event_edit_color_unsupported">Non disponible pour ce calendrier</string>
|
||||||
|
<string name="event_edit_color_unsupported_hint">Ce calendrier ne propose aucun ensemble de couleurs. Vous pouvez autoriser des couleurs personnalisées pour ces calendriers dans les paramètres.</string>
|
||||||
|
<string name="event_edit_color_sync_warning">Ce calendrier pourrait retirer ou remplacer la couleur lors de sa prochaine synchronisation.</string>
|
||||||
|
<string name="event_edit_conflict_title">L\'événement a changé ailleurs</string>
|
||||||
|
<string name="event_edit_conflict_body">Durant l\'édition, cet événement a été altéré - par la synchronisation ou une autre application. Voulez-vous enregistrer ou annuler vos modifications ?</string>
|
||||||
|
<string name="event_edit_conflict_overwrite">Enregistrer mes modifications</string>
|
||||||
|
<string name="event_edit_conflict_overwrite_hint">Seuls les champs que vous modifiez remplacent l\'altération externe</string>
|
||||||
|
<string name="event_edit_conflict_discard">Annuler mes modifications</string>
|
||||||
|
<string name="event_edit_conflict_discard_hint">L’événement reste tel qu’il est maintenant</string>
|
||||||
|
<string name="event_edit_gone_title">Evénement supprimé</string>
|
||||||
|
<string name="event_edit_gone_body">Cet événement a été supprimé entre-temps, par exemple sur un autre appareil. Vos modifications ne peuvent plus être enregistrées.</string>
|
||||||
|
<string name="import_reminder_prompt_title">Appliquer votre rappel par défaut ?</string>
|
||||||
|
<string name="import_reminder_prompt_body_none">Cet événement a été importé sans aucun rappel.</string>
|
||||||
|
<plurals name="import_reminder_prompt_body_existing">
|
||||||
|
<item quantity="one">Cet événement a été importé avec %1$d rappel.</item>
|
||||||
|
<item quantity="many">Cet événement a été importé avec %1$d rappels.</item>
|
||||||
|
<item quantity="other">Cet événement a été importé avec %1$d rappels.</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="import_reminder_prompt_apply">Appliquer par défaut</string>
|
||||||
|
<string name="import_reminder_prompt_keep">Garder tel quel</string>
|
||||||
|
<string name="event_edit_recurrence_none">Pas de répétitions</string>
|
||||||
|
<string name="event_edit_recurrence_custom">Personnaliser</string>
|
||||||
|
<string name="event_edit_recurrence_every">Chaque</string>
|
||||||
|
<string name="recurrence_unit_days">jours</string>
|
||||||
|
<string name="recurrence_unit_weeks">semaines</string>
|
||||||
|
<string name="recurrence_unit_months">mois</string>
|
||||||
|
<string name="recurrence_unit_years">années</string>
|
||||||
|
<string name="event_edit_recurrence_ends">Fin</string>
|
||||||
|
<string name="event_edit_recurrence_end_never">Jamais</string>
|
||||||
|
<string name="event_edit_recurrence_end_until">A une date</string>
|
||||||
|
<string name="event_edit_recurrence_end_count">Après un nombre de fois</string>
|
||||||
|
<string name="event_edit_recurrence_times">fois</string>
|
||||||
|
<string name="event_edit_error_recurrence_ends_before_start">Les répétitions s\'arrêtent avant le début de l\'événement</string>
|
||||||
|
<string name="event_availability_busy">Occupé</string>
|
||||||
|
<string name="event_access_default">Par défaut</string>
|
||||||
|
<string name="event_access_public">Publique</string>
|
||||||
|
<string name="event_detail_all_day">Journée entière</string>
|
||||||
|
<string name="event_detail_calendar">Calendrier</string>
|
||||||
|
<string name="event_detail_calendar_unknown">Calendrier non reconnu</string>
|
||||||
|
<string name="event_detail_location">Lieux</string>
|
||||||
|
<string name="event_detail_description">Description</string>
|
||||||
|
<string name="event_detail_attendees">participants</string>
|
||||||
|
<string name="event_detail_recurrence">Récurrence</string>
|
||||||
|
<string name="event_detail_recurring">Evénement répété</string>
|
||||||
|
<string name="recurrence_daily">Tous les jours</string>
|
||||||
|
<string name="recurrence_weekly">Toutes les semaines</string>
|
||||||
|
<string name="recurrence_monthly">Tous les mois</string>
|
||||||
|
<string name="recurrence_yearly">Tous les ans</string>
|
||||||
|
<string name="recurrence_every_n_days">Tous les %1$d jours</string>
|
||||||
|
<string name="recurrence_every_n_weeks">Toutes les %1$d semaines</string>
|
||||||
|
<string name="recurrence_every_n_months">Tous les %1$d mois</string>
|
||||||
|
<string name="recurrence_every_n_years">Tous les %1$d ans</string>
|
||||||
|
<string name="recurrence_on_days">%1$s sur %2$s</string>
|
||||||
|
<string name="recurrence_with_until">%1$s jusqu\'à %2$s</string>
|
||||||
|
<string name="recurrence_with_count">%1$s, %2$d fois</string>
|
||||||
|
<string name="event_detail_not_found">Cet événement n\'existe plus.</string>
|
||||||
|
<string name="event_attendee_accepted">Accepté</string>
|
||||||
|
<string name="event_attendee_declined">Refusé</string>
|
||||||
|
<string name="event_attendee_tentative">Provisoire</string>
|
||||||
|
<string name="event_attendee_needs_action">Aucune réponse</string>
|
||||||
|
<string name="event_attendee_unknown">—</string>
|
||||||
|
<string name="event_detail_reminders">Rappels</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -6,4 +6,295 @@
|
|||||||
<item>Days</item>
|
<item>Days</item>
|
||||||
<item>Weeks</item>
|
<item>Weeks</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
<string name="app_tagline">Nowoczesny kalendarz.</string>
|
||||||
|
<string name="state_loading">Ładowanie…</string>
|
||||||
|
<string name="state_retry">Spróbuj ponownie</string>
|
||||||
|
<string name="state_failure_unknown">Coś poszło nie tak.</string>
|
||||||
|
<string name="state_failure_permission">Dostęp do kalendarza jest wymagany.</string>
|
||||||
|
<string name="state_failure_permission_action">Przyznaj dostęp</string>
|
||||||
|
<string name="state_failure_no_calendars">Brak skonfigurowanych kalendarzy.</string>
|
||||||
|
<string name="state_failure_no_calendars_action">Otwórz systemowe ustawienia kalendarza</string>
|
||||||
|
<string name="state_failure_provider">Nie udało się kalendarza.</string>
|
||||||
|
<string name="permission_rationale_title">Zobacz wszystkie swoje wydarzenia w pięknej oprawie</string>
|
||||||
|
<string name="permission_rationale_body">Calendula potrzebuje dostępu do Twojego kalendarza, aby wyświetlać wydarzenia i nimi zarządzać. To jedyne, o co prosi na wstępie — i żadne dane nigdy nie opuszczają Twojego urządzenia.</string>
|
||||||
|
<string name="permission_request_button">Przyznaj dostęp do kalendarza</string>
|
||||||
|
<string name="permission_denied_title">Brak dostępu do kalendarza</string>
|
||||||
|
<string name="permission_denied_body">Calendula nie może wyświetlać wydarzeń bez dostępu do kalendarza. Możesz go ponownie przyznać w ustawieniach systemowych.</string>
|
||||||
|
<string name="permission_open_settings_button">Otwórz ustawienia systemowe</string>
|
||||||
|
<string name="permission_retry_button">Spróbuj ponownie</string>
|
||||||
|
<string name="permission_benefit_private_title">Pozostaje na Twoim urządzeniu</string>
|
||||||
|
<string name="permission_benefit_private_body">Twoje kalendarze są odczytywane lokalnie i nigdy nie opuszczają telefonu.</string>
|
||||||
|
<string name="permission_benefit_sync_title">Wszystkie Twoje kalendarze w jednym miejscu</string>
|
||||||
|
<string name="permission_benefit_sync_body">Google, CalDAV, lokalne — wszystko, co jest zsynchronizowane z urządzeniem, po prostu się pojawia.</string>
|
||||||
|
<string name="permission_benefit_privacy_title">Nigdy żadnego śledzenia</string>
|
||||||
|
<string name="permission_benefit_privacy_body">Zero telemetrii, zero analityki, bez reklam.</string>
|
||||||
|
<string name="permission_privacy_footnote">Pozostaje na Twoim urządzeniu - brak uprawnień do internetu</string>
|
||||||
|
<string name="month_prev">Poprzedni miesiąc</string>
|
||||||
|
<string name="month_next">Następny miesiąc</string>
|
||||||
|
<string name="month_today_action">Dzisiaj</string>
|
||||||
|
<string name="month_more_actions">Więcej akcji</string>
|
||||||
|
<string name="month_open_menu">Otwórz menu</string>
|
||||||
|
<string name="month_action_settings">Ustawienia</string>
|
||||||
|
<string name="month_a11y_today_prefix">Dzisiaj</string>
|
||||||
|
<string name="week_today_action">Bieżący tydzień</string>
|
||||||
|
<string name="week_number_label">tydz.</string>
|
||||||
|
<string name="day_today_action">Dzisiaj</string>
|
||||||
|
<string name="event_detail_back">Wstecz</string>
|
||||||
|
<string name="event_detail_edit">Edytuj</string>
|
||||||
|
<string name="event_detail_delete">Usuń</string>
|
||||||
|
<string name="event_detail_share">Udostępnij</string>
|
||||||
|
<string name="event_share_chooser_title">Udostępnij wydarzenie</string>
|
||||||
|
<string name="event_share_failed">Nie udało się udostępnić tego wydarzenia.</string>
|
||||||
|
<string name="event_delete_title">Usunąć wydarzenie?</string>
|
||||||
|
<string name="event_delete_body">Usunięto wydarzenie z Twojego kalendarza i wszystkich zsynchronizowanych urządzeń.</string>
|
||||||
|
<string name="event_delete_recurring_title">Usuń powtarzające się wydarzenie</string>
|
||||||
|
<string name="event_delete_option_occurrence">Tylko to wystąpienie</string>
|
||||||
|
<string name="event_delete_option_following">To i wszystkie następne wystąpienia</string>
|
||||||
|
<string name="event_delete_option_series">Wszystkie wystąpienia w serii</string>
|
||||||
|
<string name="event_edit_recurring_title">Edytuj powtarzające się wydarzenie</string>
|
||||||
|
<string name="event_delete_failed">Nie udało się usunąć tego wydarzenia</string>
|
||||||
|
<string name="event_delete_write_denied">Calendula wymaga uprawnień do zapisu, aby móc usuwać wydarzenia</string>
|
||||||
|
<string name="dialog_cancel">Anuluj</string>
|
||||||
|
<string name="dialog_ok">OK</string>
|
||||||
|
<string name="event_edit_new_title">Nowe wydarzenie</string>
|
||||||
|
<string name="event_edit_close">Zamknij</string>
|
||||||
|
<string name="event_edit_save">Zapisz</string>
|
||||||
|
<string name="event_edit_title_hint">Dodaj tytuł</string>
|
||||||
|
<string name="event_edit_managed_hint">Zarządzane przez „%1$s” — tytuł, data i powtarzanie są synchronizowane z Twoimi kontaktami. Możesz edytować przypomnienia, lokalizację oraz notatki.</string>
|
||||||
|
<string name="event_edit_starts">Początek</string>
|
||||||
|
<string name="event_edit_ends">Koniec</string>
|
||||||
|
<string name="event_edit_error_end_before_start">Koniec wcześniejszy niż początek</string>
|
||||||
|
<string name="event_edit_error_no_calendar">Brak dostępnych kalendarzy z uprawnieniami do zapisu</string>
|
||||||
|
<string name="event_edit_save_failed">Nie udało się zapisać wydarzenia</string>
|
||||||
|
<string name="event_edit_write_denied">Calendula potrzebuje uprawnień do zapisu by tworzyć wydarzenia</string>
|
||||||
|
<string name="event_edit_more_fields">Więcej pól</string>
|
||||||
|
<string name="event_edit_add">Dodaj</string>
|
||||||
|
<string name="event_edit_add_reminder">Dodaj przypomnienie</string>
|
||||||
|
<string name="event_edit_remove_reminder">Usuń przypomnienie</string>
|
||||||
|
<string name="event_edit_attendees">Goście</string>
|
||||||
|
<string name="event_edit_add_guest">Dodaj gościa</string>
|
||||||
|
<string name="event_edit_add_guest_hint">Dodaj gościa poprzez adres e-mail…</string>
|
||||||
|
<string name="event_edit_add_guest_from_contacts">Dodaj z kontaktów</string>
|
||||||
|
<string name="event_edit_location_from_contacts">Wybierz adres z kontaktów</string>
|
||||||
|
<string name="event_edit_remove_guest">Usuń gościa</string>
|
||||||
|
<string name="event_edit_attendee_required">Wymagany</string>
|
||||||
|
<string name="event_edit_attendee_optional">Opcjonalny</string>
|
||||||
|
<string name="event_edit_attendees_note_synced">Calendula nie wysyła zaproszeń. Twoje konto kalendarza może wysyłać e-maile do gości podczas synchronizacji.</string>
|
||||||
|
<string name="event_edit_attendees_note_local">Przechowywane na urządzeniu. Nikt nie jest powiadamiany.</string>
|
||||||
|
<string name="event_edit_reminder_custom">Niestandardowy</string>
|
||||||
|
<string name="reminder_unit_minutes">minuty</string>
|
||||||
|
<string name="reminder_unit_hours">godziny</string>
|
||||||
|
<string name="reminder_unit_days">dni</string>
|
||||||
|
<string name="reminder_unit_weeks">tygodnie</string>
|
||||||
|
<string name="event_edit_availability">Dostępność</string>
|
||||||
|
<string name="event_edit_visibility">Widoczność</string>
|
||||||
|
<string name="event_edit_color">Kolor</string>
|
||||||
|
<string name="event_edit_color_default">Kolor kalendarza</string>
|
||||||
|
<string name="event_edit_color_custom">Niestandardowy kolor</string>
|
||||||
|
<string name="event_edit_color_reset">Resetuj</string>
|
||||||
|
<string name="event_edit_color_unsupported">Niedostępne dla tego kalendarza</string>
|
||||||
|
<string name="event_edit_color_unsupported_hint">Ten kalendarz nie udostępnia zestawu kolorów. Możesz zezwolić na niestandardowe kolory dla takich kalendarzy w Ustawieniach.</string>
|
||||||
|
<string name="event_edit_color_sync_warning">Ten kalendarz może usunąć lub nadpisać kolor przy następnej synchronizacji.</string>
|
||||||
|
<string name="event_edit_conflict_title">Wydarzenie zostało zmienione w innym miejscu</string>
|
||||||
|
<string name="event_edit_conflict_body">Podczas edycji to wydarzenie zostało zmienione przez synchronizację lub inną aplikację. Co ma się stać z Twoimi zmianami?</string>
|
||||||
|
<string name="event_edit_conflict_overwrite">Zapisz moje zmiany</string>
|
||||||
|
<string name="event_edit_conflict_overwrite_hint">Tylko edytowane przez Ciebie pola nadpiszą zmiany zewnętrzne</string>
|
||||||
|
<string name="event_edit_conflict_discard">Odrzuć moje zmiany</string>
|
||||||
|
<string name="event_edit_conflict_discard_hint">Wydarzenie pozostaje bez zmian</string>
|
||||||
|
<string name="event_edit_gone_title">Wydarzenie usunięte</string>
|
||||||
|
<string name="event_edit_gone_body">To wydarzenie zostało w międzyczasie usunięte, na przykład na innym urządzeniu. Twoje zmiany nie mogą już zostać zapisane.</string>
|
||||||
|
<string name="import_reminder_prompt_title">Użyć domyślnego przypomnienia?</string>
|
||||||
|
<string name="import_reminder_prompt_body_none">To wydarzenie zostało zaimportowane bez żadnych przypomnień.</string>
|
||||||
|
<plurals name="import_reminder_prompt_body_existing">
|
||||||
|
<item quantity="one">To wydarzenie zostało zaimportowane z %1$d przypomnieniem.</item>
|
||||||
|
<item quantity="few">To wydarzenie zostało zaimportowane z %1$d przypomnieniami.</item>
|
||||||
|
<item quantity="many">To wydarzenie zostało zaimportowane z %1$d przypomnieniami.</item>
|
||||||
|
<item quantity="other">To wydarzenie zostało zaimportowane z %1$d przypomnieniami.</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="import_reminder_prompt_apply">Użyj domyślnego</string>
|
||||||
|
<string name="import_reminder_prompt_keep">Pozostaw bez zmian</string>
|
||||||
|
<string name="event_edit_recurrence_none">Nie powtarza się</string>
|
||||||
|
<string name="event_edit_recurrence_custom">Własne</string>
|
||||||
|
<string name="event_edit_recurrence_every">Co</string>
|
||||||
|
<string name="recurrence_unit_days">dni</string>
|
||||||
|
<string name="recurrence_unit_weeks">tygodnie</string>
|
||||||
|
<string name="recurrence_unit_months">miesiące</string>
|
||||||
|
<string name="recurrence_unit_years">lata</string>
|
||||||
|
<string name="event_edit_recurrence_ends">Koniec</string>
|
||||||
|
<string name="event_edit_recurrence_end_never">Nigdy</string>
|
||||||
|
<string name="event_edit_recurrence_end_until">Wybrana data</string>
|
||||||
|
<string name="event_edit_recurrence_end_count">Po wybranej liczbie powtórzeń</string>
|
||||||
|
<string name="event_edit_recurrence_times">razy</string>
|
||||||
|
<string name="event_edit_error_recurrence_ends_before_start">Koniec powtarzania przed początkiem wydarzenia</string>
|
||||||
|
<string name="event_availability_busy">Zajęty</string>
|
||||||
|
<string name="event_access_default">Domyślny</string>
|
||||||
|
<string name="event_access_public">Publiczny</string>
|
||||||
|
<string name="event_detail_all_day">Cały dzień</string>
|
||||||
|
<string name="event_detail_calendar">Kalendarz</string>
|
||||||
|
<string name="event_detail_calendar_unknown">Nieznany kalendarz</string>
|
||||||
|
<string name="event_detail_location">Lokalizacja</string>
|
||||||
|
<string name="event_detail_description">Opis</string>
|
||||||
|
<string name="event_detail_attendees">Uczestnicy</string>
|
||||||
|
<string name="event_detail_recurrence">Powtarzanie</string>
|
||||||
|
<string name="event_detail_recurring">Powtarzające się wydarzenie</string>
|
||||||
|
<string name="recurrence_daily">Codziennie</string>
|
||||||
|
<string name="recurrence_weekly">Co tydzień</string>
|
||||||
|
<string name="recurrence_monthly">Co miesiąc</string>
|
||||||
|
<string name="recurrence_yearly">Co rok</string>
|
||||||
|
<string name="recurrence_every_n_days">Co %1$d dni</string>
|
||||||
|
<string name="recurrence_every_n_weeks">Co %1$d tygodni</string>
|
||||||
|
<string name="recurrence_every_n_months">Co %1$d miesięcy</string>
|
||||||
|
<string name="recurrence_every_n_years">Co %1$d lata</string>
|
||||||
|
<string name="recurrence_on_days">%1$s w %2$s</string>
|
||||||
|
<string name="recurrence_with_until">%1$s do %2$s</string>
|
||||||
|
<string name="recurrence_with_count">%1$s, %2$d razy</string>
|
||||||
|
<string name="event_detail_not_found">To wydarzenie już nie istnieje.</string>
|
||||||
|
<string name="event_attendee_accepted">Zaakceptowano</string>
|
||||||
|
<string name="event_attendee_declined">Odrzucono</string>
|
||||||
|
<string name="event_attendee_tentative">Niepewny</string>
|
||||||
|
<string name="event_attendee_needs_action">Brak odpowiedzi</string>
|
||||||
|
<string name="event_attendee_unknown">—</string>
|
||||||
|
<string name="event_detail_reminders">Przypomnienia</string>
|
||||||
|
<string name="event_detail_timezone">Strefa czasowa</string>
|
||||||
|
<string name="event_status_tentative">Niepewne</string>
|
||||||
|
<string name="event_status_cancelled">Odwołane</string>
|
||||||
|
<string name="event_availability_free">Dostępny</string>
|
||||||
|
<string name="event_access_private">Prywatne</string>
|
||||||
|
<string name="event_access_confidential">Poufne</string>
|
||||||
|
<string name="event_attendee_organizer">Organizator</string>
|
||||||
|
<string name="event_attendee_optional">Opcjonalny</string>
|
||||||
|
<string name="event_attendee_resource">Zasób</string>
|
||||||
|
<string name="event_detail_self_response">Twoja odpowiedź: %1$s</string>
|
||||||
|
<string name="reminder_at_time">W momencie wydarzenia</string>
|
||||||
|
<string name="reminder_default">Domyślne przypomnienie</string>
|
||||||
|
<plurals name="reminder_minutes">
|
||||||
|
<item quantity="one">%d minuta przed</item>
|
||||||
|
<item quantity="few">%d minuty przed</item>
|
||||||
|
<item quantity="many">%d minut przed</item>
|
||||||
|
<item quantity="other">%d minut przed</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="reminder_hours">
|
||||||
|
<item quantity="one">%d godzina przed</item>
|
||||||
|
<item quantity="few">%d godziny przed</item>
|
||||||
|
<item quantity="many">%d godzin przed</item>
|
||||||
|
<item quantity="other">%d godzin przed</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="reminder_days">
|
||||||
|
<item quantity="one">%d dzień przed</item>
|
||||||
|
<item quantity="few">%d dni przed</item>
|
||||||
|
<item quantity="many">%d dni przed</item>
|
||||||
|
<item quantity="other">%d dni przed</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="reminder_weeks">
|
||||||
|
<item quantity="one">%d tydzień przed</item>
|
||||||
|
<item quantity="few">%d tygodnie przed</item>
|
||||||
|
<item quantity="many">%d tygodni przed</item>
|
||||||
|
<item quantity="other">%d tygodni przed</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="duration_minutes">
|
||||||
|
<item quantity="one">%d minuta</item>
|
||||||
|
<item quantity="few">%d minuty</item>
|
||||||
|
<item quantity="many">%d minut</item>
|
||||||
|
<item quantity="other">%d minut</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="duration_hours">
|
||||||
|
<item quantity="one">%d godzina</item>
|
||||||
|
<item quantity="few">%d godziny</item>
|
||||||
|
<item quantity="many">%d godzin</item>
|
||||||
|
<item quantity="other">%d godzin</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="duration_days">
|
||||||
|
<item quantity="one">%d dzień</item>
|
||||||
|
<item quantity="few">%d dni</item>
|
||||||
|
<item quantity="many">%d dni</item>
|
||||||
|
<item quantity="other">%d dni</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="duration_weeks">
|
||||||
|
<item quantity="one">%d tydzień</item>
|
||||||
|
<item quantity="few">%d tygodnie</item>
|
||||||
|
<item quantity="many">%d tygodni</item>
|
||||||
|
<item quantity="other">%d tygodni</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="event_untitled">(Bez tytułu)</string>
|
||||||
|
<string name="reminder_channel_name">Przypomnienia o wydarzeniach</string>
|
||||||
|
<string name="reminder_channel_description">Powiadomienia w czasie przypomnień o Twoich wydarzeniach</string>
|
||||||
|
<string name="reminder_onboarding_title">Nigdy nie przegap wydarzenia</string>
|
||||||
|
<string name="reminder_onboarding_body">Android sam nie wyświetla przypomnień o wydarzeniach — musi to robić aplikacja kalendarza. Pozwól na to Calenduli.</string>
|
||||||
|
<string name="reminder_benefit_delivery_title">Niezawodne przypomnienia</string>
|
||||||
|
<string name="reminder_benefit_delivery_body">Każde przypomnienie o Twoich wydarzeniach przychodzi jako powiadomienie, dokładnie na czas.</string>
|
||||||
|
<string name="reminder_benefit_duplicates_title">Używasz innej aplikacji kalendarza?</string>
|
||||||
|
<string name="reminder_benefit_duplicates_body">Jeśli inna aplikacja również wysyła przypomnienia, będą one widoczne podwójnie - wyłącz je tam lub tutaj.</string>
|
||||||
|
<string name="reminder_benefit_reversible_title">Możesz to zmienić w dowolnej chwili</string>
|
||||||
|
<string name="reminder_benefit_reversible_body">Przełącznik znajduje się w Ustawieniach, w sekcji Powiadomienia.</string>
|
||||||
|
<string name="reminder_onboarding_enable_button">Włącz przypomnienia</string>
|
||||||
|
<string name="reminder_onboarding_skip_button">Nie teraz</string>
|
||||||
|
<string name="reminder_action_snooze">Drzemka</string>
|
||||||
|
<string name="reminder_action_dismiss">Odrzuć</string>
|
||||||
|
<string name="view_month">Miesiąc</string>
|
||||||
|
<string name="view_week">Tydzień</string>
|
||||||
|
<string name="view_day">Dzień</string>
|
||||||
|
<string name="view_agenda">Agenda</string>
|
||||||
|
<string name="view_section">Widok</string>
|
||||||
|
<string name="drawer_jump_to_date">Skocz do daty</string>
|
||||||
|
<string name="agenda_today_action">Dzisiaj</string>
|
||||||
|
<string name="agenda_header_today">Dzisiaj</string>
|
||||||
|
<string name="agenda_header_tomorrow">Jutro</string>
|
||||||
|
<string name="agenda_empty_title">Jesteś na bieżąco</string>
|
||||||
|
<string name="search_action">Szukaj</string>
|
||||||
|
<string name="search_hint">Szukaj wydarzeń</string>
|
||||||
|
<string name="search_back">Wstecz</string>
|
||||||
|
<string name="search_clear">Wyczyść</string>
|
||||||
|
<string name="search_idle_hint">Przeszukuj swoje wydarzenia według tytułu, lokalizacji lub notatek.</string>
|
||||||
|
<string name="search_empty">Brak wydarzeń pasujących do „%1$s”.</string>
|
||||||
|
<string name="widget_agenda_title">Nadchodzące wydarzenia</string>
|
||||||
|
<string name="widget_agenda_label">Agenda Calendula</string>
|
||||||
|
<string name="widget_month_label">Miesiąc Calendula</string>
|
||||||
|
<string name="widget_refresh">Odśwież</string>
|
||||||
|
<string name="widget_new_event">Nowe wydarzenie</string>
|
||||||
|
<string name="widget_needs_permission">Otwórz Calendula aby przyznać dostęp do kalendarza</string>
|
||||||
|
<string name="widget_prev_month">Poprzedni miesiąc</string>
|
||||||
|
<string name="widget_next_month">Następny miesiąc</string>
|
||||||
|
<string name="widget_today">Dzisiaj</string>
|
||||||
|
<string name="filter_title">Kalendarze</string>
|
||||||
|
<string name="settings_title">Ustawienia</string>
|
||||||
|
<string name="settings_back">Wstecz</string>
|
||||||
|
<string name="back">Wstecz</string>
|
||||||
|
<string name="settings_section_appearance">Wygląd</string>
|
||||||
|
<string name="settings_theme">Motyw</string>
|
||||||
|
<string name="settings_theme_system">Systemowy</string>
|
||||||
|
<string name="settings_theme_light">Jasny</string>
|
||||||
|
<string name="settings_theme_dark">Ciemny</string>
|
||||||
|
<string name="settings_default_view">Domyślny widok</string>
|
||||||
|
<string name="settings_dynamic_color">Dynamiczne kolory</string>
|
||||||
|
<string name="settings_dynamic_color_unavailable">Wymaga Androida 12 lub nowszego</string>
|
||||||
|
<string name="settings_font_headings">Czcionka nagłówków</string>
|
||||||
|
<string name="settings_font_body">Czcionka tekstu</string>
|
||||||
|
<string name="settings_font_system">Domyślna systemu</string>
|
||||||
|
<string name="font_atkinson_hyperlegible">Atkinson Hyperlegible</string>
|
||||||
|
<string name="font_lora">Lora</string>
|
||||||
|
<string name="font_jetbrains_mono">JetBrains Mono</string>
|
||||||
|
<string name="settings_font_choose_file">Wybierz plik…</string>
|
||||||
|
<string name="settings_font_custom_selected">Niestandardowa czcionka</string>
|
||||||
|
<string name="app_name">Calendula</string>
|
||||||
|
<string name="settings_font_import_failed">Nie udało się odczytać czcionki z pliku</string>
|
||||||
|
<string name="settings_week_start">Pierwszy dzień tygodnia</string>
|
||||||
|
<string name="settings_week_start_auto">Automatycznie</string>
|
||||||
|
<string name="settings_week_numbers">Numery tygodni</string>
|
||||||
|
<string name="settings_week_numbers_summary">Pokaż numery tygodni w widoku miesiąca</string>
|
||||||
|
<string name="settings_time_format">Format czasu</string>
|
||||||
|
<string name="settings_time_format_auto">Automatyczny</string>
|
||||||
|
<string name="settings_time_format_12h">12-godzinny (2:00 PM)</string>
|
||||||
|
<string name="settings_time_format_24h">24-godzinny (14:00)</string>
|
||||||
|
<string name="settings_hour_lines">Linie godzin</string>
|
||||||
|
<string name="settings_hour_lines_summary">Pokaż linię oddzielającą przy każdej godzinie w widoku tygodnia i dnia</string>
|
||||||
|
<string name="settings_dim_completed">Przyciemnij zakończone wydarzenia</string>
|
||||||
|
<string name="settings_dim_completed_summary">Przyciemnij wydarzenia, które już się zakończyły w widoku miesiąca i tygodnia</string>
|
||||||
|
<string name="settings_past_events">Przeszłe wydarzenia</string>
|
||||||
|
<string name="settings_past_events_show">Pokaż</string>
|
||||||
|
<string name="settings_past_events_dim">Przyciemnij</string>
|
||||||
|
<string name="settings_past_events_hide">Ukryj</string>
|
||||||
|
<string name="settings_agenda_header">Agenda</string>
|
||||||
|
<string name="settings_agenda_range">Zakres agendy</string>
|
||||||
|
<string name="settings_agenda_range_hint">Jak daleko w przód wyświetlane są wydarzenia na ekranie Agendy.</string>
|
||||||
|
<string name="settings_agenda_widget_range">Zakres widżetu agendy</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -293,6 +293,8 @@
|
|||||||
<string name="settings_default_view">Default view</string>
|
<string name="settings_default_view">Default view</string>
|
||||||
<string name="settings_dynamic_color">Dynamic colour</string>
|
<string name="settings_dynamic_color">Dynamic colour</string>
|
||||||
<string name="settings_dynamic_color_unavailable">Requires Android 12 or newer</string>
|
<string name="settings_dynamic_color_unavailable">Requires Android 12 or newer</string>
|
||||||
|
<string name="settings_soften_colors">Soften calendar colours</string>
|
||||||
|
<string name="settings_soften_colors_summary">Tone calendar and event colours down to fit the theme. Turn off to show the raw colours from the calendar source.</string>
|
||||||
<string name="settings_font_headings">Headings font</string>
|
<string name="settings_font_headings">Headings font</string>
|
||||||
<string name="settings_font_body">Body font</string>
|
<string name="settings_font_body">Body font</string>
|
||||||
<string name="settings_font_system">System default</string>
|
<string name="settings_font_system">System default</string>
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
<locale android:name="en" />
|
<locale android:name="en" />
|
||||||
<locale android:name="de" />
|
<locale android:name="de" />
|
||||||
<locale android:name="es" />
|
<locale android:name="es" />
|
||||||
|
<locale android:name="fr" />
|
||||||
<locale android:name="it" />
|
<locale android:name="it" />
|
||||||
|
<locale android:name="pl" />
|
||||||
<locale android:name="zh-CN" />
|
<locale android:name="zh-CN" />
|
||||||
</locale-config>
|
</locale-config>
|
||||||
|
|||||||
74
fastlane/metadata/android/en-US/changelogs/21500.txt
Normal file
74
fastlane/metadata/android/en-US/changelogs/21500.txt
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
### 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
|
||||||
|
after exactly the delay you want instead of only a preset one ([#40]).
|
||||||
|
- Move an event to another calendar. When editing an existing event, the
|
||||||
|
calendar row is now tappable — pick a different calendar and saving moves the
|
||||||
|
event across, instead of having to delete it and recreate it elsewhere.
|
||||||
|
Recurring series move as a whole, keeping their individually-edited and
|
||||||
|
cancelled occurrences, and any reminders and guests come along too. A calendar
|
||||||
|
can't simply be reassigned underneath an event, so Calendula recreates it on
|
||||||
|
the target and removes the original — the same approach other calendar apps
|
||||||
|
take. Thanks to @prismplex for the suggestion ([#39]).
|
||||||
|
- Open an event straight into the edit form from another app. Calendula already
|
||||||
|
answered the "new event" and "open this event" hand-offs from other apps and
|
||||||
|
widgets; it now also answers the "edit this event" one, so an assistant, task
|
||||||
|
app, or widget can send an existing event to Calendula and land on its edit
|
||||||
|
screen rather than the read-only details. A hand-off with no event attached
|
||||||
|
opens the same prefilled create form as "new event". Calendula also recognises
|
||||||
|
a couple more file labels the same calendar data arrives under (`.vcs`
|
||||||
|
vCalendar files and the `application/ics` type), so opening or sharing those
|
||||||
|
into Calendula works too.
|
||||||
|
- Keep today at the top of the agenda. A new **Always show today** setting
|
||||||
|
(Settings → Agenda, on by default) anchors today as the first entry in both the
|
||||||
|
Agenda screen and its home-screen widget even once nothing is left today —
|
||||||
|
under today's header a "No more events today" note appears — so the first
|
||||||
|
events you see are clearly today's rather than a future day's. Turn it off to
|
||||||
|
keep the agenda purely upcoming. Thanks to @ptab for the suggestion ([#35]).
|
||||||
|
- Duplicate an event. The event details now carry a **Duplicate** action that
|
||||||
|
opens the editor pre-filled with a copy of the event as a new, unsaved one, so
|
||||||
|
a one-off like a shift or an appointment can be recreated by just changing the
|
||||||
|
day and time instead of re-typing every field. The copy keeps the original's
|
||||||
|
time, and its title, location, notes, colour, guests and reminders come along;
|
||||||
|
it's saved as its own single event (any repeat is left off — add one in the
|
||||||
|
editor if you want it). Duplicate works from read-only calendars too, dropping
|
||||||
|
the copy into a writable one. Thanks to @internet-rando for the suggestion
|
||||||
|
([#52]).
|
||||||
|
- French and Polish, in early form. Calendula has started speaking French and
|
||||||
|
Polish, both contributed as community translations through
|
||||||
|
[Calendula's Weblate](https://weblate.dev.jeanlucmakiola.de/projects/calendula/).
|
||||||
|
They are partway there, so untranslated parts still show in English until they
|
||||||
|
fill out — you can already pick either under Settings → Language or in Android's
|
||||||
|
per-app language settings. Thanks to Thomas Tref (French) and Bazyli Cyran
|
||||||
|
(Polish) for getting them started; help finishing them is very welcome.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Reminders for events on another day no longer read as if they were today. A
|
||||||
|
reminder fired ahead of time — say, the day before — used to show only the
|
||||||
|
event's time, making it look like it was happening now. The notification now
|
||||||
|
says which day: **Tomorrow** or **Yesterday**, the weekday for another day this
|
||||||
|
week, or the date for anything further out. Thanks to @moonj for the report
|
||||||
|
([#46]).
|
||||||
|
- Agenda dates now read in your locale's format. The agenda's range bar and its
|
||||||
|
day headers used a fixed day-month-year layout — and the range span even mixed
|
||||||
|
two orders (e.g. "15 Jul – Aug 13, 2026") — instead of following your language's
|
||||||
|
conventions. Dates across the agenda and its widget now use your locale's own
|
||||||
|
field order, matching the rest of the app. The range bar also no longer repeats
|
||||||
|
the range's name from the selector button beside it, showing just the dates.
|
||||||
|
- Calendar gutters line up with the menu button. The Month view's week-number
|
||||||
|
column, and the Week and Day views' hour labels, sat a few pixels left of the
|
||||||
|
hamburger menu above them; they now line up with it. In Month view the day
|
||||||
|
cells also sit squarely under their weekday letters.
|
||||||
|
|
||||||
Reference in New Issue
Block a user