Compare commits

..

2 Commits

Author SHA1 Message Date
60aa889c57 Merge pull request 'feat(settings): custom snooze duration, extract editor card to floret-kit' (!67) from feat/custom-snooze-duration into release/v2.15.0
Reviewed-on: #67
2026-07-08 16:10:32 +00:00
3c9767387b feat(settings): custom snooze duration, extract editor card to floret-kit
All checks were successful
CI / ci (pull_request) Successful in 10m14s
The Snooze-duration setting gains a Custom… option: a new single-select
SnoozeDurationPicker keeps the minute presets and adds a Custom row that
expands an amount field with a Minutes/Hours toggle, so any delay is settable
(not just the fixed presets). Closes the settings half of the snooze request.

The three presets-plus-custom editors (reminder default, agenda range, snooze)
now delegate to floret-kit's new CustomAmountEditor instead of each duplicating
the tonal editor card — the app keeps its domain math, strings and labels.

Re-pin the floret-kit submodule to the branch commit carrying CustomAmountEditor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 18:02:13 +02:00
6 changed files with 163 additions and 138 deletions

View File

@@ -1,26 +1,14 @@
package de.jeanlucmakiola.calendula.ui.common package de.jeanlucmakiola.calendula.ui.common
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.Checkbox import androidx.compose.material3.Checkbox
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -29,13 +17,12 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.R
import de.jeanlucmakiola.floret.components.DialogAmountField import de.jeanlucmakiola.floret.components.CustomAmountEditor
import de.jeanlucmakiola.floret.components.FullScreenPicker import de.jeanlucmakiola.floret.components.FullScreenPicker
import de.jeanlucmakiola.floret.components.GroupedRow import de.jeanlucmakiola.floret.components.GroupedRow
import de.jeanlucmakiola.floret.components.Position import de.jeanlucmakiola.floret.components.Position
@@ -195,56 +182,18 @@ private fun CustomReminderEditor(
onConfirm: (Int) -> Unit, onConfirm: (Int) -> Unit,
) { ) {
val amount = amountText.toIntOrNull()?.takeIf { it in 1..999 } val amount = amountText.toIntOrNull()?.takeIf { it in 1..999 }
Surface( CustomAmountEditor(
color = MaterialTheme.colorScheme.surfaceContainerHigh, amountText = amountText,
// A Position.Bottom shape: tight top corners meeting the row, full bottom. onAmountChange = onAmountChange,
shape = RoundedCornerShape(topStart = 6.dp, topEnd = 6.dp, bottomStart = 22.dp, bottomEnd = 22.dp), unitLabels = ReminderUnit.entries.map { stringResource(reminderUnitLabel(it)) },
modifier = Modifier selectedUnit = unit.ordinal,
.fillMaxWidth() onUnitChange = { onUnitChange(ReminderUnit.entries[it]) },
.padding(horizontal = 16.dp), preview = amount?.let { reminderLeadTimeLabel(it * unit.minutesFactor) }
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
// Unit toggle first so it stays visible above the keyboard once the
// amount field (the bottom row) is focused and scrolled into view.
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
ReminderUnit.entries.forEachIndexed { index, entry ->
SegmentedButton(
selected = unit == entry,
onClick = { onUnitChange(entry) },
shape = SegmentedButtonDefaults.itemShape(index, ReminderUnit.entries.size),
label = { Text(stringResource(reminderUnitLabel(entry))) },
)
}
}
// Amount, a live preview of the lead time it resolves to, and Set —
// all on one row, sitting just above the keyboard.
Row(verticalAlignment = Alignment.CenterVertically) {
DialogAmountField(
value = amountText,
onValueChange = onAmountChange,
placeholder = "10",
)
Spacer(Modifier.width(16.dp))
Text(
text = amount?.let { reminderLeadTimeLabel(it * unit.minutesFactor) }
?: stringResource(R.string.reminder_custom_amount), ?: stringResource(R.string.reminder_custom_amount),
style = MaterialTheme.typography.bodyLarge, setLabel = stringResource(R.string.reminder_custom_set),
color = MaterialTheme.colorScheme.onSurfaceVariant, confirmEnabled = amount != null,
modifier = Modifier.weight(1f), onConfirm = { amount?.let { onConfirm(it * unit.minutesFactor) } },
) )
Spacer(Modifier.width(16.dp))
FilledTonalButton(
onClick = { amount?.let { onConfirm(it * unit.minutesFactor) } },
enabled = amount != null,
) {
Text(stringResource(R.string.reminder_custom_set))
}
}
}
}
} }
/** A short explanatory paragraph shown under a picker's title, above the rows. */ /** A short explanatory paragraph shown under a picker's title, above the rows. */
@@ -364,39 +313,134 @@ private fun CustomDaysEditor(
) { ) {
val days = amountText.toIntOrNull() val days = amountText.toIntOrNull()
?.takeIf { it in AgendaRange.MIN_CUSTOM_DAYS..AgendaRange.MAX_CUSTOM_DAYS } ?.takeIf { it in AgendaRange.MIN_CUSTOM_DAYS..AgendaRange.MAX_CUSTOM_DAYS }
Surface( CustomAmountEditor(
color = MaterialTheme.colorScheme.surfaceContainerHigh, amountText = amountText,
shape = RoundedCornerShape(topStart = 6.dp, topEnd = 6.dp, bottomStart = 22.dp, bottomEnd = 22.dp), onAmountChange = onAmountChange,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
) {
Row(
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
DialogAmountField(
value = amountText,
onValueChange = onAmountChange,
placeholder = "30", placeholder = "30",
) preview = days?.let { pluralStringResource(R.plurals.agenda_range_days, it, it) }
Spacer(Modifier.width(16.dp))
Text(
text = days?.let { pluralStringResource(R.plurals.agenda_range_days, it, it) }
?: stringResource(R.string.agenda_range_custom_hint), ?: stringResource(R.string.agenda_range_custom_hint),
style = MaterialTheme.typography.bodyLarge, setLabel = stringResource(R.string.reminder_custom_set),
color = MaterialTheme.colorScheme.onSurfaceVariant, confirmEnabled = days != null,
modifier = Modifier.weight(1f), onConfirm = { days?.let(onConfirm) },
) )
Spacer(Modifier.width(16.dp)) }
FilledTonalButton(
onClick = { days?.let(onConfirm) }, /**
enabled = days != null, * Snooze-duration picker, full-screen and **single-select**: the [presets]
* (whole-minute delays) each sit as a checkmark row, with a "Custom" row that
* expands an inline amount field plus a Minutes/Hours unit toggle to enter an
* arbitrary delay. Mirrors [AgendaRangePicker]'s custom-expand pattern; picking
* a preset or confirming a custom value applies via [onSelect] and closes.
* [label] renders a delay in minutes as a duration ("10 minutes", "1 hour") and
* is reused for both the rows and the custom preview.
*/
@Composable
fun SnoozeDurationPicker(
title: String,
presets: List<Int>,
selected: Int,
label: @Composable (Int) -> String,
onSelect: (Int) -> Unit,
onDismiss: () -> Unit,
) { ) {
Text(stringResource(R.string.reminder_custom_set)) val customSelected = selected !in presets
val rowCount = presets.size + 1 // + the custom row
var customExpanded by rememberSaveable { mutableStateOf(false) }
var amountText by rememberSaveable {
mutableStateOf(if (customSelected) snoozeCustomAmount(selected).toString() else "")
}
var unit by rememberSaveable {
mutableStateOf(if (customSelected) snoozeCustomUnit(selected) else ReminderUnit.Minutes)
}
FullScreenPicker(title = title, onDismiss = onDismiss, predictiveBack = true) {
presets.forEachIndexed { index, minute ->
val isSelected = minute == selected
GroupedRow(
title = label(minute),
position = positionOf(index, rowCount),
selected = isSelected,
trailing = if (isSelected) {
{ SelectedCheck() }
} else {
null
},
onClick = {
onSelect(minute)
onDismiss()
},
)
}
// The Custom row connects downward into the editor card when expanded, so
// the two read as one grouped container (the shared custom-expand pattern).
GroupedRow(
title = if (customSelected) label(selected) else stringResource(R.string.event_edit_reminder_custom),
position = if (customExpanded) Position.Top else positionOf(presets.size, rowCount),
selected = customSelected,
trailing = if (customSelected) {
{ SelectedCheck() }
} else {
null
},
onClick = { customExpanded = !customExpanded },
)
AnimatedVisibility(
visible = customExpanded,
enter = expandEnter(),
exit = collapseExit(),
) {
CustomSnoozeEditor(
amountText = amountText,
onAmountChange = { amountText = it },
unit = unit,
onUnitChange = { unit = it },
label = label,
onConfirm = { minutes ->
onSelect(minutes)
onDismiss()
},
)
} }
} }
} }
/** Whole hours if the delay divides evenly, else minutes. */
private fun snoozeCustomUnit(minutes: Int): ReminderUnit =
if (minutes % ReminderUnit.Hours.minutesFactor == 0) ReminderUnit.Hours else ReminderUnit.Minutes
private fun snoozeCustomAmount(minutes: Int): Int =
if (minutes % ReminderUnit.Hours.minutesFactor == 0) minutes / ReminderUnit.Hours.minutesFactor else minutes
/**
* The expanded "Custom" snooze editor: a tonal card connected to the Custom row
* above it. A Minutes/Hours unit toggle, an amount field with a live preview of
* the delay it resolves to, and a tonal confirm enabled only for a valid 1999
* amount. [onConfirm] receives the final delay in minutes.
*/
@Composable
private fun CustomSnoozeEditor(
amountText: String,
onAmountChange: (String) -> Unit,
unit: ReminderUnit,
onUnitChange: (ReminderUnit) -> Unit,
label: @Composable (Int) -> String,
onConfirm: (Int) -> Unit,
) {
val units = remember { listOf(ReminderUnit.Minutes, ReminderUnit.Hours) }
val amount = amountText.toIntOrNull()?.takeIf { it in 1..999 }
CustomAmountEditor(
amountText = amountText,
onAmountChange = onAmountChange,
unitLabels = units.map { stringResource(reminderUnitLabel(it)) },
selectedUnit = units.indexOf(unit).coerceAtLeast(0),
onUnitChange = { onUnitChange(units[it]) },
preview = amount?.let { label(it * unit.minutesFactor) }
?: stringResource(R.string.reminder_custom_amount),
setLabel = stringResource(R.string.reminder_custom_set),
confirmEnabled = amount != null,
onConfirm = { amount?.let { onConfirm(it * unit.minutesFactor) } },
)
} }
/** Human label for an [AgendaRange] (used by the picker rows and settings summary). */ /** Human label for an [AgendaRange] (used by the picker rows and settings summary). */

