From e17a1623abaabcd6ebbedaf73ad44be8d52829a0 Mon Sep 17 00:00:00 2001 From: Himanth Reddy Date: Wed, 1 Jul 2026 15:31:19 +0530 Subject: [PATCH 1/2] feat: implement Discord crash reporting flow for TV and mobile --- app/src/main/AndroidManifest.xml | 8 + .../kotlin/com/arflix/tv/ArflixApplication.kt | 24 ++ .../ui/screens/crash/CrashReportActivity.kt | 238 ++++++++++++++++++ .../com/arflix/tv/util/SentryCrashReporter.kt | 16 ++ web/app/report/page.tsx | 197 +++++++++++++++ 5 files changed, 483 insertions(+) create mode 100644 app/src/main/kotlin/com/arflix/tv/ui/screens/crash/CrashReportActivity.kt create mode 100644 web/app/report/page.tsx diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8b8bbe363..4f2fb864c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -69,6 +69,14 @@ + + + + try { + val prefs = getSharedPreferences("arvio_crash_store", android.content.Context.MODE_PRIVATE) + val eventId = prefs.getString("last_crash_id", null) ?: java.util.UUID.randomUUID().toString() + prefs.edit() + .putString("last_crash_id", eventId) + .putString("last_crash_msg", "${throwable::class.java.simpleName}: ${throwable.message?.take(200) ?: ""}") + .putLong("last_crash_time", System.currentTimeMillis()) + .putString("last_crash_version", "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") + .commit() + + val crashIntent = android.content.Intent(this, com.arflix.tv.ui.screens.crash.CrashReportActivity::class.java).apply { + addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK or android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK) + putExtra(com.arflix.tv.ui.screens.crash.CrashReportActivity.EXTRA_CRASH_ID, eventId) + putExtra(com.arflix.tv.ui.screens.crash.CrashReportActivity.EXTRA_CRASH_MSG, "${throwable::class.java.simpleName}: ${throwable.message?.take(200) ?: ""}") + } + startActivity(crashIntent) + } catch (_: Throwable) { + } finally { + defaultHandler?.uncaughtException(thread, throwable) ?: android.os.Process.killProcess(android.os.Process.myPid()) + } + } // Initialize active profile asynchronously to avoid blocking cold start. // Wire realtime push notification when realtime is enabled. cloudSyncRepository.onPushCompleted = { realtimeSyncManager.markPush() } diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/crash/CrashReportActivity.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/crash/CrashReportActivity.kt new file mode 100644 index 000000000..1b8b3f307 --- /dev/null +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/crash/CrashReportActivity.kt @@ -0,0 +1,238 @@ +package com.arflix.tv.ui.screens.crash + +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.arflix.tv.MainActivity +import com.arflix.tv.ui.components.QrCodeImage +import com.arflix.tv.ui.theme.ArflixTvTheme +import com.arflix.tv.util.DeviceType +import com.arflix.tv.util.detectDeviceType +import com.arflix.tv.util.deviceHasTouchScreen +import java.text.SimpleDateFormat +import java.util.* + +class CrashReportActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val prefs = getSharedPreferences("arvio_crash_store", Context.MODE_PRIVATE) + val crashId = intent.getStringExtra(EXTRA_CRASH_ID) ?: prefs.getString("last_crash_id", "N/A") ?: "N/A" + val crashMsg = intent.getStringExtra(EXTRA_CRASH_MSG) ?: prefs.getString("last_crash_msg", "Unexpected error") ?: "Unexpected error" + val crashTime = intent.getLongExtra(EXTRA_CRASH_TIME, prefs.getLong("last_crash_time", System.currentTimeMillis())) + val crashVersion = prefs.getString("last_crash_version", "1.0") ?: "1.0" + + setContent { + ArflixTvTheme { + CrashReportScreen( + crashId = crashId, + crashMsg = crashMsg, + crashTime = crashTime, + crashVersion = crashVersion, + onRestartApp = { + val restartIntent = Intent(this@CrashReportActivity, MainActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + } + startActivity(restartIntent) + finish() + }, + onClose = { finishAffinity() } + ) + } + } + } + + companion object { + const val EXTRA_CRASH_ID = "extra_crash_id" + const val EXTRA_CRASH_MSG = "extra_crash_msg" + const val EXTRA_CRASH_TIME = "extra_crash_time" + const val DISCORD_BUG_CHANNEL_URL = "https://discord.gg/UavuEYMfQ4" + } +} + +@Composable +fun CrashReportScreen( + crashId: String, + crashMsg: String, + crashTime: Long, + crashVersion: String, + onRestartApp: () -> Unit, + onClose: () -> Unit +) { + val context = LocalContext.current + val deviceType = detectDeviceType(context) + val hasTouch = deviceHasTouchScreen(context) + val isTv = deviceType == DeviceType.TV || !hasTouch + + val timeString = remember(crashTime) { + val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) + sdf.format(Date(crashTime)) + } + + val sentryLink = remember(crashId) { + if (crashId != "N/A") "https://sentry.io/issues/?query=id%3A$crashId" else "N/A" + } + + val formattedReport = remember(crashId, sentryLink, crashMsg, crashVersion, timeString) { + """ + **🚨 ARVIO Crash Report** + **Crash ID:** `$crashId` + **Sentry Link:** $sentryLink + **Version:** $crashVersion + **Time:** $timeString + **Error:** $crashMsg + """.trimIndent() + } + + val webBridgeUrl = remember(crashId, crashVersion, crashMsg, crashTime) { + val encodedErr = Uri.encode(crashMsg.take(300)) + val encodedV = Uri.encode(crashVersion) + "https://arvio.tv/report?id=$crashId&v=$encodedV&err=$encodedErr&t=$crashTime" + } + + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFF0F1115)) + .padding(24.dp), + contentAlignment = Alignment.Center + ) { + Column( + modifier = Modifier + .widthIn(max = if (isTv) 760.dp else 480.dp) + .verticalScroll(rememberScrollState()), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + Text( + text = "⚠️ ARVIO Encountered an Error", + color = Color.White, + fontSize = if (isTv) 26.sp else 22.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center + ) + + Text( + text = if (isTv) { + "Scan the QR code below with your phone camera to automatically copy the crash report & open our Discord bug channel." + } else { + "We apologize for the interruption. You can report this crash directly to our Discord channel to help us fix it." + }, + color = Color(0xFFA0A6B2), + fontSize = 14.sp, + textAlign = TextAlign.Center + ) + + if (isTv) { + // TV layout: Show clean QR Code prominently pointing to arvio.tv/report + Surface( + shape = RoundedCornerShape(16.dp), + color = Color.White, + modifier = Modifier.padding(vertical = 8.dp) + ) { + QrCodeImage( + data = webBridgeUrl, + sizePx = 320, + modifier = Modifier + .size(220.dp) + .padding(16.dp) + ) + } + + Text( + text = "Scan to open arvio.tv — 1 tap to copy report & jump into Discord.", + color = Color(0xFF00F0D0), + fontSize = 14.sp, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Center + ) + } + + // Crash Details Card + Surface( + shape = RoundedCornerShape(12.dp), + color = Color(0xFF181C24), + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = "Crash Reference Details", + color = Color.White, + fontSize = 13.sp, + fontWeight = FontWeight.Bold + ) + Text( + text = "ID: $crashId\nTime: $timeString\nVersion: $crashVersion\nError: $crashMsg", + color = Color(0xFFD0D5DD), + fontSize = 12.sp, + fontFamily = FontFamily.Monospace + ) + } + } + + // Action Buttons + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally) + ) { + if (!isTv) { + Button( + onClick = { + val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText("ARVIO Crash Report", formattedReport) + clipboard.setPrimaryClip(clip) + Toast.makeText(context, "Crash details copied! Opening Discord...", Toast.LENGTH_LONG).show() + + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(CrashReportActivity.DISCORD_BUG_CHANNEL_URL)).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + context.startActivity(intent) + }, + colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF5865F2)), + shape = RoundedCornerShape(8.dp), + modifier = Modifier.weight(1f) + ) { + Text("Report on Discord", color = Color.White, fontWeight = FontWeight.SemiBold) + } + } + + Button( + onClick = onRestartApp, + colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF00F0D0)), + shape = RoundedCornerShape(8.dp), + modifier = Modifier.weight(if (isTv) 0.6f else 1f) + ) { + Text("Restart ARVIO", color = Color.Black, fontWeight = FontWeight.Bold) + } + } + } + } +} diff --git a/app/src/main/kotlin/com/arflix/tv/util/SentryCrashReporter.kt b/app/src/main/kotlin/com/arflix/tv/util/SentryCrashReporter.kt index 6fab12abb..b4188efa1 100644 --- a/app/src/main/kotlin/com/arflix/tv/util/SentryCrashReporter.kt +++ b/app/src/main/kotlin/com/arflix/tv/util/SentryCrashReporter.kt @@ -46,6 +46,22 @@ object SentryCrashReporter : AppLogger.CrashContextProvider { if (!CrashReportFilter.shouldSendSentryEvent(event.throwable, event.level)) { return@setBeforeSend null } + runCatching { + val prefs = context.getSharedPreferences("arvio_crash_store", Context.MODE_PRIVATE) + val eventId = event.eventId.toString() + val throwable = event.throwable + val msg = if (throwable != null) { + "${throwable::class.java.simpleName}: ${throwable.message?.take(200) ?: ""}" + } else { + event.message?.formatted ?: "Crash event" + } + prefs.edit() + .putString("last_crash_id", eventId) + .putString("last_crash_msg", msg) + .putLong("last_crash_time", System.currentTimeMillis()) + .putString("last_crash_version", "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") + .commit() + } event.setUser(null) event.setServerName(null) event.setRequest(null) diff --git a/web/app/report/page.tsx b/web/app/report/page.tsx new file mode 100644 index 000000000..b818436be --- /dev/null +++ b/web/app/report/page.tsx @@ -0,0 +1,197 @@ +"use client"; + +import React, { Suspense, useEffect, useState } from "react"; +import { useSearchParams } from "next/navigation"; + +const DISCORD_URL = "https://discord.gg/UavuEYMfQ4"; + +function ReportContent() { + const searchParams = useSearchParams(); + const id = searchParams.get("id") || "N/A"; + const version = searchParams.get("v") || searchParams.get("version") || "1.0"; + const error = searchParams.get("err") || searchParams.get("error") || "Unexpected crash"; + const timeParam = searchParams.get("t") || searchParams.get("time"); + + const [copied, setCopied] = useState(false); + const [timeStr, setTimeStr] = useState("Just now"); + + useEffect(() => { + if (timeParam) { + const parsed = parseInt(timeParam, 10); + if (!isNaN(parsed)) { + setTimeStr(new Date(parsed).toLocaleString()); + } else { + setTimeStr(timeParam); + } + } else { + setTimeStr(new Date().toLocaleString()); + } + }, [timeParam]); + + const sentryLink = id !== "N/A" ? `https://sentry.io/issues/?query=id%3A${id}` : "N/A"; + + const reportText = `**🚨 ARVIO Crash Report** +**Crash ID:** \`${id}\` +**Sentry Link:** ${sentryLink} +**Version:** ${version} +**Time:** ${timeStr} +**Error:** ${error}`; + + const handleCopyAndRedirect = async () => { + try { + if (navigator.clipboard) { + await navigator.clipboard.writeText(reportText); + } else { + const textArea = document.createElement("textarea"); + textArea.value = reportText; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand("copy"); + document.body.removeChild(textArea); + } + } catch { + // Fallback ignore + } + + setCopied(true); + setTimeout(() => { + window.location.href = DISCORD_URL; + }, 700); + }; + + return ( +
+
+
+ 🚨 +
+ +

