feat: Implement crash reporting system with CrashLogsScreen and integration in ProfileScreen

This commit is contained in:
k1ngsterr1
2026-01-25 02:33:56 +05:00
parent 766ab84f8c
commit c8214cdfa3
7 changed files with 878 additions and 1 deletions

View File

@@ -0,0 +1,337 @@
package com.rosetta.messenger.ui.crashlogs
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.BugReport
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.rosetta.messenger.utils.CrashReportManager
import java.text.SimpleDateFormat
import java.util.*
/**
* Экран для просмотра crash logs
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CrashLogsScreen(
onBackClick: () -> Unit
) {
val context = LocalContext.current
var crashReports by remember { mutableStateOf<List<CrashReportManager.CrashReport>>(emptyList()) }
var selectedReport by remember { mutableStateOf<CrashReportManager.CrashReport?>(null) }
var showDeleteAllDialog by remember { mutableStateOf(false) }
var showDeleteDialog by remember { mutableStateOf<String?>(null) }
// Загружаем crash reports
LaunchedEffect(Unit) {
crashReports = CrashReportManager.getCrashReports(context)
}
// Функция для обновления списка
fun refreshReports() {
crashReports = CrashReportManager.getCrashReports(context)
}
if (selectedReport != null) {
// Показываем детали краша
CrashDetailScreen(
crashReport = selectedReport!!,
onBackClick = { selectedReport = null },
onDelete = {
CrashReportManager.deleteCrashReport(context, selectedReport!!.fileName)
refreshReports()
selectedReport = null
}
)
} else {
// Список крашей
Scaffold(
topBar = {
TopAppBar(
title = { Text("Crash Logs") },
navigationIcon = {
IconButton(onClick = onBackClick) {
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
}
},
actions = {
if (crashReports.isNotEmpty()) {
IconButton(onClick = { showDeleteAllDialog = true }) {
Icon(Icons.Default.Delete, contentDescription = "Delete All")
}
}
}
)
}
) { paddingValues ->
if (crashReports.isEmpty()) {
// Пустое состояние
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Icon(
imageVector = Icons.Default.BugReport,
contentDescription = null,
modifier = Modifier.size(64.dp),
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)
)
Text(
text = "No crash reports",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
Text(
text = "Great! Your app is running smoothly",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)
)
}
}
} else {
// Список crash reports
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(crashReports) { report ->
CrashReportItem(
crashReport = report,
onClick = { selectedReport = report },
onDelete = { showDeleteDialog = report.fileName }
)
}
}
}
}
// Диалог удаления всех
if (showDeleteAllDialog) {
AlertDialog(
onDismissRequest = { showDeleteAllDialog = false },
title = { Text("Delete All Crash Reports?") },
text = { Text("This will permanently delete all ${crashReports.size} crash reports.") },
confirmButton = {
TextButton(
onClick = {
CrashReportManager.deleteAllCrashReports(context)
refreshReports()
showDeleteAllDialog = false
}
) {
Text("Delete All")
}
},
dismissButton = {
TextButton(onClick = { showDeleteAllDialog = false }) {
Text("Cancel")
}
}
)
}
// Диалог удаления одного
if (showDeleteDialog != null) {
AlertDialog(
onDismissRequest = { showDeleteDialog = null },
title = { Text("Delete Crash Report?") },
text = { Text("This will permanently delete this crash report.") },
confirmButton = {
TextButton(
onClick = {
CrashReportManager.deleteCrashReport(context, showDeleteDialog!!)
refreshReports()
showDeleteDialog = null
}
) {
Text("Delete")
}
},
dismissButton = {
TextButton(onClick = { showDeleteDialog = null }) {
Text("Cancel")
}
}
)
}
}
}
/**
* Элемент списка с crash report
*/
@Composable
private fun CrashReportItem(
crashReport: CrashReportManager.CrashReport,
onClick: () -> Unit,
onDelete: () -> Unit
) {
val dateFormat = remember { SimpleDateFormat("dd MMM yyyy, HH:mm:ss", Locale.getDefault()) }
val exceptionType = remember {
crashReport.content.lines()
.find { it.startsWith("Exception Type:") }
?.substringAfter("Exception Type: ")
?.substringAfterLast(".")
?: "Unknown"
}
Card(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer.copy(alpha = 0.3f)
)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.BugReport,
contentDescription = null,
modifier = Modifier.size(20.dp),
tint = MaterialTheme.colorScheme.error
)
Text(
text = exceptionType,
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
}
Text(
text = dateFormat.format(Date(crashReport.timestamp)),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
IconButton(onClick = onDelete) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "Delete",
tint = MaterialTheme.colorScheme.error
)
}
}
}
}
/**
* Детальный просмотр crash report
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CrashDetailScreen(
crashReport: CrashReportManager.CrashReport,
onBackClick: () -> Unit,
onDelete: () -> Unit
) {
var showDeleteDialog by remember { mutableStateOf(false) }
Scaffold(
topBar = {
TopAppBar(
title = { Text("Crash Details") },
navigationIcon = {
IconButton(onClick = onBackClick) {
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
}
},
actions = {
IconButton(onClick = { /* TODO: Share */ }) {
Icon(Icons.Default.Share, contentDescription = "Share")
}
IconButton(onClick = { showDeleteDialog = true }) {
Icon(Icons.Default.Delete, contentDescription = "Delete")
}
}
)
}
) { paddingValues ->
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.background(MaterialTheme.colorScheme.surface),
contentPadding = PaddingValues(16.dp)
) {
item {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant
)
) {
Text(
text = crashReport.content,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
fontFamily = FontFamily.Monospace,
fontSize = 12.sp,
lineHeight = 18.sp
)
}
}
}
if (showDeleteDialog) {
AlertDialog(
onDismissRequest = { showDeleteDialog = false },
title = { Text("Delete Crash Report?") },
text = { Text("This will permanently delete this crash report.") },
confirmButton = {
TextButton(
onClick = {
showDeleteDialog = false
onDelete()
}
) {
Text("Delete")
}
},
dismissButton = {
TextButton(onClick = { showDeleteDialog = false }) {
Text("Cancel")
}
}
)
}
}
}

View File

@@ -148,6 +148,7 @@ fun ProfileScreen(
onNavigateToTheme: () -> Unit = {},
onNavigateToSafety: () -> Unit = {},
onNavigateToLogs: () -> Unit = {},
onNavigateToCrashLogs: () -> Unit = {},
viewModel: ProfileViewModel = androidx.lifecycle.viewmodel.compose.viewModel(),
avatarRepository: AvatarRepository? = null,
dialogDao: com.rosetta.messenger.database.DialogDao? = null
@@ -425,6 +426,14 @@ fun ProfileScreen(
title = "Safety",
onClick = onNavigateToSafety,
isDarkTheme = isDarkTheme,
showDivider = true
)
TelegramSettingsItem(
icon = TablerIcons.Bug,
title = "Crash Logs",
onClick = onNavigateToCrashLogs,
isDarkTheme = isDarkTheme,
showDivider = biometricAvailable is BiometricAvailability.Available
)