From 2b2a31ecda1b2f56b25a01c7dbd409069c6178ea Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Wed, 14 Oct 2026 15:57:32 +0200 Subject: [PATCH] Create an event from shared or selected text (#306) (#396) Shared text, or text picked with the new "Create event" entry in the selection menu, opens the new-event form prefilled. The share's subject becomes the title, or the first line if there is no subject, and the rest of the text goes into the description. Start and length use the usual defaults (next full hour, default duration). Calendula doesn't try to read a date out of the text. Differences from the plan in the issue comment: - The line used as the title is left out of the description, so it doesn't show up twice. - Titles are capped at 255 characters. If a title gets cut, the full text stays in the description. - Sharing a plain-text file no longer sends it to the .ics importer. - A saved or dismissed prefilled form no longer reopens after rotating the phone. This also fixes it for "add to calendar" requests from other apps. - If a second request comes in while a prefilled form is open, it replaces the form when nothing was changed. Otherwise the save / discard / keep editing prompt comes first. - If Calendula was started just for the request, closing or saving the form takes you back to the app you came from. Closes #306 Co-authored-by: Jean-Luc Makiola Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/396 --- app/src/main/AndroidManifest.xml | 13 ++++ .../jeanlucmakiola/calendula/MainActivity.kt | 72 ++++++++++++++++-- .../calendula/domain/InsertEventForm.kt | 36 +++++++-- .../calendula/ui/CalendarHost.kt | 75 ++++++++++++++----- .../jeanlucmakiola/calendula/ui/RootScreen.kt | 2 + .../calendula/ui/edit/EventEditScreen.kt | 11 ++- app/src/main/res/values/strings.xml | 3 + .../calendula/domain/SharedTextTest.kt | 57 ++++++++++++++ 8 files changed, 239 insertions(+), 30 deletions(-) create mode 100644 app/src/test/java/de/jeanlucmakiola/calendula/domain/SharedTextTest.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a7e97b68..e1f6110f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -151,6 +151,19 @@ + + + + + + + + + + + + + Create event + New event Add Quick Settings tile diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/domain/SharedTextTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/domain/SharedTextTest.kt new file mode 100644 index 00000000..e4d12a83 --- /dev/null +++ b/app/src/test/java/de/jeanlucmakiola/calendula/domain/SharedTextTest.kt @@ -0,0 +1,57 @@ +package de.jeanlucmakiola.calendula.domain + +import com.google.common.truth.Truth.assertThat +import org.junit.jupiter.api.Test + +class SharedTextTest { + + @Test + fun `subject becomes the title and the text the description`() { + assertThat(sharedTextOrNull("Dinner", "Friday at Luigi's\nBring wine")) + .isEqualTo(SharedText("Dinner", "Friday at Luigi's\nBring wine")) + } + + @Test + fun `without a subject the first line is the title and the rest the description`() { + assertThat(sharedTextOrNull(null, "\n Dentist \n\nCheck-up, room 4\n")) + .isEqualTo(SharedText("Dentist", "Check-up, room 4")) + } + + @Test + fun `a single line is the title only`() { + assertThat(sharedTextOrNull(" ", " Call mum ")).isEqualTo(SharedText("Call mum", "")) + } + + @Test + fun `subject without text leaves the description empty`() { + assertThat(sharedTextOrNull("Meeting", null)).isEqualTo(SharedText("Meeting", "")) + } + + @Test + fun `a long first line is cut and the full text kept as the description`() { + val line = "a".repeat(300) + val shared = sharedTextOrNull(null, "$line\nmore")!! + assertThat(shared.title).isEqualTo("a".repeat(255)) + assertThat(shared.description).isEqualTo("$line\nmore") + } + + @Test + fun `a long subject is cut and kept in full above the text`() { + val subject = "s".repeat(300) + val shared = sharedTextOrNull(subject, "body")!! + assertThat(shared.title).hasLength(255) + assertThat(shared.description).isEqualTo("$subject\n\nbody") + } + + @Test + fun `the cut never splits a surrogate pair`() { + val shared = sharedTextOrNull(null, "a".repeat(254) + "😀" + "b")!! + assertThat(shared.title).isEqualTo("a".repeat(254)) + } + + @Test + fun `nothing usable is null`() { + assertThat(sharedTextOrNull(null, null)).isNull() + assertThat(sharedTextOrNull(" ", "\n \n")).isNull() + } +}