build method
Describes the part of the UI represented by this widget.
Implementation
@override
Widget build(BuildContext context) {
final theme = ThemeScope.of(context);
final filteredEvents = events
.where((event) => _matchesFilter(event))
.toList(growable: false);
final effectiveMaxItems = expanded ? filteredEvents.length : maxItems;
final visibleEvents = filteredEvents
.take(effectiveMaxItems)
.toList(growable: false);
final groupedEvents = _groupEvents(visibleEvents);
final typeCounts = _typeCounts(filteredEvents);
final canExpand = filteredEvents.length > maxItems;
final hiddenCount = filteredEvents.length - visibleEvents.length;
final hiddenGroupedCount = _groupEvents(
filteredEvents.skip(effectiveMaxItems).toList(growable: false),
).length;
final totalGroupedCount = _groupEvents(filteredEvents).length;
return PanelBox(
title: title,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
gap: 1,
children: [
if (showFilterChips)
Wrap(
spacing: 1,
runSpacing: 1,
children: [
for (final candidate in ReplayEventHistoryFilter.values)
ChoiceChip(
key: ValueKey('replay-history-filter-${candidate.name}'),
label: Text(_filterLabel(candidate)),
selected: candidate == filter,
onSelected: onFilterSelected == null
? null
: (selected) {
if (!selected) return null;
return onFilterSelected?.call(candidate);
},
),
],
),
if (showModeChips)
Wrap(
spacing: 1,
runSpacing: 1,
children: [
for (final candidate in ReplayEventHistoryMode.values)
ChoiceChip(
key: ValueKey('replay-history-mode-${candidate.name}'),
label: Text(_modeLabel(candidate)),
selected: candidate == mode,
onSelected: onModeSelected == null
? null
: (selected) {
if (!selected) return null;
return onModeSelected?.call(candidate);
},
),
],
),
if (showFilterSummary && filter != ReplayEventHistoryFilter.all)
Text('filter: ${_filterLabel(filter)}', style: theme.labelSmall),
if (showModeSummary && mode != ReplayEventHistoryMode.flat)
Text('mode: ${_modeLabel(mode)}', style: theme.labelSmall),
if (showTypeChips && typeCounts.isNotEmpty)
Wrap(
spacing: 1,
runSpacing: 1,
children: [
for (final entry in typeCounts.entries)
Chip(label: Text('${entry.key} ${entry.value}')),
],
),
if (showExpandToggle && canExpand)
Row(
gap: 1,
children: [
ActionChip(
key: const ValueKey('replay-history-expand-toggle'),
label: Text(
_expandLabel(
totalEventCount: filteredEvents.length,
totalGroupedCount: totalGroupedCount,
),
),
onPressed: onExpandedChanged == null
? null
: () => onExpandedChanged?.call(!expanded),
),
if (!expanded && hiddenCount > 0)
Text(
_hiddenSummaryLabel(
hiddenCount: hiddenCount,
hiddenGroupedCount: hiddenGroupedCount,
),
style: theme.labelSmall,
),
],
),
if (visibleEvents.isEmpty)
Text(
events.isEmpty
? 'No replay events yet.'
: 'No matching replay events.',
style: theme.bodySmall,
)
else if (mode == ReplayEventHistoryMode.flat)
...visibleEvents.map(
(event) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
gap: 0,
children: [
Text(event.summary, style: theme.bodySmall),
Text(event.statusHint, style: theme.labelSmall),
],
),
)
else
...groupedEvents.map(
(group) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
gap: 0,
children: [
Text(
'${group.count}x ${group.summary}',
style: theme.bodySmall,
),
Text(group.statusHint, style: theme.labelSmall),
],
),
),
],
),
);
}