From 572a4734eaee84dca66c512b19b5b9f407eae7be Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Tue, 23 Jun 2026 09:52:24 +0200 Subject: [PATCH 1/3] feat(qs): add a 'New event' Quick Settings tile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A stateless TileService that opens the create-event form on today — the same action as the launcher 'New event' shortcut and the agenda widget's +. Reuses MainActivity.openCreateIntent; wrapped in unlockAndRun and using the API 34+ startActivityAndCollapse(PendingIntent) form (deprecated Intent form below 34). No new permission (BIND_QUICK_SETTINGS_TILE is system-side). Discoverability: Settings → New event form gains an 'Add Quick Settings tile' row that fires StatusBarManager.requestAddTileService (API 33+); on older versions the tile is still addable manually from the system QS editor. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/src/main/AndroidManifest.xml | 14 +++++++ .../calendula/qs/NewEventTileService.kt | 41 +++++++++++++++++++ .../calendula/ui/settings/SettingsScreen.kt | 35 ++++++++++++++++ app/src/main/res/drawable/ic_qs_new_event.xml | 18 ++++++++ app/src/main/res/values-de/strings.xml | 5 +++ app/src/main/res/values/strings.xml | 5 +++ 6 files changed, 118 insertions(+) create mode 100644 app/src/main/java/de/jeanlucmakiola/calendula/qs/NewEventTileService.kt create mode 100644 app/src/main/res/drawable/ic_qs_new_event.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 20737d8..d91eb70 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -88,6 +88,20 @@ android:excludeFromRecents="true" android:launchMode="singleTask" /> + + + + + + + diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/qs/NewEventTileService.kt b/app/src/main/java/de/jeanlucmakiola/calendula/qs/NewEventTileService.kt new file mode 100644 index 0000000..4199341 --- /dev/null +++ b/app/src/main/java/de/jeanlucmakiola/calendula/qs/NewEventTileService.kt @@ -0,0 +1,41 @@ +package de.jeanlucmakiola.calendula.qs + +import android.app.PendingIntent +import android.os.Build +import android.service.quicksettings.TileService +import de.jeanlucmakiola.calendula.MainActivity +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import kotlin.time.Clock + +/** + * Quick Settings tile: tapping it opens the create-event form on today — the + * same action as the launcher "New event" shortcut and the agenda widget's "+". + * A stateless action tile, so there is no on/off state to keep in sync. + */ +class NewEventTileService : TileService() { + + override fun onClick() { + super.onClick() + val today = Clock.System.now() + .toLocalDateTime(TimeZone.currentSystemDefault()).date + val intent = MainActivity.openCreateIntent(this, today) + // Launch only once the device is unlocked: creating an event behind the + // keyguard makes no sense, and the shade can't start an activity over a + // locked screen anyway. + unlockAndRun { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + val pending = PendingIntent.getActivity( + this, + 0, + intent, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT, + ) + startActivityAndCollapse(pending) + } else { + @Suppress("DEPRECATION") + startActivityAndCollapse(intent) + } + } + } +} 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 604ac85..47e86ba 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 @@ -1,12 +1,16 @@ package de.jeanlucmakiola.calendula.ui.settings import android.Manifest +import android.app.StatusBarManager +import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.graphics.drawable.Icon import android.os.Build import android.os.PowerManager import android.provider.Settings +import androidx.annotation.RequiresApi import android.text.format.DateFormat import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts @@ -80,6 +84,7 @@ import de.jeanlucmakiola.calendula.data.prefs.CalendarReminderOverride import de.jeanlucmakiola.calendula.data.prefs.ThemeMode import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref import de.jeanlucmakiola.calendula.domain.EventFormField +import de.jeanlucmakiola.calendula.qs.NewEventTileService import de.jeanlucmakiola.calendula.ui.crash.CrashReportDialog import de.jeanlucmakiola.calendula.ui.crash.openIssueTracker import de.jeanlucmakiola.calendula.ui.crash.submitCrashReport @@ -514,9 +519,39 @@ private fun EventFormScreen( ) }, ) + + // One-tap add of the "New event" Quick Settings tile. The system prompt + // is API 33+; on older versions the tile is still addable manually from + // the QS editor, so the row simply doesn't appear there. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + Spacer(Modifier.height(24.dp)) + val context = LocalContext.current + GroupedRow( + title = stringResource(R.string.settings_qs_tile), + summary = stringResource(R.string.settings_qs_tile_hint), + position = Position.Alone, + onClick = { requestAddQsTile(context) }, + ) + } } } +/** + * Ask the system to add the "New event" Quick Settings tile (API 33+). The OS + * shows its own confirmation dialog and handles the already-added case, so no + * result handling is needed here. + */ +@RequiresApi(Build.VERSION_CODES.TIRAMISU) +private fun requestAddQsTile(context: Context) { + val statusBar = context.getSystemService(StatusBarManager::class.java) ?: return + statusBar.requestAddTileService( + ComponentName(context, NewEventTileService::class.java), + context.getString(R.string.qs_tile_new_event_label), + Icon.createWithResource(context, R.drawable.ic_qs_new_event), + context.mainExecutor, + ) { /* result code unused — the system surfaces its own feedback */ } +} + /** * Reminder-notifications toggle (v1.4), mirroring the onboarding step. * Turning it on re-requests `POST_NOTIFICATIONS` when missing (API 33+) — diff --git a/app/src/main/res/drawable/ic_qs_new_event.xml b/app/src/main/res/drawable/ic_qs_new_event.xml new file mode 100644 index 0000000..df0f3fb --- /dev/null +++ b/app/src/main/res/drawable/ic_qs_new_event.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 037c717..af81d6d 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -247,6 +247,11 @@ Neuer Termin Neuen Termin erstellen + + Neuer Termin + Schnelleinstellungen-Kachel hinzufügen + Eine Kachel „Neuer Termin“ zu den Schnelleinstellungen hinzufügen. + Kalender diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 21e31be..0a412c6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -361,6 +361,11 @@ New event Create a new event + + New event + Add Quick Settings tile + Add a “New event” tile to the Quick Settings panel. + https://gitea.jeanlucmakiola.de/makiolaj/calendula https://gitea.jeanlucmakiola.de/makiolaj/calendula/src/branch/main/LICENSE -- 2.49.1 From b8a7191fbe4a9023597b812aa1ab610896c22923 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Tue, 23 Jun 2026 09:59:44 +0200 Subject: [PATCH 2/3] fix(nav): bring external new-event/open-date to the front The create form and Settings/calendar-manager are sibling overlays in one Box, with Settings drawn above the form. An external 'new event' request (QS tile, launcher shortcut, widget) set the create state correctly but rendered the form underneath an open Settings, forcing the user to back out first. Dismiss the covering overlays (Settings, calendar manager, detail/edit, import) when handling a Create or OpenDate nav request, so the requested destination is revealed on top. Fixes the same latent bug for the shortcut and widget. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../calendula/ui/CalendarHost.kt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt index 941f80f..9629464 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt @@ -137,16 +137,37 @@ fun CalendarHost( } } + // Close every overlay that can sit over the calendar, so an externally + // requested destination (a widget/shortcut/QS-tile launch) is revealed on + // top instead of underneath whatever the user had open. + fun dismissCoveringOverlays() { + showSettings = false + showCalendars = false + detailKey = null + editKey = null + importUri = null + importForm = null + } + // A home-screen widget launch asks to open a date (→ day view) or start a // create. Handled once and cleared, mirroring [requestedDetailKey]. LaunchedEffect(widgetNavRequest) { when (val req = widgetNavRequest) { is WidgetNavRequest.OpenDate -> { + // Reveal the day view: drop any overlay that would cover it, so + // an external date-open doesn't land under an open Settings/form. + dismissCoveringOverlays() + createDateIso = null pendingDayIso = req.dateIso view = CalendarView.Day onWidgetNavConsumed() } is WidgetNavRequest.Create -> { + // External "new event" entries (QS tile / launcher shortcut / + // widget) must land on top of whatever is open — the form overlay + // sits below Settings/calendars in the Box, so without this it + // would open hidden underneath them. + dismissCoveringOverlays() val iso = req.dateIso ?: Clock.System.now() .toLocalDateTime(TimeZone.currentSystemDefault()).date.toString() heldCreateIso = iso -- 2.49.1 From 5b99da32b0603c1ce4e32517c3965abb2efea49a Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Tue, 23 Jun 2026 10:13:33 +0200 Subject: [PATCH 3/3] fix(qs): silence StartActivityAndCollapseDeprecated lint error lintDebug aborts on the deprecated startActivityAndCollapse(Intent) overload in the pre-34 branch, even though that overload is the only one available below UpsideDownCake and is reached only there. Suppress the lint issue (and the compiler deprecation) at the function level since the call is intentional and version-gated. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../de/jeanlucmakiola/calendula/qs/NewEventTileService.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/qs/NewEventTileService.kt b/app/src/main/java/de/jeanlucmakiola/calendula/qs/NewEventTileService.kt index 4199341..757aef6 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/qs/NewEventTileService.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/qs/NewEventTileService.kt @@ -15,6 +15,9 @@ import kotlin.time.Clock */ class NewEventTileService : TileService() { + // The pre-34 branch intentionally uses the deprecated Intent overload: it is + // the only form available below UpsideDownCake, and is reached only there. + @Suppress("DEPRECATION", "StartActivityAndCollapseDeprecated") override fun onClick() { super.onClick() val today = Clock.System.now() @@ -33,7 +36,6 @@ class NewEventTileService : TileService() { ) startActivityAndCollapse(pending) } else { - @Suppress("DEPRECATION") startActivityAndCollapse(intent) } } -- 2.49.1