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

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