+ ARVIO Crash Diagnostics +

+

+ Tap below to instantly copy this bug report to your phone clipboard and open the ARVIO Discord bug channel. +

+ +
+
+ Formatted Bug Report +
+
+            {reportText}
+          
+
+ + + + +
+
+ ); +} + +export default function ReportPage() { + return ( + }> + + + ); +} From 54ab15bf207bdaeead820d67d1533a0ca37f6e50 Mon Sep 17 00:00:00 2001 From: Himanth Reddy Date: Thu, 2 Jul 2026 10:35:47 +0530 Subject: [PATCH 2/2] feat: implement web-based crash reporting diagnostic page and update crash handling flow --- .../kotlin/com/arflix/tv/ArflixApplication.kt | 12 +- .../main/kotlin/com/arflix/tv/MainActivity.kt | 15 ++ .../ui/screens/crash/CrashReportActivity.kt | 29 ++- .../com/arflix/tv/util/SentryCrashReporter.kt | 7 +- netlify-arvio-tv-site/_redirects | 2 + netlify-arvio-tv-site/report/index.html | 212 ++++++++++++++++++ web/app/report/page.tsx | 2 +- 7 files changed, 265 insertions(+), 14 deletions(-) create mode 100644 netlify-arvio-tv-site/_redirects create mode 100644 netlify-arvio-tv-site/report/index.html diff --git a/app/src/main/kotlin/com/arflix/tv/ArflixApplication.kt b/app/src/main/kotlin/com/arflix/tv/ArflixApplication.kt index 8464001b6..ef41a95ca 100644 --- a/app/src/main/kotlin/com/arflix/tv/ArflixApplication.kt +++ b/app/src/main/kotlin/com/arflix/tv/ArflixApplication.kt @@ -108,20 +108,14 @@ class ArflixApplication : Application(), Configuration.Provider, ImageLoaderFact Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> try { val prefs = getSharedPreferences("arvio_crash_store", android.content.Context.MODE_PRIVATE) - val eventId = prefs.getString("last_crash_id", null) ?: java.util.UUID.randomUUID().toString() + val fallbackId = java.util.UUID.randomUUID().toString() prefs.edit() - .putString("last_crash_id", eventId) + .putString("last_crash_id", fallbackId) .putString("last_crash_msg", "${throwable::class.java.simpleName}: ${throwable.message?.take(200) ?: ""}") .putLong("last_crash_time", System.currentTimeMillis()) .putString("last_crash_version", "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") + .putBoolean("has_pending_crash_report", true) .commit() - - val crashIntent = android.content.Intent(this, com.arflix.tv.ui.screens.crash.CrashReportActivity::class.java).apply { - addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK or android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK) - putExtra(com.arflix.tv.ui.screens.crash.CrashReportActivity.EXTRA_CRASH_ID, eventId) - putExtra(com.arflix.tv.ui.screens.crash.CrashReportActivity.EXTRA_CRASH_MSG, "${throwable::class.java.simpleName}: ${throwable.message?.take(200) ?: ""}") - } - startActivity(crashIntent) } catch (_: Throwable) { } finally { defaultHandler?.uncaughtException(thread, throwable) ?: android.os.Process.killProcess(android.os.Process.myPid()) diff --git a/app/src/main/kotlin/com/arflix/tv/MainActivity.kt b/app/src/main/kotlin/com/arflix/tv/MainActivity.kt index 0489f76c7..16e4b3f09 100644 --- a/app/src/main/kotlin/com/arflix/tv/MainActivity.kt +++ b/app/src/main/kotlin/com/arflix/tv/MainActivity.kt @@ -205,6 +205,21 @@ class MainActivity : ComponentActivity() { overridePendingTransition(0, 0) pendingLauncherRequest = parseLauncherRequest(intent) + val crashPrefs = getSharedPreferences("arvio_crash_store", Context.MODE_PRIVATE) + if (crashPrefs.getBoolean("has_pending_crash_report", false)) { + val crashId = crashPrefs.getString("last_crash_id", "N/A") + val crashMsg = crashPrefs.getString("last_crash_msg", "Unexpected error") + val crashTime = crashPrefs.getLong("last_crash_time", System.currentTimeMillis()) + crashPrefs.edit().putBoolean("has_pending_crash_report", false).commit() + + val crashIntent = android.content.Intent(this, com.arflix.tv.ui.screens.crash.CrashReportActivity::class.java).apply { + putExtra(com.arflix.tv.ui.screens.crash.CrashReportActivity.EXTRA_CRASH_ID, crashId) + putExtra(com.arflix.tv.ui.screens.crash.CrashReportActivity.EXTRA_CRASH_MSG, crashMsg) + putExtra(com.arflix.tv.ui.screens.crash.CrashReportActivity.EXTRA_CRASH_TIME, crashTime) + } + startActivity(crashIntent) + } + // Set orientation based on device type requestedOrientation = when (initialDeviceType) { DeviceType.TV -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE diff --git a/app/src/main/kotlin/com/arflix/tv/ui/screens/crash/CrashReportActivity.kt b/app/src/main/kotlin/com/arflix/tv/ui/screens/crash/CrashReportActivity.kt index 1b8b3f307..5fbe30226 100644 --- a/app/src/main/kotlin/com/arflix/tv/ui/screens/crash/CrashReportActivity.kt +++ b/app/src/main/kotlin/com/arflix/tv/ui/screens/crash/CrashReportActivity.kt @@ -10,6 +10,7 @@ import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background +import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape @@ -18,6 +19,9 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontFamily @@ -87,6 +91,15 @@ fun CrashReportScreen( val hasTouch = deviceHasTouchScreen(context) val isTv = deviceType == DeviceType.TV || !hasTouch + val restartFocusRequester = remember { FocusRequester() } + var isRestartFocused by remember { mutableStateOf(false) } + + LaunchedEffect(isTv) { + if (isTv) { + runCatching { restartFocusRequester.requestFocus() } + } + } + val timeString = remember(crashTime) { val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) sdf.format(Date(crashTime)) @@ -226,9 +239,21 @@ fun CrashReportScreen( Button( onClick = onRestartApp, - colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF00F0D0)), + colors = ButtonDefaults.buttonColors( + containerColor = if (isRestartFocused) Color.White else Color(0xFF00F0D0) + ), shape = RoundedCornerShape(8.dp), - modifier = Modifier.weight(if (isTv) 0.6f else 1f) + modifier = Modifier + .weight(if (isTv) 0.6f else 1f) + .focusRequester(restartFocusRequester) + .onFocusChanged { isRestartFocused = it.isFocused } + .then( + if (isRestartFocused) { + Modifier.border(2.dp, Color(0xFF00F0D0), RoundedCornerShape(8.dp)) + } else { + Modifier + } + ) ) { Text("Restart ARVIO", color = Color.Black, fontWeight = FontWeight.Bold) } diff --git a/app/src/main/kotlin/com/arflix/tv/util/SentryCrashReporter.kt b/app/src/main/kotlin/com/arflix/tv/util/SentryCrashReporter.kt index b4188efa1..099e9530e 100644 --- a/app/src/main/kotlin/com/arflix/tv/util/SentryCrashReporter.kt +++ b/app/src/main/kotlin/com/arflix/tv/util/SentryCrashReporter.kt @@ -55,12 +55,15 @@ object SentryCrashReporter : AppLogger.CrashContextProvider { } else { event.message?.formatted ?: "Crash event" } - prefs.edit() + val editor = prefs.edit() .putString("last_crash_id", eventId) .putString("last_crash_msg", msg) .putLong("last_crash_time", System.currentTimeMillis()) .putString("last_crash_version", "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") - .commit() + if (event.isCrashed == true || event.level == SentryLevel.FATAL) { + editor.putBoolean("has_pending_crash_report", true) + } + editor.commit() } event.setUser(null) event.setServerName(null) diff --git a/netlify-arvio-tv-site/_redirects b/netlify-arvio-tv-site/_redirects new file mode 100644 index 000000000..38c3c6d1e --- /dev/null +++ b/netlify-arvio-tv-site/_redirects @@ -0,0 +1,2 @@ +/report /report/index.html 200 +/report/ /report/index.html 200 diff --git a/netlify-arvio-tv-site/report/index.html b/netlify-arvio-tv-site/report/index.html new file mode 100644 index 000000000..ef6624bd2 --- /dev/null +++ b/netlify-arvio-tv-site/report/index.html @@ -0,0 +1,212 @@ + + + + + + ARVIO Crash Diagnostics + + + + + + + +
+
🚨
+

ARVIO Crash Diagnostics

+

+ Tap below to instantly copy this bug report to your phone clipboard and open the ARVIO Discord bug channel. +

+ +
+
Formatted Bug Report
+
Loading diagnostic report...
+
+ + + + +
+ + + + diff --git a/web/app/report/page.tsx b/web/app/report/page.tsx index b818436be..2f8939239 100644 --- a/web/app/report/page.tsx +++ b/web/app/report/page.tsx @@ -11,7 +11,7 @@ function ReportContent() { const version = searchParams.get("v") || searchParams.get("version") || "1.0"; const error = searchParams.get("err") || searchParams.get("error") || "Unexpected crash"; const timeParam = searchParams.get("t") || searchParams.get("time"); - + const [copied, setCopied] = useState(false); const [timeStr, setTimeStr] = useState("Just now");