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
|
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
|
default size looks exactly as before — no new setting; it follows the size you
|
||||||
already chose ([#51]).
|
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
|
## [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
|
[#53]: https://codeberg.org/jlmakiola/calendula/issues/53
|
||||||
[#60]: https://codeberg.org/jlmakiola/calendula/issues/60
|
[#60]: https://codeberg.org/jlmakiola/calendula/issues/60
|
||||||
[#65]: https://codeberg.org/jlmakiola/calendula/issues/65
|
[#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
|
<!-- 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:
|
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
|
event's fields as CalendarContract extras
|
||||||
(MainActivity.insertFormOrNull, issue #30). ACTION_EDIT on the
|
(MainActivity.insertFormOrNull, issue #30). ACTION_EDIT on the
|
||||||
dir mime is AOSP's "edit a new event" — i.e. create — so it maps
|
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>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.INSERT" />
|
<action android:name="android.intent.action.INSERT" />
|
||||||
<action android:name="android.intent.action.EDIT" />
|
<action android:name="android.intent.action.EDIT" />
|
||||||
@@ -135,14 +137,31 @@
|
|||||||
<data android:mimeType="vnd.android.cursor.dir/event" />
|
<data android:mimeType="vnd.android.cursor.dir/event" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<!-- Edit an existing event another app/assistant/widget points at:
|
<!-- Create or edit an event another app/assistant/widget points at,
|
||||||
ACTION_EDIT on content://com.android.calendar/events/<id>, the way
|
addressed by the provider's *item* MIME type. Three actions share
|
||||||
AOSP fires it. Opens the occurrence in the edit form (not the
|
this filter, told apart at runtime by the intent's data:
|
||||||
read-only detail — that's the VIEW filter above). Matched by the
|
|
||||||
provider's item MIME type, like the VIEW filter. The occurrence's
|
• ACTION_INSERT — create. This is the form the Android docs'
|
||||||
times ride as EXTRA_EVENT_BEGIN_TIME / EXTRA_EVENT_END_TIME when
|
"insert an event" example and many apps use
|
||||||
supplied (MainActivity.editEventKeyOrNull). -->
|
(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>
|
<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" />
|
<action android:name="android.intent.action.EDIT" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="vnd.android.cursor.item/event" />
|
<data android:mimeType="vnd.android.cursor.item/event" />
|
||||||
|
|||||||
@@ -222,10 +222,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
* [buildInsertEventForm].
|
* [buildInsertEventForm].
|
||||||
*/
|
*/
|
||||||
private fun Intent.insertFormOrNull(): EventForm? {
|
private fun Intent.insertFormOrNull(): EventForm? {
|
||||||
// ACTION_EDIT on an existing event routes to the edit form instead
|
// ACTION_EDIT / ACTION_INSERT_OR_EDIT on an existing event route to the
|
||||||
// ([editEventKeyOrNull]); only an id-less EDIT is a create.
|
// edit form instead ([editEventKeyOrNull]); an id-less one is a create,
|
||||||
|
// as is any plain ACTION_INSERT.
|
||||||
val isCreate = action == Intent.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
|
if (!isCreate) return null
|
||||||
return buildInsertEventForm(
|
return buildInsertEventForm(
|
||||||
beginMillis = longExtraOrNull(CalendarContract.EXTRA_EVENT_BEGIN_TIME),
|
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`
|
* occurrence's times as `EXTRA_EVENT_BEGIN_TIME` / `EXTRA_EVENT_END_TIME`
|
||||||
* when it has them, otherwise we carry [NO_OCCURRENCE_TIME] and
|
* when it has them, otherwise we carry [NO_OCCURRENCE_TIME] and
|
||||||
* [EventEditViewModel.openForEdit] falls back to the event row's own
|
* [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? {
|
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
|
val uri = data ?: return null
|
||||||
if (uri.host != CALENDAR_PROVIDER_HOST) return null
|
if (uri.host != CALENDAR_PROVIDER_HOST) return null
|
||||||
val segments = uri.pathSegments
|
val segments = uri.pathSegments
|
||||||
|
|||||||
Reference in New Issue
Block a user