feat(crash): file reports via the public Codeberg tracker instead of email
Now that a public, writable issue tracker exists (the Codeberg mirror at
codeberg.org/jlmakiola/calendula, which the Gitea issue tab links to),
revert the email hand-off (6150ce6) back to the in-app issue-creation flow:
the crash path opens a prefilled issues/new page (with clipboard copy as the
long-report fallback) and the manual "Report a problem" path opens the issue
template chooser.
The email pivot existed only because the personal Gitea has no public issue
creation (reporters hit a login wall). Codeberg lets anyone register and
file, so that reason is gone. Still no INTERNET permission — the user submits
via the browser themselves. URLs point at Codeberg directly so the prefilled
title/body survive (a Gitea external-tracker redirect wouldn't carry query
params).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,44 +9,25 @@ import androidx.core.net.toUri
|
|||||||
import de.jeanlucmakiola.calendula.R
|
import de.jeanlucmakiola.calendula.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hand the captured crash report off to the user's email app: the report is
|
* Hand the captured crash report off to the user's chosen channel: the report
|
||||||
* copied to the clipboard (a safety net) and a pre-addressed email is composed
|
* is copied to the clipboard (the reliable path for a full stack trace) and the
|
||||||
* with the report as the body. Nothing is sent automatically — the app has no
|
* project's Gitea "new issue" page is opened with the body prefilled. Nothing is
|
||||||
* network access; the user reviews and sends the mail themselves.
|
* sent automatically — the app has no network access; the user reviews and
|
||||||
*
|
* submits the issue 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) {
|
fun submitCrashReport(context: Context, report: String) {
|
||||||
copyReportToClipboard(context, report)
|
copyReportToClipboard(context, report)
|
||||||
val subject = context.getString(R.string.crash_report_issue_title)
|
val opened = runCatching {
|
||||||
val body = context.getString(R.string.crash_report_body_template, "```\n$report\n```")
|
context.startActivity(Intent(Intent.ACTION_VIEW, buildIssueUri(context, report)))
|
||||||
val opened = sendReportEmail(context, subject, body)
|
}.isSuccess
|
||||||
val message = if (opened) R.string.crash_report_copied else R.string.crash_report_open_failed
|
val message = if (opened) R.string.crash_report_copied else R.string.crash_report_open_failed
|
||||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Compose a fresh problem-report email for a manual (non-crash) report. */
|
/** Open the issue tracker's template chooser for a manual (non-crash) report. */
|
||||||
fun reportIssueByEmail(context: Context) {
|
fun openIssueTracker(context: Context) {
|
||||||
val subject = context.getString(R.string.report_issue_subject)
|
val uri = context.getString(R.string.report_issue_choose_url).toUri()
|
||||||
val body = context.getString(R.string.report_issue_body_template)
|
runCatching { context.startActivity(Intent(Intent.ACTION_VIEW, uri)) }
|
||||||
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) {
|
private fun copyReportToClipboard(context: Context, report: String) {
|
||||||
@@ -54,3 +35,27 @@ private fun copyReportToClipboard(context: Context, report: String) {
|
|||||||
val label = context.getString(R.string.crash_report_clip_label)
|
val label = context.getString(R.string.crash_report_clip_label)
|
||||||
clipboard.setPrimaryClip(ClipData.newPlainText(label, report))
|
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
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ import de.jeanlucmakiola.calendula.data.prefs.ThemeMode
|
|||||||
import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref
|
import de.jeanlucmakiola.calendula.data.prefs.WeekStartPref
|
||||||
import de.jeanlucmakiola.calendula.domain.EventFormField
|
import de.jeanlucmakiola.calendula.domain.EventFormField
|
||||||
import de.jeanlucmakiola.calendula.ui.crash.CrashReportDialog
|
import de.jeanlucmakiola.calendula.ui.crash.CrashReportDialog
|
||||||
import de.jeanlucmakiola.calendula.ui.crash.reportIssueByEmail
|
import de.jeanlucmakiola.calendula.ui.crash.openIssueTracker
|
||||||
import de.jeanlucmakiola.calendula.ui.crash.submitCrashReport
|
import de.jeanlucmakiola.calendula.ui.crash.submitCrashReport
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CollapsingScaffold
|
import de.jeanlucmakiola.calendula.ui.common.CollapsingScaffold
|
||||||
import de.jeanlucmakiola.calendula.ui.common.GroupedRow
|
import de.jeanlucmakiola.calendula.ui.common.GroupedRow
|
||||||
@@ -209,10 +209,10 @@ private fun SettingsHub(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composes a problem-report email. If a crash report was captured (and not yet
|
* Opens the project's issue tracker to report a problem. If a crash report was
|
||||||
* sent), it surfaces that report first via the same dialog the next-launch
|
* captured (and not yet sent), it surfaces that report first via the same
|
||||||
* prompt uses; otherwise it opens a blank report email. No data leaves the
|
* dialog the next-launch prompt uses; otherwise it opens the issue template
|
||||||
* device until the user sends the mail themselves.
|
* chooser. No data leaves the device until the user submits the issue.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun ReportProblemRow(position: Position) {
|
private fun ReportProblemRow(position: Position) {
|
||||||
@@ -226,7 +226,7 @@ private fun ReportProblemRow(position: Position) {
|
|||||||
leading = { CategoryIcon(Icons.Default.BugReport, ChipAccent.Neutral) },
|
leading = { CategoryIcon(Icons.Default.BugReport, ChipAccent.Neutral) },
|
||||||
onClick = {
|
onClick = {
|
||||||
val pending = CrashReporter.pendingReport(context)
|
val pending = CrashReporter.pendingReport(context)
|
||||||
if (pending != null) report = pending else reportIssueByEmail(context)
|
if (pending != null) report = pending else openIssueTracker(context)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -283,7 +283,7 @@
|
|||||||
<string name="settings_about_version">Version %1$s</string>
|
<string name="settings_about_version">Version %1$s</string>
|
||||||
<string name="settings_about_logo_desc">Calendula-App-Symbol</string>
|
<string name="settings_about_logo_desc">Calendula-App-Symbol</string>
|
||||||
<string name="settings_report_problem">Problem melden</string>
|
<string name="settings_report_problem">Problem melden</string>
|
||||||
<string name="settings_report_problem_hint">Absturzbericht oder Feedback per E-Mail senden</string>
|
<string name="settings_report_problem_hint">Absturzbericht senden oder Issue-Tracker öffnen</string>
|
||||||
|
|
||||||
<!-- Calendar manager -->
|
<!-- Calendar manager -->
|
||||||
<string name="calendars_title">Kalender</string>
|
<string name="calendars_title">Kalender</string>
|
||||||
@@ -347,9 +347,8 @@
|
|||||||
<string name="crash_dialog_dismiss">Nicht jetzt</string>
|
<string name="crash_dialog_dismiss">Nicht jetzt</string>
|
||||||
<string name="crash_report_issue_title">Absturzbericht</string>
|
<string name="crash_report_issue_title">Absturzbericht</string>
|
||||||
<string name="crash_report_clip_label">Calendula-Absturzbericht</string>
|
<string name="crash_report_clip_label">Calendula-Absturzbericht</string>
|
||||||
<string name="crash_report_copied">Bericht kopiert — E-Mail-App wird geöffnet</string>
|
<string name="crash_report_copied">Bericht in die Zwischenablage kopiert</string>
|
||||||
<string name="crash_report_open_failed">Keine E-Mail-App gefunden. Der Bericht ist in deiner Zwischenablage.</string>
|
<string name="crash_report_open_failed">Der Issue-Tracker konnte nicht geöffnet werden. Der Bericht ist in deiner Zwischenablage.</string>
|
||||||
<string name="crash_report_body_template">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</string>
|
<string name="crash_report_body_template">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</string>
|
||||||
<string name="report_issue_subject">Calendula — Problemmeldung</string>
|
<string name="crash_report_body_paste">_(Der Bericht war zu lang für diesen Link — füge ihn aus deiner Zwischenablage hier ein.)_</string>
|
||||||
<string name="report_issue_body_template">Bitte beschreibe das aufgetretene Problem und was du gerade getan hast.\n\n</string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -280,7 +280,7 @@
|
|||||||
<string name="settings_about_version">Version %1$s</string>
|
<string name="settings_about_version">Version %1$s</string>
|
||||||
<string name="settings_about_logo_desc">Calendula app icon</string>
|
<string name="settings_about_logo_desc">Calendula app icon</string>
|
||||||
<string name="settings_report_problem">Report a problem</string>
|
<string name="settings_report_problem">Report a problem</string>
|
||||||
<string name="settings_report_problem_hint">Send a crash report or feedback by email</string>
|
<string name="settings_report_problem_hint">Send a crash report or open the issue tracker</string>
|
||||||
|
|
||||||
<!-- Calendar manager -->
|
<!-- Calendar manager -->
|
||||||
<string name="calendars_title">Calendars</string>
|
<string name="calendars_title">Calendars</string>
|
||||||
@@ -351,10 +351,10 @@
|
|||||||
<string name="crash_dialog_dismiss">Not now</string>
|
<string name="crash_dialog_dismiss">Not now</string>
|
||||||
<string name="crash_report_issue_title">Crash report</string>
|
<string name="crash_report_issue_title">Crash report</string>
|
||||||
<string name="crash_report_clip_label">Calendula crash report</string>
|
<string name="crash_report_clip_label">Calendula crash report</string>
|
||||||
<string name="crash_report_copied">Report copied — opening your email app</string>
|
<string name="crash_report_copied">Report copied to your clipboard</string>
|
||||||
<string name="crash_report_open_failed">No email app found. The report is on your clipboard.</string>
|
<string name="crash_report_open_failed">Couldn\'t open the issue tracker. The report is on your clipboard.</string>
|
||||||
<string name="crash_report_body_template">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</string>
|
<string name="crash_report_body_template">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</string>
|
||||||
<string name="report_issue_email" translatable="false">mail@jeanlucmakiola.de</string>
|
<string name="crash_report_body_paste">_(The report was too long for this link — paste it from your clipboard here.)_</string>
|
||||||
<string name="report_issue_subject">Calendula — problem report</string>
|
<string name="report_issue_url" translatable="false">https://codeberg.org/jlmakiola/calendula/issues/new</string>
|
||||||
<string name="report_issue_body_template">Please describe the problem you ran into, and what you were doing when it happened.\n\n</string>
|
<string name="report_issue_choose_url" translatable="false">https://codeberg.org/jlmakiola/calendula/issues/new/choose</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user