Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdcf2b3823 | ||
|
|
5c20c5f6eb | ||
|
|
c4bb7ae0b2 | ||
|
|
0c985a280c | ||
|
|
17ebb9fd42 | ||
|
|
cadc2b14db | ||
|
|
1fdca903be | ||
|
|
5e7658aa80 | ||
|
|
bea31032fb | ||
|
|
f7c1dbdbc4 | ||
|
|
a1ed010963 |
@@ -5,6 +5,24 @@ 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/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.20.4] — 2026-09-20
|
||||
|
||||
### Fixed
|
||||
- **The app no longer reopens the event you last came in on.** Opening an event
|
||||
from a reminder or a home-screen widget made the app return to that same
|
||||
event on every later launch — and show an error screen once the event had
|
||||
been deleted. Android hands an app the launch it was started with again each
|
||||
time it is rebuilt, and Calendula was acting on it every time instead of
|
||||
once ([#309]).
|
||||
- **"Today" works in the agenda again.** Scrolling a few days ahead and tapping
|
||||
today did nothing, because the agenda is browsed by scrolling rather than by
|
||||
moving its date, and the button only moved the date. It now brings the list
|
||||
back to today, and it stays offered while the list is scrolled away — before,
|
||||
it was hidden in exactly the situation it is for ([#305]).
|
||||
- **Calendula is filed under "Calendar & Agenda" in the self-hosted repo.**
|
||||
Browsing by category in an F-Droid client set to prefer that repo listed the
|
||||
app under "Time" instead ([#294]).
|
||||
|
||||
## [2.20.3] — 2026-09-12
|
||||
|
||||
### Fixed
|
||||
@@ -1693,3 +1711,6 @@ automatically, with zero telemetry and no internet permission.
|
||||
[#239]: https://codeberg.org/jlmakiola/calendula/issues/239
|
||||
[#297]: https://codeberg.org/jlmakiola/calendula/issues/297
|
||||
[#298]: https://codeberg.org/jlmakiola/calendula/issues/298
|
||||
[#309]: https://codeberg.org/jlmakiola/calendula/issues/309
|
||||
[#305]: https://codeberg.org/jlmakiola/calendula/issues/305
|
||||
[#294]: https://codeberg.org/jlmakiola/calendula/issues/294
|
||||
|
||||
@@ -28,8 +28,8 @@ android {
|
||||
// which builds this version and then creates the matching vX.Y.Z tag +
|
||||
// release itself (versionCode is pinned to MAJOR*10000 + MINOR*100 +
|
||||
// PATCH from versionName, e.g. 2.7.2 -> 20702). See docs/RELEASING.md.
|
||||
versionCode = 22003
|
||||
versionName = "2.20.3"
|
||||
versionCode = 22004
|
||||
versionName = "2.20.4"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -113,11 +113,26 @@ class MainActivity : AppCompatActivity() {
|
||||
systemBarsDark = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
|
||||
Configuration.UI_MODE_NIGHT_YES
|
||||
applyEdgeToEdge()
|
||||
requestedDetailKey = intent.detailKeyOrNull() ?: intent.viewEventKeyOrNull()
|
||||
// Android hands the intent that started the task back to onCreate every
|
||||
// time the activity is recreated — on a rotation, or on a restart after
|
||||
// the system reclaimed the process. Re-running a launch that opens one
|
||||
// specific event is what made a single reminder or widget tap re-open
|
||||
// that event on every later launch, an error screen once the event had
|
||||
// been deleted (#309); the user left it long ago, and the activity
|
||||
// restores where they actually were along with its saved state.
|
||||
//
|
||||
// Only those channels are held back. An .ics import or a prefilled
|
||||
// create form keeps no saved state of its own, so re-reading the intent
|
||||
// is what carries one through a recreation.
|
||||
val replaying = savedInstanceState != null || intent.isRelaunch()
|
||||
if (!replaying) {
|
||||
requestedDetailKey = intent.detailKeyOrNull() ?: intent.viewEventKeyOrNull()
|
||||
requestedEditKey = intent.editEventKeyOrNull()
|
||||
}
|
||||
requestedNav = intent.navRequestOrNull()
|
||||
?.takeUnless { replaying && it is WidgetNavRequest.OpenEvent }
|
||||
requestedImportUri = intent.importUriOrNull()
|
||||
requestedInsert = intent.insertRequestOrNull()
|
||||
requestedEditKey = intent.editEventKeyOrNull()
|
||||
if (CrashReporter.shouldPrompt(this)) pendingCrashReport = CrashReporter.pendingReport(this)
|
||||
setContent {
|
||||
// One activity-scoped SettingsViewModel drives both the theme here
|
||||
@@ -242,6 +257,16 @@ class MainActivity : AppCompatActivity() {
|
||||
intent.editEventKeyOrNull()?.let { requestedEditKey = it }
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this launch is the task being resumed rather than a fresh
|
||||
* delivery — Android sets the flag when an activity is started from the
|
||||
* recents list, where the intent it carries is the one the task was started
|
||||
* with however long ago. `onNewIntent` is the channel a real new request
|
||||
* arrives on while the task lives, so nothing is lost by ignoring these.
|
||||
*/
|
||||
private fun Intent.isRelaunch(): Boolean =
|
||||
flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY != 0
|
||||
|
||||
/**
|
||||
* The `.ics` Uri an external app asked us to open (file manager `ACTION_VIEW`)
|
||||
* or share into us (`ACTION_SEND`). Restricted to content/file schemes so the
|
||||
|
||||
@@ -13,7 +13,9 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Coffee
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
@@ -32,6 +34,8 @@ import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberDrawerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -106,6 +110,27 @@ fun AgendaScreen(
|
||||
}
|
||||
val successState = state as? AgendaUiState.Success
|
||||
|
||||
// The agenda window always starts at the anchor, so moving through it is a
|
||||
// list scroll, not an anchor change — "today" has to bring the list back to
|
||||
// the top as well, and stay offered while it is scrolled away (#305).
|
||||
val listState = rememberLazyListState()
|
||||
val scrolledAway by remember {
|
||||
derivedStateOf {
|
||||
listState.firstVisibleItemIndex > 0 || listState.firstVisibleItemScrollOffset > 0
|
||||
}
|
||||
}
|
||||
// A new window is rendered from its first day, so the list belongs at the
|
||||
// top. Keyed on the anchor the days arrived with, not the one just
|
||||
// requested: scrolling before the new rows compose leaves the list keyed to
|
||||
// a row that reappears further down, and it follows it there.
|
||||
LaunchedEffect(successState?.anchor) { listState.scrollToItem(0) }
|
||||
val jumpToToday = {
|
||||
// Off today, moving the anchor is what resets the list (above); on today
|
||||
// the window doesn't change, so the scroll back is the whole action.
|
||||
if (isOnToday) scope.launch { listState.animateScrollToItem(0) }
|
||||
viewModel.goToToday()
|
||||
}
|
||||
|
||||
ModalNavigationDrawer(
|
||||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
@@ -139,14 +164,15 @@ fun AgendaScreen(
|
||||
onOpenDrawer = { scope.launch { drawerState.open() } },
|
||||
onOpenSearch = onOpenSearch,
|
||||
showTodayButton = todayInToolbar,
|
||||
onToday = viewModel::goToToday,
|
||||
onToday = jumpToToday,
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
CalendarFabColumn(
|
||||
todayVisible = !isOnToday && !todayInToolbar,
|
||||
todayVisible = (!isOnToday || (scrolledAway && successState != null)) &&
|
||||
!todayInToolbar,
|
||||
todayText = stringResource(R.string.agenda_today_action),
|
||||
onToday = viewModel::goToToday,
|
||||
onToday = jumpToToday,
|
||||
onCreate = { onCreateEvent(anchor, null) },
|
||||
)
|
||||
},
|
||||
@@ -195,6 +221,7 @@ fun AgendaScreen(
|
||||
state = state,
|
||||
pastDisplay = pastDisplay,
|
||||
showToday = showToday,
|
||||
listState = listState,
|
||||
onRetry = viewModel::goToToday,
|
||||
onEventClick = onEventClick,
|
||||
onOpenDay = onOpenDay,
|
||||
@@ -298,6 +325,7 @@ internal fun AgendaContent(
|
||||
onEventClick: (EventInstance) -> Unit,
|
||||
onOpenDay: (LocalDate) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
listState: LazyListState = rememberLazyListState(),
|
||||
) {
|
||||
when (state) {
|
||||
AgendaUiState.Loading -> Box(modifier)
|
||||
@@ -326,12 +354,16 @@ internal fun AgendaContent(
|
||||
enabled = showToday && state.anchor == state.today,
|
||||
)
|
||||
if (days.isEmpty()) {
|
||||
// Nothing to scroll: drop the position a previous list left, so
|
||||
// the today FAB doesn't linger over an empty screen.
|
||||
LaunchedEffect(Unit) { listState.scrollToItem(0) }
|
||||
AgendaEmpty(modifier)
|
||||
} else {
|
||||
AgendaList(
|
||||
days = days,
|
||||
today = state.today,
|
||||
zone = state.zone,
|
||||
listState = listState,
|
||||
dimPast = pastDisplay == PastEventDisplay.DIM,
|
||||
now = now,
|
||||
onEventClick = onEventClick,
|
||||
@@ -349,6 +381,7 @@ private fun AgendaList(
|
||||
days: List<AgendaDay>,
|
||||
today: LocalDate,
|
||||
zone: TimeZone,
|
||||
listState: LazyListState,
|
||||
dimPast: Boolean,
|
||||
now: Instant,
|
||||
onEventClick: (EventInstance) -> Unit,
|
||||
@@ -357,6 +390,7 @@ private fun AgendaList(
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
state = listState,
|
||||
// Bottom inset clears the FAB stack so the last row stays tappable.
|
||||
contentPadding = PaddingValues(top = 8.dp, bottom = 96.dp),
|
||||
) {
|
||||
|
||||
@@ -19,7 +19,6 @@ import androidx.compose.ui.input.pointer.PointerEventPass
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@@ -197,18 +196,26 @@ fun rememberTimelinePinchZoom(
|
||||
* names, jumping the whole column as a pinch drifts across each half pixel.
|
||||
* Pinning the hour to whole pixels keeps every part of the timeline on one grid.
|
||||
*
|
||||
* The bounds themselves are pulled onto that grid too, each in the direction
|
||||
* that keeps its own promise — up for the fill floor, so no dead space opens
|
||||
* under midnight, down for the ceiling. A fractional bound would be a height the
|
||||
* pinch can be held against but never actually land on, and the difference feeds
|
||||
* the focal anchor a scroll correction on every frame the fingers sit still.
|
||||
* The ceiling is pulled onto that grid too, downwards, so it stays a height the
|
||||
* pinch can actually land on.
|
||||
*
|
||||
* [fillPx] is deliberately *not* rounded (#290). It is the one height the whole
|
||||
* day exactly fills the viewport at, and it is the same value
|
||||
* [TimelineScale.FitDay] resolves to — rounding it up by the fraction of a pixel
|
||||
* that 24 hours don't divide the viewport into leaves the timeline a pixel per
|
||||
* hour taller than its own viewport, so a pinched-all-the-way-out day still
|
||||
* scrolls a hair and bounces off Android's overscroll stretch, while the
|
||||
* identical FitDay preset sits still. Being the clamp result rather than a bound
|
||||
* the gesture is merely held against, it is a height the pinch does land on: the
|
||||
* next frame reads it back unchanged and the focal anchor is handed nothing to
|
||||
* correct.
|
||||
*/
|
||||
internal fun pinchedHourHeightPx(target: Float, fillPx: Float, maxPx: Float): Float =
|
||||
// Filling the viewport wins over the ceiling: on a screen tall enough for
|
||||
// the two to disagree, dead space is the worse of the two failures.
|
||||
target.roundToInt().toFloat()
|
||||
.coerceAtMost(floor(maxPx))
|
||||
.coerceAtLeast(ceil(fillPx))
|
||||
.coerceAtLeast(fillPx)
|
||||
|
||||
/**
|
||||
* The scroll offset that keeps the moment under [centroidY] under it after the
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<string name="event_edit_timezone_search">Vyhledat časové pásmo</string>
|
||||
<string name="event_edit_timezone_recent">Nedávné</string>
|
||||
<string name="event_edit_timezone_all">Všechna časová pásma</string>
|
||||
<string name="event_edit_timezone_none">Žádné časové pásmo neodpovídá “%1$s”</string>
|
||||
<string name="event_edit_timezone_none">Žádné časové pásmo neodpovídá „%1$s“</string>
|
||||
<string name="event_edit_timezone_local_time">%1$s vašeho času</string>
|
||||
<string name="event_edit_color">Barva</string>
|
||||
<string name="event_edit_color_default">Barva kalendáře</string>
|
||||
@@ -115,7 +115,7 @@
|
||||
<string name="event_edit_color_unsupported">Pro tento kalendář není k dispozici</string>
|
||||
<string name="event_edit_color_unsupported_hint">Tento kalendář nemá žádnou sadu barev. Vlastní barvy kalendářů můžete povolit v nastavení.</string>
|
||||
<string name="event_edit_color_sync_warning">Tato barva může být během příští synchronizace kalendáře ztracena nebo přepsána.</string>
|
||||
<string name="event_edit_conflict_title">Ke změně události došlo na jiném místě.</string>
|
||||
<string name="event_edit_conflict_title">Ke změně události došlo na jiném místě</string>
|
||||
<string name="event_edit_conflict_body">Během úpravy došlo ke změně události — v rámci synchronizace nebo pomocí jiné aplikací. Co se má stát s vašimi změnami?</string>
|
||||
<string name="event_edit_conflict_overwrite">Uložit změny</string>
|
||||
<string name="event_edit_conflict_overwrite_hint">Pouze u upravených polí dojde k přepsání vnějšího zdroje</string>
|
||||
@@ -169,7 +169,7 @@
|
||||
<string name="recurrence_every_n_months">Každý %1$d. měsíc</string>
|
||||
<string name="recurrence_every_n_years">Každý %1$d. rok</string>
|
||||
<string name="recurrence_on_days">%1$s v %2$s</string>
|
||||
<string name="recurrence_with_until">%1$s do%2$s</string>
|
||||
<string name="recurrence_with_until">%1$s do %2$s</string>
|
||||
<string name="recurrence_with_count">%1$s, %2$d opakování</string>
|
||||
<string name="event_detail_not_found">Tato událost již neexistuje.</string>
|
||||
<string name="event_attendee_accepted">Přijato</string>
|
||||
@@ -190,7 +190,7 @@
|
||||
<string name="event_access_confidential_summary">Označeno jako utajené; záleží na daném účtu kalendáře, jak s tímto štítkem naloží</string>
|
||||
<string name="event_attendee_organizer">Pořadatel</string>
|
||||
<string name="event_attendee_optional">Volitelné</string>
|
||||
<string name="event_attendee_resource">Zdroj</string>
|
||||
<string name="event_attendee_resource">Prostředky</string>
|
||||
<string name="event_detail_self_response">Vaše odpověď: %1$s</string>
|
||||
<string name="reminder_at_time">V době konání události</string>
|
||||
<string name="reminder_default">Výchozí připomínka</string>
|
||||
@@ -296,7 +296,7 @@
|
||||
<string name="search_back">Zpět</string>
|
||||
<string name="search_clear">Vymazat</string>
|
||||
<string name="search_idle_hint">Prohledávejte své události podle názvu, místa konání nebo poznámek.</string>
|
||||
<string name="search_empty">Žádná událost neodpovídá “%1$s”.</string>
|
||||
<string name="search_empty">Žádná událost neodpovídá „%1$s“.</string>
|
||||
<plurals name="search_selected_count">
|
||||
<item quantity="one">%d označený</item>
|
||||
<item quantity="few">%d označené</item>
|
||||
@@ -409,7 +409,7 @@
|
||||
<string name="settings_widget_size_medium">Střední</string>
|
||||
<string name="settings_widget_size_large">Velký</string>
|
||||
<string name="settings_widget_size_extra_large">Největší</string>
|
||||
<string name="settings_widget_size_summary">Text události%1$d</string>
|
||||
<string name="settings_widget_size_summary">Text události %1$d</string>
|
||||
<string name="settings_agenda_show_today">Vždy zobrazovat dnešek</string>
|
||||
<string name="settings_agenda_show_today_hint">Ponechat dnešní den na prvním místě agendy a jejího widgetu, třebaže již nezbývá žádná nadcházející událost.</string>
|
||||
<string name="state_failure_all_hidden">Všechny vaše kalendáře jsou vypnuté.</string>
|
||||
@@ -430,12 +430,12 @@
|
||||
<string name="settings_agenda_range_bar_hint">V horní části agendy se zobrazí lišta s rozsahem dat a tlačítkem pro změnu rozsahu dané relace</string>
|
||||
<string name="agenda_range_showing_label">Zobrazit všechny nadcházející události pro</string>
|
||||
<plurals name="agenda_range_days">
|
||||
<item quantity="one">%dden</item>
|
||||
<item quantity="few">%ddny</item>
|
||||
<item quantity="many">%ddnů</item>
|
||||
<item quantity="other">%ddnů</item>
|
||||
<item quantity="one">%d den</item>
|
||||
<item quantity="few">%d dny</item>
|
||||
<item quantity="many">%d dnů</item>
|
||||
<item quantity="other">%d dnů</item>
|
||||
</plurals>
|
||||
<string name="settings_section_views">Pohledy</string>
|
||||
<string name="settings_section_views">Zobrazení</string>
|
||||
<string name="settings_month_header">Měsíční zobrazení</string>
|
||||
<string name="settings_month_view_style">Styl měsíčního zobrazení</string>
|
||||
<string name="month_style_paged">Stránky</string>
|
||||
@@ -485,4 +485,212 @@
|
||||
<string name="settings_reliable_delivery_exempt">Výloučení z optimalizace baterie – připomínky chodí bez prodlení.</string>
|
||||
<string name="settings_notifications_subtitle">Připomínky a doručení</string>
|
||||
<string name="settings_special_dates_reminders">Připomínky</string>
|
||||
<string name="settings_reliable_delivery">Spolehlivé doručení</string>
|
||||
<string name="settings_snooze_duration">Doba odložení</string>
|
||||
<string name="settings_section_calendars">Kalendáře</string>
|
||||
<string name="settings_manage_calendars">Správa kalendářů</string>
|
||||
<string name="settings_manage_calendars_hint">Vytvářet místní kalendáře; spravovat synchronizované kalendáře</string>
|
||||
<string name="settings_section_language">Jazyk</string>
|
||||
<string name="settings_language">Jazyk aplikace</string>
|
||||
<string name="settings_language_auto">Výchozí nastavení systému</string>
|
||||
<string name="settings_translate">Pomozte s překladem</string>
|
||||
<string name="settings_translate_hint">Přidat nebo vylepšit jazyk na Weblate</string>
|
||||
<string name="settings_group_look">Vzhled a chování</string>
|
||||
<string name="settings_group_data">Data</string>
|
||||
<string name="settings_group_app">Aplikace</string>
|
||||
<string name="settings_group_about">O aplikaci</string>
|
||||
<string name="settings_appearance_subtitle">Motiv, barvy, písma</string>
|
||||
<string name="settings_views_subtitle">Výchozí zobrazení, rozložení a pořadí</string>
|
||||
<string name="settings_event_form_subtitle">Výchozí pole a chování</string>
|
||||
<string name="settings_special_dates_subtitle">Narozeniny a výročí kontaktů</string>
|
||||
<string name="settings_widgets_subtitle">Widget s agendou, dlaždice rychlého nastavení</string>
|
||||
<string name="settings_backup_subtitle">Export, import, automatické zálohování</string>
|
||||
<string name="settings_section_widgets">Widgety a dlaždice</string>
|
||||
<string name="settings_widgets_hint">Nastavení widgetů na domovské obrazovce a dlaždice rychlého nastavení. Widget přidáte dlouhým stisknutím domovské obrazovky.</string>
|
||||
<string name="settings_section_backup">Záloha a obnovení</string>
|
||||
<string name="settings_views_all_header">Všechny pohledy</string>
|
||||
<string name="settings_week_day_header">Týden a den</string>
|
||||
<string name="settings_default_view_hint">Pohled, který se otevře při spuštění aplikace.</string>
|
||||
<string name="settings_week_start_hint">Počáteční den týdne ve všech zobrazeních a widgetech.</string>
|
||||
<string name="settings_time_format_hint">Způsob zápisu času v celé aplikaci. Funkce „Automaticky“ se řídí nastavením vašeho systému.</string>
|
||||
<string name="settings_past_events_hint">Jak program zachází s událostmi, které již skončily.</string>
|
||||
<string name="settings_dynamic_color_summary">Aplikace převezme barvy z vaší tapety.</string>
|
||||
<string name="settings_section_special_dates">Speciální data kontaktů</string>
|
||||
<string name="settings_special_dates_enable">Zobrazit data kontaktů</string>
|
||||
<string name="settings_special_dates_enable_hint">Zkopírujte data narozenin a další data svých kontaktů do místních kalendářů. Aplikace čte pouze kontakty v tomto zařízení – nikam se nic nenahrává, vaše kontakty zůstávají beze změny.</string>
|
||||
<string name="settings_special_dates_type_birthday">Narozeniny</string>
|
||||
<string name="settings_special_dates_type_anniversary">Výročí</string>
|
||||
<string name="settings_special_dates_type_custom">Ostatní data</string>
|
||||
<string name="settings_special_dates_template">Formát názvu</string>
|
||||
<string name="settings_special_dates_template_hint">Pro kontakt použijte {name} a pro rok {year} (rok narození nebo rok zahájení výročí; pokud není znám, údaj se skryje).</string>
|
||||
<string name="settings_special_dates_show_year">Zobrazit rok</string>
|
||||
<string name="settings_special_dates_show_year_hint">Uvést {year} v názvu, je-li znám</string>
|
||||
<string name="settings_special_dates_sync_now">Synchronizovat nyní</string>
|
||||
<string name="settings_special_dates_never_synced">Zatím nesynchronizováno</string>
|
||||
<string name="settings_special_dates_last_synced">Naposledy synchronizováno %1$s</string>
|
||||
<string name="settings_special_dates_calendar_hint">Barvu a viditelnost jednotlivých kalendářů nastavte v nastavení kalendářů.</string>
|
||||
<string name="settings_calendar_reminders_managed_hint">Nastavte v části Speciální data kontaktu</string>
|
||||
<string name="settings_special_dates_paused_title">Pozastaveno</string>
|
||||
<string name="settings_special_dates_paused_hint">Calendula už nemá přístup k vašim kontaktům, tudíž zůstávají bez aktualizace.</string>
|
||||
<string name="settings_special_dates_grant">Udělit přístup</string>
|
||||
<string name="settings_special_dates_disable_title">Vypnout data kontaktů?</string>
|
||||
<string name="settings_special_dates_disable_type_message">Tímto se odstraní kalendář „%1$s“ a jeho události. Veškeré připomínky a poznámky, které jste do nich přidali, budou ztraceny.</string>
|
||||
<string name="settings_special_dates_disable_confirm">Vypnout</string>
|
||||
<string name="dialog_save">Uložit</string>
|
||||
<string name="settings_section_about">O aplikaci</string>
|
||||
<string name="settings_license">Licence</string>
|
||||
<string name="settings_license_value">MIT</string>
|
||||
<string name="settings_about_author">od Jean-Luca Makioly</string>
|
||||
<string name="settings_about_source">Zdroj</string>
|
||||
<string name="settings_licences">Licence open source</string>
|
||||
<string name="settings_licences_subtitle">Projekty, na kterých je Calendula postavena</string>
|
||||
<string name="settings_about_privacy">Zásady ochrany osobních údajů</string>
|
||||
<string name="settings_about_support">Podpořte vývoj</string>
|
||||
<string name="settings_about_version">Verze %1$s</string>
|
||||
<string name="settings_about_logo_desc">Ikona aplikace</string>
|
||||
<string name="settings_report_problem">Nahlásit problém</string>
|
||||
<string name="settings_report_problem_hint">Odeslat hlášení o pádu nebo otevřít systém pro sledování chyb</string>
|
||||
<string name="licences_title">Licence open source</string>
|
||||
<string name="licences_intro">Calendula zahrnuje níže uvedené projekty. Klepnutím na libovolnou položku zobrazíte jeho zdrojový kód.</string>
|
||||
<string name="licences_footer">Každý projekt je zde zahrnut v nezměněné podobě a pod uvedenou licencí.</string>
|
||||
<string name="calendars_title">Kalendáře</string>
|
||||
<string name="calendars_local_header">Vaše kalendáře</string>
|
||||
<string name="calendars_local_empty">Zatím nejsou k dispozici žádné místní kalendáře. Vytvořte si kalendář, chcete-li události ukládat pouze do tohoto zařízení.</string>
|
||||
<string name="calendars_add">Přidat kalendář</string>
|
||||
<string name="calendars_visibility_hint">Vypnutím kalendáře jej na tomto zařízení skryjete – jeho události zmizí z aplikace a přestanou se zobrazovat připomínky. Jde o stejný přepínač, jaký používají i ostatní aplikace kalendáře, takže se kalendář skryje i v nich. Nic se nemaže, další zařízení to neovlivní a kalendář můžete kdykoli znovu zapnout.</string>
|
||||
<string name="calendars_visibility_a11y">Zobrazit „%1$s“</string>
|
||||
<string name="calendars_visibility_notice_title">Některé kalendáře jsou vypnuté</string>
|
||||
<string name="calendars_visibility_notice_message">Calendula zobrazuje kalendáře, které jsou pro toto zařízení zapnuté; některé z vašich kalendářů jsou však vypnuté. Chcete-li zobrazit jejich události, zapněte je v nabídce Nastavení → Kalendáře.</string>
|
||||
<string name="calendar_picker_missing_title">Chybí vám kalendář?</string>
|
||||
<string name="calendar_picker_missing_summary">Může být vypnutý, nastavený na režim „pouze pro čtení“ nebo naplněný daty z vašich kontaktů – své kalendáře spravujte zde.</string>
|
||||
<string name="calendars_state_read_only">Pouze pro čtení</string>
|
||||
<string name="calendars_state_not_synced">Není synchronizováno s tímto zařízením</string>
|
||||
<string name="calendars_state_managed">Vyplněno z vašich kontaktů</string>
|
||||
<string name="calendars_managed_delete_locked">Tento kalendář se plní z vašich kontaktů, takže jej aplikace Calendula při příští synchronizaci znovu vytvoří. Chcete-li jej odstranit, vypněte možnost speciálních dat v nabídce Nastavení → Speciální data.</string>
|
||||
<string name="calendars_synced_header">Synchronizované kalendáře</string>
|
||||
<string name="calendars_synced_hint">Tyto kontakty pocházejí z účtů ve vašem zařízení. Vytvářejte je a upravujte v příslušné aplikaci.</string>
|
||||
<string name="calendars_manage_in_app">Spravovat v aplikaci</string>
|
||||
<string name="calendars_account_from_source">%1$s (%2$s)</string>
|
||||
<string name="calendars_account_menu_a11y">Více možností pro %1$s</string>
|
||||
<string name="calendars_enable_all">Povolit vše</string>
|
||||
<string name="calendars_disable_all">Zakázat vše</string>
|
||||
<string name="calendars_add_account">Přidat účet</string>
|
||||
<string name="calendars_new_title">Nový kalendář</string>
|
||||
<string name="calendars_edit_title">Upravit kalendář</string>
|
||||
<string name="calendars_name_label">Jméno</string>
|
||||
<string name="calendars_color_label">Barva</string>
|
||||
<string name="calendars_description_hint">Přidat popis</string>
|
||||
<string name="calendars_delete_confirm_title">Vymazat kalendář?</string>
|
||||
<string name="calendars_delete_confirm_message">„%1$s“ a všechny jeho události budou z tohoto zařízení trvale odstraněny.</string>
|
||||
<string name="calendars_write_error">Změnu se nepodařilo uložit.</string>
|
||||
<string name="calendars_backup_header">Záloha</string>
|
||||
<string name="calendars_backup_hint">Místní kalendáře nejsou nikde synchronizovány, proto je pro uchování kopie exportujte do souboru ve formátu .ics.</string>
|
||||
<string name="calendars_backup_action">Exportovat jako soubor .ics</string>
|
||||
<string name="calendars_export_title">Exportovat kalendáře</string>
|
||||
<string name="calendars_export_hint">Vyberte, které kalendáře chcete zahrnout do souboru .ics.</string>
|
||||
<string name="calendars_export_action">Exportovat</string>
|
||||
<string name="calendars_restore_header">Obnovit</string>
|
||||
<string name="calendars_restore_action">Obnovit ze souboru .ics</string>
|
||||
<string name="calendars_restore_hint">Importuovat události ze zálohy nebo z jiné aplikace kalendáře.</string>
|
||||
<string name="calendars_auto_backup">Automatické zálohování</string>
|
||||
<string name="calendars_auto_backup_hint">Pravidelně exportujte své místní kalendáře do složky jako soubor .ics.</string>
|
||||
<string name="calendars_auto_backup_folder">Složka pro zálohování</string>
|
||||
<string name="calendars_auto_backup_folder_unset">Klepnutím vyberte složku</string>
|
||||
<string name="calendars_auto_backup_interval">Interval</string>
|
||||
<string name="calendars_auto_backup_every">Každý %1$s</string>
|
||||
<string name="calendars_auto_backup_interval_min">Minimálně 30 minut.</string>
|
||||
<string name="calendars_auto_backup_status_never">Zatím není nastaveno žádné automatické zálohování</string>
|
||||
<string name="calendars_auto_backup_status_ok">Poslední záloha: %1$s</string>
|
||||
<string name="calendars_auto_backup_status_failed">Poslední zálohování selhalo: %1$s</string>
|
||||
<string name="backup_channel_name">Záloha</string>
|
||||
<string name="backup_channel_description">Upozorní, pokud automatické zálohování opakovaně selže.</string>
|
||||
<string name="backup_failed_title">Automatické zálohování selhalo</string>
|
||||
<string name="backup_failed_text">Calendula nemohla zapsat záložní soubor. Zkontrolujte složku pro zálohování v nastavení.</string>
|
||||
<string name="calendars_backup_failed">Zálohu se nepodařilo exportovat.</string>
|
||||
<plurals name="calendars_backup_done">
|
||||
<item quantity="one">%d událost exportována.</item>
|
||||
<item quantity="few">%d události exportovány.</item>
|
||||
<item quantity="many">%d událostí exportováno.</item>
|
||||
<item quantity="other">%d událostí exportováno.</item>
|
||||
</plurals>
|
||||
<string name="import_title">Importovat události</string>
|
||||
<string name="import_target_header">Přidat do kalendáře</string>
|
||||
<string name="import_empty">V souboru nenalezeny žádné události.</string>
|
||||
<string name="import_failed">Tento soubor se nepodařilo přečíst.</string>
|
||||
<string name="import_no_calendar">Neexistuje žádný kalendář s právem zápisu, do kterého by bylo možné importovat. Nejprve vytvořte místní kalendář.</string>
|
||||
<string name="import_done_title">Import dokončen</string>
|
||||
<string name="import_done_dedup_note">Události, které již byly v kalendáři, byly přeskočeny.</string>
|
||||
<string name="import_done_added_label">Přidáno</string>
|
||||
<string name="import_done_skipped_label">Duplicity</string>
|
||||
<string name="import_done_failed_label">Nepřidáno</string>
|
||||
<string name="import_done_failed_note">Některé události se nepodařilo přidat, a proto byly vynechány.</string>
|
||||
<string name="import_done_stopped_title">Import byl zastaven</string>
|
||||
<string name="import_close">Zavřít</string>
|
||||
<string name="import_warning_recurrence">Některé změněné výskyty opakujících se událostí byly přeskočeny.</string>
|
||||
<string name="import_warning_no_start">Událost bez času zahájení byla přeskočena.</string>
|
||||
<string name="import_warning_attendees">Seznamy hostů nebyly importovány.</string>
|
||||
<string name="import_warning_timezone">Neznámé časové pásmo se přepnulo na časové pásmo vašeho zařízení.</string>
|
||||
<string name="import_warning_tasks">Úkoly v tomto souboru byly přidány jako události.</string>
|
||||
<string name="import_warning_recurrence_repaired">Chybné pravidlo pro opakování bylo opraveno.</string>
|
||||
<string name="import_button">Importovat</string>
|
||||
<plurals name="import_title_count">
|
||||
<item quantity="one">Importována %d událost</item>
|
||||
<item quantity="few">Importovány %d události</item>
|
||||
<item quantity="many">Importováno %d událostí</item>
|
||||
<item quantity="other">Importováno %d událostí</item>
|
||||
</plurals>
|
||||
<plurals name="import_event_count">
|
||||
<item quantity="one">V tomto souboru je %d událost.</item>
|
||||
<item quantity="few">V tomto souboru jsou %d události.</item>
|
||||
<item quantity="many">V tomto souboru je %d událostí.</item>
|
||||
<item quantity="other">V tomto souboru je %d událostí.</item>
|
||||
</plurals>
|
||||
<plurals name="import_action">
|
||||
<item quantity="one">Importovat %d událost</item>
|
||||
<item quantity="few">Importovat %d události</item>
|
||||
<item quantity="many">Importovat %d událostí</item>
|
||||
<item quantity="other">Importovat %d událostí</item>
|
||||
</plurals>
|
||||
<plurals name="import_done_stopped">
|
||||
<item quantity="one">Kalendář přestal v průběhu procesu zpracovávat události. %d událost ze souboru nebyla přidána.</item>
|
||||
<item quantity="few">Kalendář přestal v průběhu procesu zpracovávat události. %d události ze souboru nebyly přidány.</item>
|
||||
<item quantity="many">Kalendář přestal v průběhu procesu zpracovávat události. %d událostí ze souboru nebylo přidáno.</item>
|
||||
<item quantity="other">Kalendář přestal v průběhu procesu zpracovávat události. %d událostí ze souboru nebylo přidáno.</item>
|
||||
</plurals>
|
||||
<plurals name="import_done_imported">
|
||||
<item quantity="one">%d událost importována.</item>
|
||||
<item quantity="few">%d události importovány.</item>
|
||||
<item quantity="many">%d událostí importováno.</item>
|
||||
<item quantity="other">%d událostí importováno.</item>
|
||||
</plurals>
|
||||
<plurals name="import_done_skipped">
|
||||
<item quantity="one">Přeskočena %d událost, která již v tomto kalendáři existuje.</item>
|
||||
<item quantity="few">Přeskočeny %d události, která již v tomto kalendáři existují.</item>
|
||||
<item quantity="many">Přeskočeno %d událostí, která již v tomto kalendáři existují.</item>
|
||||
<item quantity="other">Přeskočeno %d událostí, která již v tomto kalendáři existují.</item>
|
||||
</plurals>
|
||||
<plurals name="import_done_failed">
|
||||
<item quantity="one">%d událost nebylo možné přidat.</item>
|
||||
<item quantity="few">%d události nebylo možné přidat.</item>
|
||||
<item quantity="many">%d událostí nebylo možné přidat.</item>
|
||||
<item quantity="other">%d událostí nebylo možné přidat.</item>
|
||||
</plurals>
|
||||
<string name="shortcut_new_event_short">Nová událost</string>
|
||||
<string name="shortcut_new_event_long">Vytvořit novou událost</string>
|
||||
<string name="qs_tile_new_event_label">Nová událost</string>
|
||||
<string name="settings_qs_tile">Přidat dlaždici rychlého nastavení</string>
|
||||
<string name="settings_qs_tile_hint">Přidat dlaždici „Nová událost“ do panelu rychlého nastavení.</string>
|
||||
<string name="crash_dialog_title">%1$s selhalo</string>
|
||||
<string name="crash_dialog_message">%1$s se minule neočekávaně ukončilo. Můžete pomoci problém vyřešit odesláním tohoto hlášení. Hlášení zůstane ve vašem zařízení, dokud se jej nerozhodnete odeslat, a neobsahuje žádné osobní údaje ani obsah kalendáře – pouze níže uvedené technické detaily.</string>
|
||||
<string name="crash_dialog_report">Odeslat hlášení o selhání</string>
|
||||
<string name="crash_dialog_dismiss">Později</string>
|
||||
<string name="crash_report_clip_label">Hlášení o selhání %1$s</string>
|
||||
<string name="crash_report_copied">Hlášení bylo zkopírováno do schránky</string>
|
||||
<string name="crash_report_open_failed">Nepodařilo se otevřít systém pro hlášení problémů. Zpráva je zkopírována ve vaší schránce.</string>
|
||||
<string name="special_dates_calendar_birthday">Narozeniny</string>
|
||||
<string name="special_dates_calendar_anniversary">Výročí</string>
|
||||
<string name="special_dates_calendar_custom">Speciální data</string>
|
||||
<string name="special_dates_default_title_birthday">Narozeniny má {name} ({year})</string>
|
||||
<string name="special_dates_default_title_anniversary">Výročí má {name} ({year})</string>
|
||||
<string name="special_dates_default_title_custom">{name}</string>
|
||||
</resources>
|
||||
|
||||
@@ -263,4 +263,44 @@
|
||||
<string name="onboarding_done_button">Otvoriť môj kalendár</string>
|
||||
<string name="reminder_channel_name">Pripomienky udalostí</string>
|
||||
<string name="reminder_channel_description">Notifikácie v časoch pripomienok vašich udalostí</string>
|
||||
<string name="agenda_today_action">Dnes</string>
|
||||
<string name="agenda_header_today">Dnes</string>
|
||||
<string name="search_back">Späť</string>
|
||||
<string name="widget_new_event">Nová udalosť</string>
|
||||
<string name="widget_prev_month">Predchádzajúci mesiac</string>
|
||||
<string name="widget_next_month">Nasledujúci mesiac</string>
|
||||
<string name="widget_today">Dnes</string>
|
||||
<string name="filter_title">Kalendáre</string>
|
||||
<string name="settings_title">Nastavenia</string>
|
||||
<string name="settings_back">Späť</string>
|
||||
<string name="back">Späť</string>
|
||||
<string name="timeline_scale_custom">Vlastné</string>
|
||||
<string name="agenda_range_day">Dnes</string>
|
||||
<string name="state_failure_all_hidden">Všetky vaše kalendáre sú vypnuté.</string>
|
||||
<string name="state_failure_all_hidden_action">Správa kalendárov</string>
|
||||
<string name="field_copy_action">Kopírovať</string>
|
||||
<string name="field_copied">Skopirovane do schránky</string>
|
||||
<string name="field_copy_failed">Toto sa nepodarilo skopírovať</string>
|
||||
<string name="event_detail_title">Názov</string>
|
||||
<string name="reminder_after">%1$s po</string>
|
||||
<string name="reminder_onboarding_title">Nikdy nezmeškajte udalosť</string>
|
||||
<string name="reminder_onboarding_body">Samotný Android nezobrazuje pripomienky udalostí - to musí zabezpečiť aplikácia kalendáru. Nechajte túto prácu na Calendule.</string>
|
||||
<string name="reminder_benefit_delivery_title">Pripomienky, doručené</string>
|
||||
<string name="reminder_benefit_delivery_body">Pripomienky k vašim udalostiam vám prídu ako notifikácia, vždy a načas.</string>
|
||||
<string name="reminder_benefit_duplicates_title">Používate ďalšiu aplikáciu s kalendárom?</string>
|
||||
<string name="reminder_onboarding_skip_button">Neskôr</string>
|
||||
<plurals name="search_delete_title">
|
||||
<item quantity="one">Odstrániť %d udalosť?</item>
|
||||
<item quantity="few">Odstrániť %d udalosti?</item>
|
||||
<item quantity="many">Odstrániť %d udalostí?</item>
|
||||
<item quantity="other">Odstrániť %d udalostí?</item>
|
||||
</plurals>
|
||||
<plurals name="search_delete_done">
|
||||
<item quantity="one">%d udalosť odstránená</item>
|
||||
<item quantity="few">%d udalosti odstránené</item>
|
||||
<item quantity="many">%d odstránených udalostí</item>
|
||||
<item quantity="other">%d odstránených udalostí</item>
|
||||
</plurals>
|
||||
<string name="agenda_range_this_week">Tento týždeň</string>
|
||||
<string name="agenda_range_custom">Vlastné…</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.jeanlucmakiola.calendula.ui.common
|
||||
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -75,17 +76,16 @@ class TimelineZoomTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a pinch held against a fractional bound stays put`() {
|
||||
// A bound that is not a whole pixel is a height the pinch can be pushed
|
||||
// against but never land on, so every frame of a held gesture would look
|
||||
// like a scale change and hand the focal anchor a scroll correction.
|
||||
fun `a pinch held against either bound stays put`() {
|
||||
val fillPx = 62.083f
|
||||
val maxPx = 616.5f
|
||||
|
||||
val floor = pinchedHourHeightPx(target = 1f, fillPx, maxPx)
|
||||
val ceiling = pinchedHourHeightPx(target = 9_000f, fillPx, maxPx)
|
||||
|
||||
assertThat(floor).isEqualTo(63f)
|
||||
// The ceiling is pulled onto the pixel grid so it stays landable; the
|
||||
// fill floor is landable as it is, being the clamp result itself.
|
||||
assertThat(floor).isEqualTo(fillPx)
|
||||
assertThat(ceiling).isEqualTo(616f)
|
||||
// Landing there and being pushed further must not move them again.
|
||||
assertThat(pinchedHourHeightPx(floor * 0.9f, fillPx, maxPx)).isEqualTo(floor)
|
||||
@@ -100,6 +100,35 @@ class TimelineZoomTest {
|
||||
assertThat(floor * 24).isAtLeast(viewport)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `pinching all the way out leaves nothing to scroll`() {
|
||||
// #290: rounding the fill floor up to a whole pixel made the day one
|
||||
// pixel per hour taller than the viewport it was supposed to fill, so a
|
||||
// fully zoomed-out timeline still scrolled a hair and bounced off
|
||||
// Android's overscroll stretch -- while FitDay, at the same zoom, sat
|
||||
// still. 1490 is deliberately not divisible by 24.
|
||||
val viewport = 1490f
|
||||
val floor = pinchedHourHeightPx(target = 1f, fillPx = viewport / 24f, maxPx = 616f)
|
||||
assertThat(floor * 24).isWithin(0.01f).of(viewport)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `the pinch floor is the height FitDay resolves to`() {
|
||||
// The inconsistency the issue is about: the two ways to reach "the whole
|
||||
// day on one screen" have to arrive at the same height.
|
||||
val density = Density(2.5f)
|
||||
val viewport = 596.dp
|
||||
with(density) {
|
||||
val fitDay = TimelineScale.FitDay.hourHeight(viewport).toPx()
|
||||
val pinched = pinchedHourHeightPx(
|
||||
target = 1f,
|
||||
fillPx = fillHourHeight(viewport).toPx(),
|
||||
maxPx = MAX_PINCH_HOUR_HEIGHT.toPx(),
|
||||
)
|
||||
assertThat(pinched).isWithin(0.01f).of(fitDay)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a settled pinch is what gets persisted`() {
|
||||
var persisted: TimelineScale? = null
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Privacy Policy — Calendula
|
||||
description: What Calendula does with your data. It collects nothing, sends nothing, and has no internet permission at all.
|
||||
updated: 2026-07-28
|
||||
updated: 2026-09-15
|
||||
---
|
||||
|
||||
<!--
|
||||
@@ -33,7 +33,7 @@ Calendula collects nothing, sends nothing, and has no user accounts. It has **no
|
||||
IT-Dienstleister | Jean-Luc Makiola
|
||||
Mahlerstraße 10
|
||||
14772 Brandenburg an der Havel
|
||||
Email: [business@jeanlucmakiola.de](mailto:business@jeanlucmakiola.de)
|
||||
Email: [support@jeanlucmakiola.de](mailto:support@jeanlucmakiola.de)
|
||||
|
||||
## 2. No data collection
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
Fixed
|
||||
• The app no longer reopens the event you last came in on. Opening an event from a reminder or a widget made it return to that event on every later launch, or show an error screen once the event was gone.
|
||||
• "Today" works in the Agenda again. It now scrolls the list back to today, and stays offered while you are scrolled away from it.
|
||||
@@ -4,7 +4,7 @@ Name: Calendula
|
||||
Summary: A modern Material 3 Expressive calendar for Android.
|
||||
|
||||
Categories:
|
||||
- Time
|
||||
- Calendar & Agenda
|
||||
|
||||
SourceCode: https://codeberg.org/jlmakiola/calendula
|
||||
IssueTracker: https://codeberg.org/jlmakiola/calendula/issues
|
||||
|
||||
Reference in New Issue
Block a user