From 6150ce633fa19006d461826e5d7837796b1ee943 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sun, 21 Jun 2026 21:52:19 +0200 Subject: [PATCH] feat(crash): hand off reports by email instead of the Gitea issue tracker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filing a Gitea issue requires an account on the instance, so anonymous reporters hit a login wall — Gitea has no anonymous issue creation. Switch both the crash-report and manual problem-report paths to compose a pre-addressed email via ACTION_SENDTO (mailto:), which needs no account and preserves the existing no-INTERNET, user-sends-it-themselves model. The full report rides in EXTRA_TEXT, so the old URL-length cap and clipboard-paste fallback are gone (clipboard copy stays as a safety net). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../calendula/ui/crash/CrashReportSubmit.kt | 67 +++++++++---------- .../calendula/ui/settings/SettingsScreen.kt | 12 ++-- app/src/main/res/values-de/strings.xml | 9 +-- app/src/main/res/values/strings.xml | 14 ++-- 4 files changed, 49 insertions(+), 53 deletions(-) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/crash/CrashReportSubmit.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/crash/CrashReportSubmit.kt index 1477beb..80b7259 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/crash/CrashReportSubmit.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/crash/CrashReportSubmit.kt @@ -9,25 +9,44 @@ import androidx.core.net.toUri import de.jeanlucmakiola.calendula.R /** - * Hand the captured crash report off to the user's chosen channel: the report - * is copied to the clipboard (the reliable path for a full stack trace) and the - * project's Gitea "new issue" page is opened with the body prefilled. Nothing is - * sent automatically — the app has no network access; the user reviews and - * submits the issue themselves. + * Hand the captured crash report off to the user's email app: the report is + * copied to the clipboard (a safety net) and a pre-addressed email is composed + * with the report as the body. Nothing is sent automatically — the app has no + * network access; the user reviews and sends the mail themselves. + * + * Email is used rather than the web issue tracker because filing a Gitea issue + * requires an account on the instance, which anonymous reporters don't have — + * whereas anyone can send an email from the app they already use. */ fun submitCrashReport(context: Context, report: String) { copyReportToClipboard(context, report) - val opened = runCatching { - context.startActivity(Intent(Intent.ACTION_VIEW, buildIssueUri(context, report))) - }.isSuccess + val subject = context.getString(R.string.crash_report_issue_title) + val body = context.getString(R.string.crash_report_body_template, "```\n$report\n```") + val opened = sendReportEmail(context, subject, body) val message = if (opened) R.string.crash_report_copied else R.string.crash_report_open_failed Toast.makeText(context, message, Toast.LENGTH_LONG).show() } -/** Open the issue tracker's template chooser for a manual (non-crash) report. */ -fun openIssueTracker(context: Context) { - val uri = context.getString(R.string.report_issue_choose_url).toUri() - runCatching { context.startActivity(Intent(Intent.ACTION_VIEW, uri)) } +/** Compose a fresh problem-report email for a manual (non-crash) report. */ +fun reportIssueByEmail(context: Context) { + val subject = context.getString(R.string.report_issue_subject) + val body = context.getString(R.string.report_issue_body_template) + sendReportEmail(context, subject, body) +} + +/** + * Open the user's email app with a pre-addressed, prefilled message. The + * `mailto:` data URI guarantees only email apps resolve the intent (a plain + * ACTION_SEND would also offer messengers). The body rides in an extra rather + * than the URI, so there is no URL-length ceiling on the report. + */ +private fun sendReportEmail(context: Context, subject: String, body: String): Boolean { + val email = context.getString(R.string.report_issue_email) + val intent = Intent(Intent.ACTION_SENDTO, "mailto:$email".toUri()).apply { + putExtra(Intent.EXTRA_SUBJECT, subject) + putExtra(Intent.EXTRA_TEXT, body) + } + return runCatching { context.startActivity(intent) }.isSuccess } private fun copyReportToClipboard(context: Context, report: String) { @@ -35,27 +54,3 @@ private fun copyReportToClipboard(context: Context, report: String) { val label = context.getString(R.string.crash_report_clip_label) clipboard.setPrimaryClip(ClipData.newPlainText(label, report)) } - -/** - * The Gitea `issues/new` URL with `title` and `body` prefilled. A full report - * can blow past URL-length limits, so an over-long one is left out of the link - * (with a "paste from clipboard" placeholder) — the clipboard copy is the - * source of truth in that case. - */ -private fun buildIssueUri(context: Context, report: String) = - context.getString(R.string.report_issue_url).toUri().buildUpon() - .appendQueryParameter("title", context.getString(R.string.crash_report_issue_title)) - .appendQueryParameter("body", buildIssueBody(context, report)) - .build() - -private fun buildIssueBody(context: Context, report: String): String { - val block = if (report.length > MAX_URL_REPORT_CHARS) { - context.getString(R.string.crash_report_body_paste) - } else { - "```\n$report\n```" - } - return context.getString(R.string.crash_report_body_template, block) -} - -/** Keep the prefilled body comfortably under common URL-length ceilings. */ -private const val MAX_URL_REPORT_CHARS = 6_000 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 4d1a06f..f7c6cc6 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 @@ -80,7 +80,7 @@ import de.jeanlucmakiola.calendula.data.prefs.ThemeMode import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref import de.jeanlucmakiola.calendula.domain.EventFormField import de.jeanlucmakiola.calendula.ui.crash.CrashReportDialog -import de.jeanlucmakiola.calendula.ui.crash.openIssueTracker +import de.jeanlucmakiola.calendula.ui.crash.reportIssueByEmail import de.jeanlucmakiola.calendula.ui.crash.submitCrashReport import de.jeanlucmakiola.calendula.ui.common.CollapsingScaffold import de.jeanlucmakiola.calendula.ui.common.GroupedRow @@ -209,10 +209,10 @@ private fun SettingsHub( } /** - * Opens the project's issue tracker to report a problem. If a crash report was - * captured (and not yet sent), it surfaces that report first via the same - * dialog the next-launch prompt uses; otherwise it opens the issue template - * chooser. No data leaves the device until the user submits the issue. + * Composes a problem-report email. If a crash report was captured (and not yet + * sent), it surfaces that report first via the same dialog the next-launch + * prompt uses; otherwise it opens a blank report email. No data leaves the + * device until the user sends the mail themselves. */ @Composable private fun ReportProblemRow(position: Position) { @@ -226,7 +226,7 @@ private fun ReportProblemRow(position: Position) { leading = { CategoryIcon(Icons.Default.BugReport, ChipAccent.Neutral) }, onClick = { val pending = CrashReporter.pendingReport(context) - if (pending != null) report = pending else openIssueTracker(context) + if (pending != null) report = pending else reportIssueByEmail(context) }, ) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4ec4c11..3fdeba9 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -283,7 +283,7 @@ Version %1$s Calendula-App-Symbol Problem melden - Absturzbericht senden oder Issue-Tracker öffnen + Absturzbericht oder Feedback per E-Mail senden Kalender @@ -347,8 +347,9 @@ Nicht jetzt Absturzbericht Calendula-Absturzbericht - Bericht in die Zwischenablage kopiert - Der Issue-Tracker konnte nicht geöffnet werden. Der Bericht ist in deiner Zwischenablage. + Bericht kopiert — E-Mail-App wird geöffnet + Keine E-Mail-App gefunden. Der Bericht ist in deiner Zwischenablage. Danke, dass du einen Absturz in Calendula meldest. Bitte ergänze, was du gerade getan hast, und sende dann ab.\n\n### Was ist passiert\n\n\n### Absturzbericht\n%1$s\n - _(Der Bericht war zu lang für diesen Link — füge ihn aus deiner Zwischenablage hier ein.)_ + Calendula — Problemmeldung + Bitte beschreibe das aufgetretene Problem und was du gerade getan hast.\n\n diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7801091..0be0675 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -280,7 +280,7 @@ Version %1$s Calendula app icon Report a problem - Send a crash report or open the issue tracker + Send a crash report or feedback by email Calendars @@ -351,10 +351,10 @@ Not now Crash report Calendula crash report - Report copied to your clipboard - Couldn\'t open the issue tracker. The report is on your clipboard. - Thanks for reporting a crash in Calendula. Please add anything you remember about what you were doing, then submit.\n\n### What happened\n\n\n### Crash report\n%1$s\n - _(The report was too long for this link — paste it from your clipboard here.)_ - https://gitea.jeanlucmakiola.de/makiolaj/calendula/issues/new - https://gitea.jeanlucmakiola.de/makiolaj/calendula/issues/new/choose + Report copied — opening your email app + No email app found. The report is on your clipboard. + Thanks for reporting a crash in Calendula. Please add anything you remember about what you were doing, then send.\n\n### What happened\n\n\n### Crash report\n%1$s\n + mail@jeanlucmakiola.de + Calendula — problem report + Please describe the problem you ran into, and what you were doing when it happened.\n\n