Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions lib/features/all_events/presentation/detail/error_detail.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import '../../../../core/constants/app_constants.dart';
import '../../../../core/utils/smooth_scroll_controller.dart';
import '../../../../core/utils/toast_utils.dart';
import '../../../../models/log/error_event.dart';
import '../shared/copy_button.dart';
import '../shared/error_block.dart';
import 'platform_badge.dart';
import 'severity_badge.dart';
Comment on lines +4 to +11

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Import app_localizations.dart and error_tokens.dart to support internationalization and proper formatting of platform labels.

Suggested change
import '../../../../core/constants/app_constants.dart';
import '../../../../core/utils/smooth_scroll_controller.dart';
import '../../../../core/utils/toast_utils.dart';
import '../../../../models/log/error_event.dart';
import '../shared/copy_button.dart';
import '../shared/error_block.dart';
import 'platform_badge.dart';
import 'severity_badge.dart';
import '../../../../core/constants/app_constants.dart';
import '../../../../core/utils/smooth_scroll_controller.dart';
import '../../../../core/utils/toast_utils.dart';
import '../../../../l10n/app_localizations.dart';
import '../../../../models/log/error_event.dart';
import '../shared/copy_button.dart';
import '../shared/error_block.dart';
import 'error_tokens.dart' show platformLabel;
import 'platform_badge.dart';
import 'severity_badge.dart';


/// Right-pane detail for error events. Mirrors `ErrorDetailPanel` from
/// the error_inspector feature but in a single-scroll layout suitable
/// for the All Events side panel (no tabs). Local copy of the badge
/// widgets avoids a cross-feature import.
class ErrorDetail extends StatefulWidget {
final ErrorEvent entry;

const ErrorDetail({super.key, required this.entry});

@override
State<ErrorDetail> createState() => _ErrorDetailState();
}

class _ErrorDetailState extends State<ErrorDetail> {
final _scrollController = SmoothScrollController();

@override
void dispose() {
_scrollController.dispose();
super.dispose();
}

void _copyText(BuildContext context, String text, String label) {
Clipboard.setData(ClipboardData(text: text));
showCopiedToast(context, label: '$label copied');
}

@override
Widget build(BuildContext context) {
final entry = widget.entry;
final isDark = Theme.of(context).brightness == Brightness.dark;

return SingleChildScrollView(
controller: _scrollController,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header row: severity + platform badges
Row(
children: [
SeverityBadge(severity: entry.severity),
const SizedBox(width: 6),
PlatformBadge(platform: entry.platform),
],
),
// Message
const SizedBox(height: 16),
Row(
children: [
Text(
'Message',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.grey[500],
),
),
const Spacer(),
CopyButton(
tooltip: 'Copy message',
onTap: () => _copyText(context, entry.message, 'Message'),
),
Comment on lines +63 to +75

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use localized strings from S.of(context) instead of hardcoded 'Message' to adhere to internationalization standards.

Suggested change
Text(
'Message',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.grey[500],
),
),
const Spacer(),
CopyButton(
tooltip: 'Copy message',
onTap: () => _copyText(context, entry.message, 'Message'),
),
Text(
S.of(context).message,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.grey[500],
),
),
const Spacer(),
CopyButton(
tooltip: 'Copy message',
onTap: () => _copyText(context, entry.message, S.of(context).message),
),

],
),
const SizedBox(height: 6),
Text(
entry.message,
style: TextStyle(
fontFamily: AppConstants.monoFontFamily,
fontSize: 12,
color: isDark ? Colors.white : Colors.black87,
),
),
// Stack trace
if (entry.stackTrace != null) ...[
const SizedBox(height: 16),
Row(
children: [
Text(
'Stack Trace',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.grey[500],
),
),
const Spacer(),
CopyButton(
tooltip: 'Copy stack trace',
onTap: () => _copyText(
context,
entry.stackTrace!,
'Stack trace',
),
),
],
),
const SizedBox(height: 6),
ErrorBlock(text: entry.stackTrace!, isDark: isDark),
],
// Details
const SizedBox(height: 16),
Text(
'Details',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.grey[500],
),
),
Comment on lines +116 to +123

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use localized strings from S.of(context) instead of hardcoded 'Details' to adhere to internationalization standards.

Suggested change
Text(
'Details',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.grey[500],
),
),
Text(
S.of(context).details,
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.grey[500],
),
),

const SizedBox(height: 6),
_DetailRow('Platform', entry.platform.name),
_DetailRow('Severity', entry.severity.name),
_DetailRow('Source', entry.source ?? 'unknown'),
_DetailRow('Device ID', entry.deviceId),
if (entry.deviceInfo != null)
_DetailRow('Device Info', entry.deviceInfo!),
Comment on lines +125 to +130

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Improve user-facing details by using localized labels and properly formatted values (e.g., uppercase severity and platformLabel for platforms). Additionally, include the missing metadata section to match the ErrorDetailPanel functionality.

          _DetailRow(S.of(context).platform, platformLabel(entry.platform)),
          _DetailRow(S.of(context).severity, entry.severity.name.toUpperCase()),
          _DetailRow(S.of(context).source, entry.source ?? 'unknown'),
          _DetailRow(S.of(context).deviceId, entry.deviceId),
          if (entry.deviceInfo != null)
            _DetailRow(S.of(context).deviceInfo, entry.deviceInfo!),
          if (entry.metadata != null && entry.metadata!.isNotEmpty) ...[
            const SizedBox(height: 12),
            Text(
              'Metadata',
              style: TextStyle(
                fontSize: 11,
                fontWeight: FontWeight.w600,
                color: Colors.grey[500],
              ),
            ),
            const SizedBox(height: 4),
            Container(
              width: double.infinity,
              padding: const EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: isDark ? Colors.black26 : Colors.grey.shade100,
                borderRadius: BorderRadius.circular(8),
              ),
              child: Text(
                entry.metadata.toString(),
                style: TextStyle(
                  fontFamily: AppConstants.monoFontFamily,
                  fontSize: 11,
                  color: isDark ? Colors.white70 : Colors.black87,
                ),
              ),
            ),
          ],

],
),
);
}
}

