diff --git a/CHANGELOG.md b/CHANGELOG.md index 398434f..945c8d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ 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.12.0] — 2026-06-28 + +### Added +- Jump straight to typing a title. When you start a new event, Calendula now puts + the cursor in the title field and opens the keyboard right away, so you can type + the name without an extra tap. It's on by default and only affects creating an + event — editing never grabs focus — and a new **Focus title on new event** + switch (Settings → New event form) turns it off. Thanks to @abrossimow for the + suggestion ([#10]). + ## [2.11.1] — 2026-06-28 ### Fixed @@ -695,3 +705,4 @@ automatically, with zero telemetry and no internet permission. [#7]: https://codeberg.org/jlmakiola/calendula/issues/7 [#8]: https://codeberg.org/jlmakiola/calendula/issues/8 [#9]: https://codeberg.org/jlmakiola/calendula/issues/9 +[#10]: https://codeberg.org/jlmakiola/calendula/issues/10 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 51bb325..b20a171 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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 = 21101 - versionName = "2.11.1" + versionCode = 21200 + versionName = "2.12.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt b/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt index 0fec684..2d3b9e0 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefs.kt @@ -182,6 +182,20 @@ class SettingsPrefs @Inject constructor( } } + /** + * Whether opening the new-event form focuses the title field and raises the + * keyboard straight away (issue #10). Default ON — a new event almost always + * gets a title, so this saves a tap; users who set the time/calendar first + * can turn it off. Only the create form auto-focuses — editing never does. + */ + val autofocusEventTitle: Flow = store.data.map { prefs -> + prefs[AUTOFOCUS_EVENT_TITLE_KEY] ?: true + } + + suspend fun setAutofocusEventTitle(enabled: Boolean) { + store.edit { it[AUTOFOCUS_EVENT_TITLE_KEY] = enabled } + } + /** * Whether Calendula posts reminder notifications (v1.4). Defaults to ON — * for users whose only calendar app this is, reminders are essential; the @@ -392,6 +406,7 @@ class SettingsPrefs @Inject constructor( internal val SHOW_HOUR_LINES_KEY = booleanPreferencesKey("show_hour_lines") internal val DEFAULT_VIEW_KEY = stringPreferencesKey("default_view") internal val FORM_FIELDS_KEY = stringPreferencesKey("event_form_default_fields") + internal val AUTOFOCUS_EVENT_TITLE_KEY = booleanPreferencesKey("autofocus_event_title") internal val REMINDERS_ENABLED_KEY = booleanPreferencesKey("reminders_enabled") internal val REMINDER_ONBOARDING_KEY = booleanPreferencesKey("reminder_onboarding_done") internal val ALLOW_COLOR_UNSUPPORTED_KEY = diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt index 22d5ac6..166c0fd 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt @@ -81,6 +81,8 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.isSpecified @@ -479,6 +481,17 @@ private fun EventEditContent( ?: MaterialTheme.colorScheme.primary val gap = 12.dp + // Issue #10: on a fresh create form (setting on, nothing typed yet) focus the + // title and raise the keyboard so the user can type straight away. Keyed on + // Unit so it runs once per open — not on every keystroke — and guarded so + // editing or an imported/prefilled form never grabs focus. + val titleFocusRequester = remember { FocusRequester() } + LaunchedEffect(Unit) { + if (state.autofocusTitle && !state.isEditing && form.title.isBlank()) { + runCatching { titleFocusRequester.requestFocus() } + } + } + // Per-event colour applicability for the resolved calendar: // - palette calendars (Google, …) and local calendars always support it; // - synced calendars with no palette only when the user opted in, and even @@ -506,6 +519,10 @@ private fun EventEditContent( placeholder = stringResource(R.string.event_edit_title_hint), textStyle = MaterialTheme.typography.headlineMedium .copy(fontWeight = FontWeight.SemiBold), + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + .focusRequester(titleFocusRequester), ) Spacer(Modifier.height(10.dp)) Box( diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditUiState.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditUiState.kt index b9186dc..04b063d 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditUiState.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditUiState.kt @@ -29,6 +29,12 @@ data class EventEditUiState( val hiddenFields: List = emptyList(), /** True while editing an existing event (the calendar is then fixed). */ val isEditing: Boolean = false, + /** + * Whether to focus the title and raise the keyboard when this is a fresh + * create form (issue #10). Mirrors the settings flag; the screen only acts + * on it for a new event with an empty title, never when editing/importing. + */ + val autofocusTitle: Boolean = true, /** * True while an edit changed the recurrence rule — the save-scope dialog * then drops "only this event" (an exception row can't carry a rule). diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModel.kt index dbf9cf1..50a2f7d 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModel.kt @@ -119,6 +119,7 @@ class EventEditViewModel @Inject constructor( val lastUsed: Long?, val defaultFields: Set, val allowColorOnUnsupported: Boolean, + val autofocusTitle: Boolean, ) /** @@ -160,6 +161,7 @@ class EventEditViewModel @Inject constructor( prefs.lastUsedCalendarId, settingsPrefs.defaultFormFields, settingsPrefs.allowColorOnUnsupportedCalendars, + settingsPrefs.autofocusEventTitle, ::ExternalInputs, ).flowOn(io), colorPalette, @@ -178,6 +180,7 @@ class EventEditViewModel @Inject constructor( visibleFields = visibleFields, hiddenFields = (EventFormField.entries.toSet() - visibleFields).sorted(), isEditing = local.editTarget != null, + autofocusTitle = external.autofocusTitle, // A modified-occurrence exception can't carry its own rule, so // the scope dialog drops "only this event" after a rule change. recurrenceChanged = local.editTarget != null && diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt index fc1447e..c0be33e 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt @@ -45,6 +45,7 @@ import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Gavel +import androidx.compose.material.icons.filled.Keyboard import androidx.compose.material.icons.filled.Language import androidx.compose.material.icons.filled.Notifications import androidx.compose.material.icons.filled.Palette @@ -673,6 +674,30 @@ private fun EventFormScreen( ) } + // Auto-focus the title on a new event (issue #10) — on by default, since + // most events get a title; raising the keyboard saves a tap. Off lets you + // set the time/calendar first without the keyboard in the way. + Spacer(Modifier.height(24.dp)) + GroupedRow( + title = stringResource(R.string.settings_autofocus_title), + summary = stringResource(R.string.settings_autofocus_title_hint), + position = Position.Alone, + leading = { + Icon( + imageVector = Icons.Default.Keyboard, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + }, + trailing = { + Switch( + checked = state.autofocusEventTitle, + onCheckedChange = { viewModel.setAutofocusEventTitle(it) }, + ) + }, + onClick = { viewModel.setAutofocusEventTitle(!state.autofocusEventTitle) }, + ) + // Per-event colour on calendars that publish no colour set (some // CalDAV) — off by default, with the honest caveat that the colour may // not survive their next sync. Local and palette calendars ignore it. diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt index 0507e6a..8996709 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsUiState.kt @@ -34,6 +34,8 @@ data class SettingsUiState( val defaultView: CalendarView = CalendarView.Week, /** Optional event-form fields shown by default (rest behind "more fields"). */ val defaultFormFields: Set = SettingsPrefs.DEFAULT_FORM_FIELDS, + /** Whether the new-event form auto-focuses the title and shows the keyboard (#10). */ + val autofocusEventTitle: Boolean = true, /** Whether Calendula posts reminder notifications (v1.4). */ val remindersEnabled: Boolean = true, /** diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt index b9516a6..e0bcd8a 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsViewModel.kt @@ -96,15 +96,20 @@ class SettingsViewModel @Inject constructor( ) { view, screenRange, widgetRange, timeFormat, showHourLines -> ViewSettings(view, screenRange, widgetRange, timeFormat, showHourLines) }, - prefs.agendaShowRangeBar, - ) { base, defaults, overrides, views, showRangeBar -> + combine( + prefs.agendaShowRangeBar, + prefs.autofocusEventTitle, + ::MiscSettings, + ), + ) { base, defaults, overrides, views, misc -> base.copy( defaultView = views.defaultView, agendaScreenRange = views.agendaScreenRange, agendaWidgetRange = views.agendaWidgetRange, timeFormat = views.timeFormat, showHourLines = views.showHourLines, - agendaShowRangeBar = showRangeBar, + agendaShowRangeBar = misc.showRangeBar, + autofocusEventTitle = misc.autofocusEventTitle, allowColorOnUnsupportedCalendars = defaults.allowColor, defaultReminderMinutes = defaults.defaultReminder, defaultAllDayReminderMinutes = defaults.allDayReminder, @@ -148,6 +153,11 @@ class SettingsViewModel @Inject constructor( val showHourLines: Boolean, ) + private data class MiscSettings( + val showRangeBar: Boolean, + val autofocusEventTitle: Boolean, + ) + fun setThemeMode(mode: ThemeMode) { viewModelScope.launch { prefs.setThemeMode(mode) } } @@ -213,6 +223,10 @@ class SettingsViewModel @Inject constructor( viewModelScope.launch { prefs.setRemindersEnabled(enabled) } } + fun setAutofocusEventTitle(enabled: Boolean) { + viewModelScope.launch { prefs.setAutofocusEventTitle(enabled) } + } + fun setDefaultReminderMinutes(minutes: Int?) { viewModelScope.launch { prefs.setDefaultReminderMinutes(minutes) } } diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 348ba61..d6c9ab0 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -302,6 +302,8 @@ Termin-Formular Standardmäßig angezeigte Felder — alles Weitere liegt hinter \"Weitere Felder\" + Titel bei neuem Termin fokussieren + Beim Anlegen eines neuen Termins den Cursor direkt ins Titelfeld setzen und die Tastatur öffnen. Farben auf nicht unterstützten Kalendern erlauben Manche Kalender (z. B. bestimmte CalDAV) stellen keine Farbpalette bereit; eine eigene Terminfarbe wird dort bei der nächsten Synchronisierung unter Umständen verworfen oder überschrieben. Das ist eine Einschränkung dieser Kalender und kann von Calendula nicht behoben werden. Benachrichtigungen diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bf0b417..9b85a5e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -303,6 +303,8 @@ New event form Fields shown by default — everything else sits behind \"More fields\" + Focus title on new event + When you start a new event, place the cursor in the title field and open the keyboard right away. Allow colors on unsupported calendars Some calendars (e.g. certain CalDAV) publish no color set; a custom event color may be dropped or overwritten on their next sync. That\'s a limitation of those calendars, not something Calendula can fix. Notifications diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefsTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefsTest.kt index 012b75d..eec1075 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefsTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/data/prefs/SettingsPrefsTest.kt @@ -90,6 +90,14 @@ class SettingsPrefsTest { assertThat(prefs.showHourLines.first()).isTrue() } + @Test + fun `autofocus event title defaults on and round-trips`(@TempDir tempDir: Path) = runTest { + val prefs = SettingsPrefs(newDataStore(tempDir)) + assertThat(prefs.autofocusEventTitle.first()).isTrue() + prefs.setAutofocusEventTitle(false) + assertThat(prefs.autofocusEventTitle.first()).isFalse() + } + @Test fun `agenda ranges default to month and round-trip independently`(@TempDir tempDir: Path) = runTest { val prefs = SettingsPrefs(newDataStore(tempDir)) diff --git a/fastlane/metadata/android/en-US/changelogs/21200.txt b/fastlane/metadata/android/en-US/changelogs/21200.txt new file mode 100644 index 0000000..2fe0efc --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/21200.txt @@ -0,0 +1,7 @@ +### Added +- Jump straight to typing a title. When you start a new event, Calendula now puts + the cursor in the title field and opens the keyboard right away, so you can type + the name without an extra tap. It's on by default and only affects creating an + event — editing never grabs focus — and a new **Focus title on new event** + switch (Settings → New event form) turns it off. Thanks to @abrossimow for the + suggestion ([#10]).