feat(qs): add a 'New event' Quick Settings tile

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) <noreply@anthropic.com>
This commit is contained in:
2026-06-23 09:52:24 +02:00
parent c0e27133b4
commit 572a4734ea
6 changed files with 118 additions and 0 deletions

View File

@@ -88,6 +88,20 @@
android:excludeFromRecents="true"
android:launchMode="singleTask" />
<!-- Quick Settings tile: a one-tap "New event" shortcut in the QS panel.
Exported with BIND_QUICK_SETTINGS_TILE so only the system QS host can
bind it; the action mirrors the launcher "New event" shortcut. -->
<service
android:name=".qs.NewEventTileService"
android:exported="true"
android:icon="@drawable/ic_qs_new_event"
android:label="@string/qs_tile_new_event_label"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<!-- The provider broadcasts EVENT_REMINDER at reminder time but posts
no notification itself — a calendar app must (v1.4, Etar model).
Exported: the broadcast arrives from the provider's process. -->

View File

@@ -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)
}
}
}
}

View File

@@ -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+) —

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Quick Settings tile icon ("New event"): a flat calendar+plus glyph. QS tile
icons are tinted by the system from their alpha, so this is a bare white
glyph on a transparent background (no brand circle, unlike the launcher
shortcut icon). -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFFFF">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,3h-1V1h-2v2H8V1H6v2H5C3.89,3 3.01,3.9 3.01,5L3,19c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM19,19H5V8h14V19z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M11.5,10.5h1v2h2v1h-2v2h-1v-2h-2v-1h2z" />
</vector>

View File

@@ -247,6 +247,11 @@
<string name="shortcut_new_event_short">Neuer Termin</string>
<string name="shortcut_new_event_long">Neuen Termin erstellen</string>
<!-- Schnelleinstellungen-Kachel -->
<string name="qs_tile_new_event_label">Neuer Termin</string>
<string name="settings_qs_tile">Schnelleinstellungen-Kachel hinzufügen</string>
<string name="settings_qs_tile_hint">Eine Kachel „Neuer Termin“ zu den Schnelleinstellungen hinzufügen.</string>
<!-- Kalender-Filter (M3) -->
<string name="filter_title">Kalender</string>

View File

@@ -361,6 +361,11 @@
<string name="shortcut_new_event_short">New event</string>
<string name="shortcut_new_event_long">Create a new event</string>
<!-- Quick Settings tile -->
<string name="qs_tile_new_event_label">New event</string>
<string name="settings_qs_tile">Add Quick Settings tile</string>
<string name="settings_qs_tile_hint">Add a “New event” tile to the Quick Settings panel.</string>
<string name="about_source_url" translatable="false">https://gitea.jeanlucmakiola.de/makiolaj/calendula</string>
<string name="about_license_url" translatable="false">https://gitea.jeanlucmakiola.de/makiolaj/calendula/src/branch/main/LICENSE</string>