class _DetailRow extends StatelessWidget {
final String label;
final String value;

const _DetailRow(this.label, this.value);

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 80,
child: Text(
label,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: Colors.grey[500],
),
),
),
Expanded(
child: Text(
value,
style: TextStyle(
fontFamily: AppConstants.monoFontFamily,
fontSize: 11,
color: Theme.of(context).brightness == Brightness.dark
? Colors.white70
: Colors.black87,
),
),
),
],
),
);
}
}
48 changes: 48 additions & 0 deletions lib/features/all_events/presentation/detail/error_tokens.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';

import '../../../../core/theme/color_tokens.dart';
import '../../../../models/log/error_event.dart';

/// Maps an [ErrorSeverity] to its accent color. Local copy of
/// `error_inspector/.../shared/error_tokens.dart` — the two pages
/// must not cross-import.
Color severityColor(ErrorSeverity severity) {
switch (severity) {
case ErrorSeverity.fatal:
return Colors.red.shade900;
case ErrorSeverity.crash:
return Colors.red;
case ErrorSeverity.error:
return ColorTokens.logError;
case ErrorSeverity.warning:
return ColorTokens.logWarn;
case ErrorSeverity.info:
return ColorTokens.logInfo;
}
}

String platformLabel(ErrorPlatform platform) {
switch (platform) {
case ErrorPlatform.js:
return 'JS';
case ErrorPlatform.native:
return 'Native';
case ErrorPlatform.android:
return 'Android';
case ErrorPlatform.ios:
return 'iOS';
}
}

Color platformColor(ErrorPlatform platform) {
switch (platform) {
case ErrorPlatform.js:
return Colors.blue;
case ErrorPlatform.native:
return Colors.purple;
case ErrorPlatform.android:
return Colors.green;
case ErrorPlatform.ios:
return Colors.orange;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import '../../../../core/utils/duration_format.dart';
import '../../../../core/utils/screenshot_filename.dart';
import '../../../../core/utils/toast_utils.dart';
import '../../../../l10n/app_localizations.dart';
import '../../../../models/log/error_event.dart';
import '../../../../models/log/log_entry.dart';
import '../../../../models/network/network_entry.dart';
import '../../../../models/state/state_change.dart';
Expand All @@ -30,6 +31,7 @@ import '../../provider/all_events_provider.dart';
import '../buttons/pressable_button.dart';
import '../detail/detail_header.dart';
import '../detail/diff_row.dart';
import '../detail/error_detail.dart';
import '../detail/fallback_detail.dart';
import '../detail/log_detail.dart';
import '../detail/network_detail.dart';
Expand Down Expand Up @@ -1426,7 +1428,11 @@ class _EventDetailPanelState extends ConsumerState<EventDetailPanel> {
return FallbackDetail(event: widget.event);
case EventType.display:
case EventType.asyncOp:
return FallbackDetail(event: widget.event);
case EventType.error:
if (widget.event.rawData is ErrorEvent) {
return ErrorDetail(entry: widget.event.rawData as ErrorEvent);
}
return FallbackDetail(event: widget.event);
Comment on lines 1432 to 1436

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While ErrorDetail is now correctly returned for the live UI, the screenshot generation logic in _buildScreenshotContent (which is outside this diff) still falls back to _fallbackScreenshot for EventType.error.

To ensure that screenshots of error events match the beautifully formatted live UI instead of rendering a generic fallback layout, please update _buildScreenshotContent to handle EventType.error by extracting the ErrorEvent and rendering a dedicated layout (similar to how it's done for log, network, state, and storage).

}
}
Expand Down
33 changes: 33 additions & 0 deletions lib/features/all_events/presentation/detail/platform_badge.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:flutter/material.dart';

import '../../../../models/log/error_event.dart';
import 'error_tokens.dart' show platformColor, platformLabel;

/// Tinted pill that labels the source [ErrorPlatform]. Local copy of
/// `error_inspector/.../shared/platform_badge.dart` — no cross-feature
/// import, by design.
class PlatformBadge extends StatelessWidget {
final ErrorPlatform platform;

const PlatformBadge({super.key, required this.platform});

@override
Widget build(BuildContext context) {
final color = platformColor(platform);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(4),
),
child: Text(
platformLabel(platform),
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: color,
),
),
);
}
}
33 changes: 33 additions & 0 deletions lib/features/all_events/presentation/detail/severity_badge.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:flutter/material.dart';

import '../../../../models/log/error_event.dart';
import 'error_tokens.dart' show severityColor;

/// Tinted uppercase pill that labels an [ErrorSeverity]. Local copy of
/// `error_inspector/.../shared/severity_badge.dart` — no cross-feature
/// import, by design.
class SeverityBadge extends StatelessWidget {
final ErrorSeverity severity;

const SeverityBadge({super.key, required this.severity});

@override
Widget build(BuildContext context) {
final color = severityColor(severity);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(4),
),
child: Text(
severity.name.toUpperCase(),
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: color,
),
),
);
}
}
Loading
Loading