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..757aef6
--- /dev/null
+++ b/app/src/main/java/de/jeanlucmakiola/calendula/qs/NewEventTileService.kt
@@ -0,0 +1,43 @@
+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() {
+
+ // 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()
+ .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 {
+ startActivityAndCollapse(intent)
+ }
+ }
+ }
+}
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
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