fix(intents): accept item-typed INSERT / INSERT_OR_EDIT for "Add to calendar" (#74)
Other apps' "Add to calendar" / "Save to calendar" actions commonly fire the
canonical insert intent — ACTION_INSERT with setType("vnd.android.cursor.item/
event") — the singular *item* MIME type (Android's own docs example; used by
e.g. DB Navigator). Calendula advertised INSERT only on the *dir* MIME type, so
it never matched: absent from the chooser, and a silent no-op when it was the
only calendar app installed.
Add the item-typed INSERT to the events filter, plus ACTION_INSERT_OR_EDIT (the
third "add to calendar" action AOSP and Google Calendar register). The runtime
parser already treats any ACTION_INSERT as create; teach insertFormOrNull /
editEventKeyOrNull about INSERT_OR_EDIT so an id-less one is a create and an
id-carrying one opens the edit form.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
Now a bigger widget gets bigger, more readable type and roomier rows, while the
|
||||
default size looks exactly as before — no new setting; it follows the size you
|
||||
already chose ([#51]).
|
||||
- Calendula now appears under other apps' "Add to calendar" / "Save to calendar"
|
||||
actions. Some apps (e.g. DB Navigator) fire the widely-used "insert event"
|
||||
intent with the singular `vnd.android.cursor.item/event` type, which Calendula
|
||||
didn't advertise — so it was left out of the chooser, and if it was your only
|
||||
calendar app the save silently did nothing. It now accepts that form, plus the
|
||||
`INSERT_OR_EDIT` action, and opens the new event prefilled for review ([#74]).
|
||||
|
||||
## [2.16.0] — 2026-07-17
|
||||
|
||||
@@ -1079,3 +1085,4 @@ automatically, with zero telemetry and no internet permission.
|
||||
[#53]: https://codeberg.org/jlmakiola/calendula/issues/53
|
||||
[#60]: https://codeberg.org/jlmakiola/calendula/issues/60
|
||||
[#65]: https://codeberg.org/jlmakiola/calendula/issues/65
|
||||
[#74]: https://codeberg.org/jlmakiola/calendula/issues/74
|
||||
|
||||
@@ -123,11 +123,13 @@
|
||||
|
||||
<!-- Let another app or widget (e.g. the Todo Agenda widget) launch us
|
||||
to create a new event, the way the AOSP calendar accepts it:
|
||||
ACTION_INSERT on the events dir mime type, carrying the new
|
||||
ACTION_INSERT on the events *dir* mime type, carrying the new
|
||||
event's fields as CalendarContract extras
|
||||
(MainActivity.insertFormOrNull, issue #30). ACTION_EDIT on the
|
||||
dir mime is AOSP's "edit a new event" — i.e. create — so it maps
|
||||
to the same prefilled create form. -->
|
||||
to the same prefilled create form. (The far more common *item*-
|
||||
typed INSERT — the form the Android docs' example and apps like
|
||||
DB Navigator use — is the item filter below, issue #74.) -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.INSERT" />
|
||||
<action android:name="android.intent.action.EDIT" />
|
||||
@@ -135,14 +137,31 @@
|
||||
<data android:mimeType="vnd.android.cursor.dir/event" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Edit an existing event another app/assistant/widget points at:
|
||||
ACTION_EDIT on content://com.android.calendar/events/<id>, the way
|
||||
AOSP fires it. Opens the occurrence in the edit form (not the
|
||||
read-only detail — that's the VIEW filter above). Matched by the
|
||||
provider's item MIME type, like the VIEW filter. The occurrence's
|
||||
times ride as EXTRA_EVENT_BEGIN_TIME / EXTRA_EVENT_END_TIME when
|
||||
supplied (MainActivity.editEventKeyOrNull). -->
|
||||
<!-- Create or edit an event another app/assistant/widget points at,
|
||||
addressed by the provider's *item* MIME type. Three actions share
|
||||
this filter, told apart at runtime by the intent's data:
|
||||
|
||||
• ACTION_INSERT — create. This is the form the Android docs'
|
||||
"insert an event" example and many apps use
|
||||
(setType("vnd.android.cursor.item/event")), e.g. DB Navigator's
|
||||
"Save to calendar". The dir-typed filter above alone missed it,
|
||||
so Calendula never showed in the chooser — and, when it was the
|
||||
only calendar app, the intent resolved to nothing (issue #74).
|
||||
• ACTION_INSERT_OR_EDIT — the third "add to calendar" action AOSP
|
||||
and Google Calendar register; a create, or an edit when it
|
||||
carries an event id.
|
||||
• ACTION_EDIT — edit the existing event at
|
||||
content://com.android.calendar/events/<id> (an id-less EDIT is a
|
||||
create). Opens the occurrence in the edit form, not the
|
||||
read-only detail — that's the VIEW filter above.
|
||||
|
||||
Create fields ride as CalendarContract extras; an edit's
|
||||
occurrence times ride as EXTRA_EVENT_BEGIN_TIME /
|
||||
EXTRA_EVENT_END_TIME when supplied (MainActivity.insertFormOrNull
|
||||
/ editEventKeyOrNull). -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.INSERT" />
|
||||
<action android:name="android.intent.action.INSERT_OR_EDIT" />
|
||||
<action android:name="android.intent.action.EDIT" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="vnd.android.cursor.item/event" />
|
||||
|
||||
@@ -222,10 +222,12 @@ class MainActivity : AppCompatActivity() {
|
||||
* [buildInsertEventForm].
|
||||
*/
|
||||
private fun Intent.insertFormOrNull(): EventForm? {
|
||||
// ACTION_EDIT on an existing event routes to the edit form instead
|
||||
// ([editEventKeyOrNull]); only an id-less EDIT is a create.
|
||||
// ACTION_EDIT / ACTION_INSERT_OR_EDIT on an existing event route to the
|
||||
// edit form instead ([editEventKeyOrNull]); an id-less one is a create,
|
||||
// as is any plain ACTION_INSERT.
|
||||
val isCreate = action == Intent.ACTION_INSERT ||
|
||||
(action == Intent.ACTION_EDIT && editEventKeyOrNull() == null)
|
||||
((action == Intent.ACTION_EDIT || action == Intent.ACTION_INSERT_OR_EDIT) &&
|
||||
editEventKeyOrNull() == null)
|
||||
if (!isCreate) return null
|
||||
return buildInsertEventForm(
|
||||
beginMillis = longExtraOrNull(CalendarContract.EXTRA_EVENT_BEGIN_TIME),
|
||||
@@ -344,10 +346,11 @@ class MainActivity : AppCompatActivity() {
|
||||
* occurrence's times as `EXTRA_EVENT_BEGIN_TIME` / `EXTRA_EVENT_END_TIME`
|
||||
* when it has them, otherwise we carry [NO_OCCURRENCE_TIME] and
|
||||
* [EventEditViewModel.openForEdit] falls back to the event row's own
|
||||
* DTSTART/DTEND. An id-less `ACTION_EDIT` is a create instead ([insertFormOrNull]).
|
||||
* DTSTART/DTEND. An id-less `ACTION_EDIT` — or `ACTION_INSERT_OR_EDIT`, which
|
||||
* some apps fire — is a create instead ([insertFormOrNull]).
|
||||
*/
|
||||
private fun Intent.editEventKeyOrNull(): LongArray? {
|
||||
if (action != Intent.ACTION_EDIT) return null
|
||||
if (action != Intent.ACTION_EDIT && action != Intent.ACTION_INSERT_OR_EDIT) return null
|
||||
val uri = data ?: return null
|
||||
if (uri.host != CALENDAR_PROVIDER_HOST) return null
|
||||
val segments = uri.pathSegments
|
||||
|
||||
Reference in New Issue
Block a user