View File

@@ -133,6 +133,7 @@ import de.jeanlucmakiola.calendula.ui.common.ReminderDefaultPicker
import de.jeanlucmakiola.calendula.ui.common.TimePickerAlert import de.jeanlucmakiola.calendula.ui.common.TimePickerAlert
import de.jeanlucmakiola.floret.components.positionOf import de.jeanlucmakiola.floret.components.positionOf
import de.jeanlucmakiola.calendula.ui.common.reminderLeadTimeLabel import de.jeanlucmakiola.calendula.ui.common.reminderLeadTimeLabel
import de.jeanlucmakiola.calendula.ui.common.SnoozeDurationPicker
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.eventFormFieldIcon import de.jeanlucmakiola.calendula.ui.common.eventFormFieldIcon
@@ -1174,10 +1175,9 @@ private fun NotificationsScreen(
} }
if (showSnooze) { if (showSnooze) {
OptionPicker( SnoozeDurationPicker(
title = stringResource(R.string.settings_snooze_duration), title = stringResource(R.string.settings_snooze_duration),
predictiveBack = true, presets = SNOOZE_PRESETS,
options = SNOOZE_PRESETS,
selected = state.snoozeMinutes, selected = state.snoozeMinutes,
label = { snoozeDurationLabel(it) }, label = { snoozeDurationLabel(it) },
onSelect = { viewModel.setSnoozeMinutes(it) }, onSelect = { viewModel.setSnoozeMinutes(it) },

View File

@@ -310,7 +310,7 @@
<string name="settings_language">App-Sprache</string> <string name="settings_language">App-Sprache</string>
<string name="settings_language_auto">Systemstandard</string> <string name="settings_language_auto">Systemstandard</string>
<!-- Hub category subtitles --> <!-- Hub category subtitles -->
<string name="settings_appearance_subtitle">Design, Standardansicht, Wochenstart</string> <string name="settings_appearance_subtitle">Design, Standartansicht, Wochenstart</string>
<string name="settings_event_form_subtitle">Standardfelder für neue Termine</string> <string name="settings_event_form_subtitle">Standardfelder für neue Termine</string>
<string name="settings_notifications_subtitle">Termin-Erinnerungen</string> <string name="settings_notifications_subtitle">Termin-Erinnerungen</string>
<string name="settings_section_about">Über</string> <string name="settings_section_about">Über</string>
@@ -474,20 +474,4 @@
<string name="special_dates_default_title_birthday">Geburtstag von {name} ({year})</string> <string name="special_dates_default_title_birthday">Geburtstag von {name} ({year})</string>
<string name="special_dates_default_title_anniversary">Jahrestag von {name} ({year})</string> <string name="special_dates_default_title_anniversary">Jahrestag von {name} ({year})</string>
<string name="special_dates_default_title_custom">{name}</string> <string name="special_dates_default_title_custom">{name}</string>
<string name="settings_week_numbers">Kalenderwochen</string>
<string name="settings_week_numbers_summary">Kalenderwochen der Monatsansicht anzeigen</string>
<string name="calendars_export_title">Kalender exportieren</string>
<string name="calendars_export_hint">Wähle aus, welche Kalender in die .ics-Datei aufgenommen werden sollen.</string>
<string name="calendars_export_action">Exportieren</string>
<string name="calendars_restore_header">Wiederherstellen</string>
<string name="calendars_restore_action">Aus .ics-Datei wiederherstellen</string>
<string name="calendars_restore_hint">Importiere Ereignisse aus einem Backup oder einer anderen Kalender-App.</string>
<string name="import_done_dedup_note">Bereits im Kalender vorhandene Ereignisse wurden übersprungen.</string>
<string name="import_done_added_label">Hinzugefügt</string>
<string name="import_done_skipped_label">Duplikate</string>
<string name="import_button">Importieren</string>
<plurals name="import_title_count">
<item quantity="one">%d Ereignis wird importiert</item>
<item quantity="other">%d Ereignisse werden importiert</item>
</plurals>
</resources> </resources>

View File

@@ -16,17 +16,17 @@
<string name="state_failure_no_calendars">Nessun calendario configurato.</string> <string name="state_failure_no_calendars">Nessun calendario configurato.</string>
<string name="state_failure_no_calendars_action">Apri le impostazioni calendario di sistema</string> <string name="state_failure_no_calendars_action">Apri le impostazioni calendario di sistema</string>
<string name="state_failure_provider">Errore nella lettura del calendario.</string> <string name="state_failure_provider">Errore nella lettura del calendario.</string>
<string name="permission_rationale_title">Gestisci magnificamente tutti i tuoi eventi</string> <string name="permission_rationale_title">Guarda tutti i tuoi eventi, magnificamente</string>
<string name="permission_rationale_body">Calendula ha bisogno di accedere al tuo calendario per mostrare e gestire gli eventi. È l\'unica cosa di cui ha bisogno e nessuna informazione lascerà mai il tuo dispositivo.</string> <string name="permission_rationale_body">Calendula ha bisogno di accedere al tuo calendario per mostrare e gestire gli eventi. È l\'unica cosa che ti chiederà.</string>
<string name="permission_request_button">Concedi l\'accesso al calendario</string> <string name="permission_request_button">Concedi l\'accesso al calendario</string>
<string name="permission_denied_title">Accesso al calendario negato</string> <string name="permission_denied_title">Accesso al calendario negato</string>
<string name="permission_denied_body">Calendula non può mostrare gli eventi senza accesso al calendario. Puoi concederlo dalle impostazioni di sistema.</string> <string name="permission_denied_body">Calendula non può mostrare gli eventi senza accesso al calendario. Puoi concederlo dalle impostazioni di sistema.</string>
<string name="permission_open_settings_button">Apri le impostazioni di sistema</string> <string name="permission_open_settings_button">Apri le impostazioni di sistema</string>
<string name="permission_retry_button">Riprova</string> <string name="permission_retry_button">Riprova</string>
<string name="permission_benefit_private_title">Rimane sul tuo dispositivo</string> <string name="permission_benefit_private_title">Rimane sul tuo dispositivo</string>
<string name="permission_benefit_private_body">I tuoi calendari sono letti localmente e non lasciano mai il tuo dispositivo.</string> <string name="permission_benefit_private_body">I tuoi calendari sono letti localmente e non lasciano mai il tuo telefono.</string>
<string name="permission_benefit_sync_title">Tutti i tuoi calendari, insieme</string> <string name="permission_benefit_sync_title">Tutti i tuoi calendari, insieme</string>
<string name="permission_benefit_sync_body">Google, CalDAV, locali - qualsiasi calendario sincronizzato sul dispositivo, semplicemente, compare.</string> <string name="permission_benefit_sync_body">Google, CalDAV, locali - qualsiasi calendario sincronizzato sul dispositivo semplicemente compare.</string>
<string name="permission_benefit_privacy_title">Nessun tracking, per sempre</string> <string name="permission_benefit_privacy_title">Nessun tracking, per sempre</string>
<string name="permission_benefit_privacy_body">Zero telemetria, zero analytics, zero pubblicità.</string> <string name="permission_benefit_privacy_body">Zero telemetria, zero analytics, zero pubblicità.</string>
<string name="permission_privacy_footnote">Rimane sul tuo dispositivo - nessun accesso a internet</string> <string name="permission_privacy_footnote">Rimane sul tuo dispositivo - nessun accesso a internet</string>
@@ -38,7 +38,7 @@
<string name="month_action_settings">Impostazioni</string> <string name="month_action_settings">Impostazioni</string>
<string name="month_a11y_today_prefix">Oggi</string> <string name="month_a11y_today_prefix">Oggi</string>
<string name="week_today_action">Questa settimana</string> <string name="week_today_action">Questa settimana</string>
<string name="week_number_label">Sett.</string> <string name="week_number_label">Wk</string>
<string name="day_today_action">Oggi</string> <string name="day_today_action">Oggi</string>
<string name="event_detail_back">Indietro</string> <string name="event_detail_back">Indietro</string>
<string name="event_detail_edit">Modifica</string> <string name="event_detail_edit">Modifica</string>
@@ -73,14 +73,14 @@
<string name="event_edit_remove_reminder">Rimuovi promemoria</string> <string name="event_edit_remove_reminder">Rimuovi promemoria</string>
<string name="event_edit_attendees">Partecipanti</string> <string name="event_edit_attendees">Partecipanti</string>
<string name="event_edit_add_guest">Aggiungi partecipante</string> <string name="event_edit_add_guest">Aggiungi partecipante</string>
<string name="event_edit_add_guest_hint">Aggiungi un partecipante tramite indirizzo email…</string> <string name="event_edit_add_guest_hint">Aggiungi un partecipante via email…</string>
<string name="event_edit_add_guest_from_contacts">Aggiungi dai contatti</string> <string name="event_edit_add_guest_from_contacts">Aggiungi dai contatti</string>
<string name="event_edit_location_from_contacts">Scegli un indirizzo dai contatti</string> <string name="event_edit_location_from_contacts">Scegli un indirizzo dai contatti</string>
<string name="event_edit_remove_guest">Rimuovi partecipante</string> <string name="event_edit_remove_guest">Rimuovi partecipante</string>
<string name="event_edit_attendee_required">Obbligatorio</string> <string name="event_edit_attendee_required">Obbligatorio</string>
<string name="event_edit_attendee_optional">Facoltativo</string> <string name="event_edit_attendee_optional">Facoltativo</string>
<string name="event_edit_attendees_note_synced">Calendula non manda gli inviti agli eventi. Il tuo account calendario potrà inviare le email ai partecipanti al momento della sincronizzazione.</string> <string name="event_edit_attendees_note_synced">Calendula non manda gli inviti agli eventi. Il tuo account calendario potrà inviare le email ai partecipanti al momento della sincronizzazione.</string>
<string name="event_edit_attendees_note_local">Salvato sul dispositivo. Nessuno dei partecipanti ha ricevuto notifiche.</string> <string name="event_edit_attendees_note_local">Salvato sul dispositivo. Nessuno ha ricevuto notifiche.</string>
<string name="event_edit_reminder_custom">Personalizza</string> <string name="event_edit_reminder_custom">Personalizza</string>
<string name="reminder_unit_minutes">minuti</string> <string name="reminder_unit_minutes">minuti</string>
<string name="reminder_unit_hours">ore</string> <string name="reminder_unit_hours">ore</string>
@@ -89,7 +89,7 @@
<string name="event_edit_availability">Disponibilità</string> <string name="event_edit_availability">Disponibilità</string>
<string name="event_edit_visibility">Visibilità</string> <string name="event_edit_visibility">Visibilità</string>
<string name="event_edit_color">Colore</string> <string name="event_edit_color">Colore</string>
<string name="event_edit_color_default">Colore del calendario</string> <string name="event_edit_color_default">Colore calendario</string>
<string name="event_edit_color_custom">Colore personalizzato</string> <string name="event_edit_color_custom">Colore personalizzato</string>
<string name="event_edit_color_reset">Reset</string> <string name="event_edit_color_reset">Reset</string>
<string name="event_edit_color_unsupported">Non disponibile per questo calendario</string> <string name="event_edit_color_unsupported">Non disponibile per questo calendario</string>
@@ -99,13 +99,13 @@
<string name="event_edit_conflict_body">Durante la modifica l\'evento è stato cambiato, tramite sincronizzazione o tramite altra app. Cosa fare con le modifiche in corso?</string> <string name="event_edit_conflict_body">Durante la modifica l\'evento è stato cambiato, tramite sincronizzazione o tramite altra app. Cosa fare con le modifiche in corso?</string>
<string name="event_edit_conflict_overwrite">Salva le modifiche</string> <string name="event_edit_conflict_overwrite">Salva le modifiche</string>
<string name="event_edit_conflict_overwrite_hint">Sovrascrivi solo i campi modificati</string> <string name="event_edit_conflict_overwrite_hint">Sovrascrivi solo i campi modificati</string>
<string name="event_edit_conflict_discard">Annulla le modifiche</string> <string name="event_edit_conflict_discard">Annulla le mie modifiche</string>
<string name="event_edit_conflict_discard_hint">Lascia l\'evento invariato</string> <string name="event_edit_conflict_discard_hint">Mantieni l\'evento com\'è ora</string>
<string name="event_edit_gone_title">Evento eliminato</string> <string name="event_edit_gone_title">Evento eliminato</string>
<string name="event_edit_gone_body">Nel frattempo, questo evento è stato eliminato, ad esempio su un altro dispositivo. Le modifiche non possono più essere salvate.</string> <string name="event_edit_gone_body">Nel frattempo, questo evento è stato eliminato, ad esempio su un altro dispositivo. Le modifiche non possono più essere salvate.</string>
<string name="event_edit_recurrence_none">Non si ripete</string> <string name="event_edit_recurrence_none">Non si ripete</string>
<string name="event_edit_recurrence_custom">Personalizza</string> <string name="event_edit_recurrence_custom">Personalizza</string>
<string name="event_edit_recurrence_every">Ogni</string> <string name="event_edit_recurrence_every">La serie</string>
<string name="recurrence_unit_days">giorni</string> <string name="recurrence_unit_days">giorni</string>
<string name="recurrence_unit_weeks">settimane</string> <string name="recurrence_unit_weeks">settimane</string>
<string name="recurrence_unit_months">mesi</string> <string name="recurrence_unit_months">mesi</string>
@@ -113,7 +113,7 @@
<string name="event_edit_recurrence_ends">Fine</string> <string name="event_edit_recurrence_ends">Fine</string>
<string name="event_edit_recurrence_end_never">Mai</string> <string name="event_edit_recurrence_end_never">Mai</string>
<string name="event_edit_recurrence_end_until">Fino alla data</string> <string name="event_edit_recurrence_end_until">Fino alla data</string>
<string name="event_edit_recurrence_end_count">Numero di occorrenze</string> <string name="event_edit_recurrence_end_count">Dopo un certo numero di occorrenze</string>
<string name="event_edit_recurrence_times">occorrenze</string> <string name="event_edit_recurrence_times">occorrenze</string>
<string name="event_edit_error_recurrence_ends_before_start">Fine antecedente l\'inizio</string> <string name="event_edit_error_recurrence_ends_before_start">Fine antecedente l\'inizio</string>
<string name="event_availability_busy">Occupato</string> <string name="event_availability_busy">Occupato</string>
@@ -125,7 +125,7 @@
<string name="event_detail_location">Luogo</string> <string name="event_detail_location">Luogo</string>
<string name="event_detail_description">Descrizione</string> <string name="event_detail_description">Descrizione</string>
<string name="event_detail_attendees">Partecipanti</string> <string name="event_detail_attendees">Partecipanti</string>
<string name="recurrence_daily">Ogni giorno</string> <string name="recurrence_daily">Ogni giorni</string>
<string name="recurrence_weekly">Ogni settimana</string> <string name="recurrence_weekly">Ogni settimana</string>
<string name="recurrence_monthly">Ogni mese</string> <string name="recurrence_monthly">Ogni mese</string>
<string name="recurrence_yearly">Ogni anno</string> <string name="recurrence_yearly">Ogni anno</string>
@@ -204,7 +204,7 @@
<string name="reminder_benefit_reversible_body">Lo switch è nelle Impostazioni, sezione Notifiche.</string> <string name="reminder_benefit_reversible_body">Lo switch è nelle Impostazioni, sezione Notifiche.</string>
<string name="reminder_onboarding_enable_button">Attiva i promemoria</string> <string name="reminder_onboarding_enable_button">Attiva i promemoria</string>
<string name="reminder_onboarding_skip_button">Non ora</string> <string name="reminder_onboarding_skip_button">Non ora</string>
<string name="reminder_action_snooze">Posticipa</string> <string name="reminder_action_snooze">Posponi</string>
<string name="reminder_action_dismiss">Ignora</string> <string name="reminder_action_dismiss">Ignora</string>
<string name="view_month">Mese</string> <string name="view_month">Mese</string>
<string name="view_week">Settimana</string> <string name="view_week">Settimana</string>
@@ -418,16 +418,16 @@
<string name="settings_default_reminder">Promemoria standard</string> <string name="settings_default_reminder">Promemoria standard</string>
<string name="settings_notifications_subtitle">Promemoria evento</string> <string name="settings_notifications_subtitle">Promemoria evento</string>
<string name="shortcut_new_event_short">Nuovo evento</string> <string name="shortcut_new_event_short">Nuovo evento</string>
<string name="event_edit_managed_hint">Gestito da “%1$s” - titolo, data e ripetizioni sono sincronizzate dai tuoi contatti. Promemoria e note sono modificabili dall\'utente.</string> <string name="event_edit_managed_hint">Gestito da “%1$s” - titolo, data e ripetizioni sono sincronizzate dai tuoi contatti. Promemoria e note sono modificabili dall\'utente</string>
<string name="settings_font_headings">Carattere del titolo</string> <string name="settings_font_headings">Carattere dell\'intestazione</string>
<string name="settings_font_body">Carattere del corpo</string> <string name="settings_font_body">Carattere del corpo</string>
<string name="settings_font_system">Predefinito di sistema</string> <string name="settings_font_system">Predefinito di sistema</string>
<string name="settings_font_choose_file">Scegli file…</string> <string name="settings_font_choose_file">Scegli file…</string>
<string name="settings_font_custom_selected">Font personalizzato</string> <string name="settings_font_custom_selected">Carattere personalizzato</string>
<string name="settings_font_import_failed">Impossibile leggere il file come font</string> <string name="settings_font_import_failed">Impossibile leggere il file come font</string>
<string name="settings_section_views">Viste</string> <string name="settings_section_views">Viste</string>
<string name="settings_quick_switch_header">Pulsante di cambio rapido</string> <string name="settings_quick_switch_header">Pulsante di cambio rapido</string>
<string name="settings_quick_switch_hint">Scegli attraverso quali viste scorrere col pulsante in alto a destra, trascinale per riordinarle. Le viste disattivate rimangono raggiungibili dal menù di navigazione.</string> <string name="settings_quick_switch_hint">Scegli attraverso quali viste scorrere mediante il pulsante in alto a destra, trascinale per riordinarle. Le viste disattivate rimangono raggiungibili dal menù di navigazione.</string>
<string name="settings_drawer_order_header">Menù di navigazione</string> <string name="settings_drawer_order_header">Menù di navigazione</string>
<string name="settings_drawer_order_hint">Trascina per riordinare le viste elencate nel menù di navigazione.</string> <string name="settings_drawer_order_hint">Trascina per riordinare le viste elencate nel menù di navigazione.</string>
<string name="reorder_drag_handle">Trascina per riordinare</string> <string name="reorder_drag_handle">Trascina per riordinare</string>
@@ -463,7 +463,4 @@
<string name="special_dates_calendar_custom">Date speciali</string> <string name="special_dates_calendar_custom">Date speciali</string>
<string name="special_dates_default_title_birthday">Compleanno di {name} ({year})</string> <string name="special_dates_default_title_birthday">Compleanno di {name} ({year})</string>
<string name="special_dates_default_title_anniversary">Anniversario di {name} ({year})</string> <string name="special_dates_default_title_anniversary">Anniversario di {name} ({year})</string>
<string name="font_lora">Lora</string>
<string name="font_jetbrains_mono">JetBrains Mono</string>
<string name="special_dates_default_title_custom">{name}</string>
</resources> </resources>

View File

@@ -13,14 +13,14 @@ composeBom = "2026.05.01"
# Re-evaluate when 1.5.0 stable lands. # Re-evaluate when 1.5.0 stable lands.
material3 = "1.5.0-alpha21" material3 = "1.5.0-alpha21"
datastore = "1.2.1" datastore = "1.2.1"
junit = "6.1.2" junit = "6.1.0"
junitPlatform = "6.1.2" junitPlatform = "6.1.0"
truth = "1.4.5" truth = "1.4.5"
androidxJunit = "1.3.0" androidxJunit = "1.3.0"
espressoCore = "3.7.0" espressoCore = "3.7.0"
kotlinxDatetime = "0.7.0" kotlinxDatetime = "0.7.0"
kotlinxCoroutines = "1.10.2" kotlinxCoroutines = "1.10.2"
turbine = "1.2.1" turbine = "1.2.0"
hiltNavigationCompose = "1.3.0" hiltNavigationCompose = "1.3.0"
lifecycleCompose = "2.10.0" lifecycleCompose = "2.10.0"
androidxTestRules = "1.7.0" androidxTestRules = "1.7.0"