Skip to content

Commit eb4dcfe

Browse files
committed
feat: add timestamp toggle and request name to terminal log tiles
1 parent 9463c47 commit eb4dcfe

File tree

2 files changed

+125
-13
lines changed

2 files changed

+125
-13
lines changed

lib/screens/terminal/terminal_page.dart

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
33
import '../../models/terminal/models.dart';
44
import '../../consts.dart';
55
import '../../providers/terminal_providers.dart';
6+
import '../../providers/collection_providers.dart';
67
import '../../widgets/button_copy.dart';
78
import '../../widgets/field_search.dart';
89
import '../../widgets/terminal_tiles.dart';
@@ -18,6 +19,7 @@ class TerminalPage extends ConsumerStatefulWidget {
1819

1920
class _TerminalPageState extends ConsumerState<TerminalPage> {
2021
final TextEditingController _searchCtrl = TextEditingController();
22+
bool _showTimestamps = false; // user toggle
2123

2224
// Initially all levels will be selected
2325
final Set<TerminalLevel> _selectedLevels = {
@@ -36,6 +38,8 @@ class _TerminalPageState extends ConsumerState<TerminalPage> {
3638
@override
3739
Widget build(BuildContext context) {
3840
final state = ref.watch(terminalStateProvider);
41+
final collection = ref.watch(collectionStateNotifierProvider);
42+
final selectedId = ref.watch(selectedIdStateProvider);
3943
final allEntries = state.entries;
4044
final filtered = _applyFilters(allEntries);
4145

@@ -58,13 +62,46 @@ class _TerminalPageState extends ConsumerState<TerminalPage> {
5862
separatorBuilder: (_, __) => const Divider(height: 1),
5963
itemBuilder: (ctx, i) {
6064
final e = filtered[filtered.length - 1 - i];
65+
String requestName = '';
66+
if (e.source == TerminalSource.js) {
67+
if (selectedId != null) {
68+
final model = collection?[selectedId];
69+
if (model != null) {
70+
requestName =
71+
model.name.isNotEmpty ? model.name : model.id;
72+
} else {
73+
requestName = selectedId;
74+
}
75+
}
76+
} else if (e.requestId != null) {
77+
final model = collection?[e.requestId];
78+
if (model != null) {
79+
requestName =
80+
model.name.isNotEmpty ? model.name : model.id;
81+
} else {
82+
requestName = e.requestId!;
83+
}
84+
}
6185
switch (e.source) {
6286
case TerminalSource.js:
63-
return JsLogTile(entry: e);
87+
return JsLogTile(
88+
entry: e,
89+
showTimestamp: _showTimestamps,
90+
requestName:
91+
requestName.isNotEmpty ? requestName : null,
92+
);
6493
case TerminalSource.network:
65-
return NetworkLogTile(entry: e);
94+
return NetworkLogTile(
95+
entry: e,
96+
showTimestamp: _showTimestamps,
97+
requestName:
98+
requestName.isNotEmpty ? requestName : null,
99+
);
66100
case TerminalSource.system:
67-
return SystemLogTile(entry: e);
101+
return SystemLogTile(
102+
entry: e,
103+
showTimestamp: _showTimestamps,
104+
);
68105
}
69106
},
70107
),
@@ -96,6 +133,22 @@ class _TerminalPageState extends ConsumerState<TerminalPage> {
96133
..addAll(set);
97134
}),
98135
),
136+
const SizedBox(width: 4),
137+
Tooltip(
138+
message: 'Show timestamps',
139+
child: Row(
140+
mainAxisSize: MainAxisSize.min,
141+
children: [
142+
Checkbox(
143+
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
144+
value: _showTimestamps,
145+
onChanged: (v) =>
146+
setState(() => _showTimestamps = v ?? false),
147+
),
148+
const Text('Timestamp', style: TextStyle(fontSize: 12)),
149+
],
150+
),
151+
),
99152

100153
const Spacer(),
101154
// Clear button

lib/widgets/terminal_tiles.dart

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import '../models/terminal/models.dart';
44
import 'expandable_section.dart';
55

66
class SystemLogTile extends StatelessWidget {
7-
const SystemLogTile({super.key, required this.entry});
7+
const SystemLogTile(
8+
{super.key, required this.entry, this.showTimestamp = false});
89
final TerminalEntry entry;
10+
final bool showTimestamp;
911
@override
1012
Widget build(BuildContext context) {
1113
assert(entry.system != null, 'System tile requires SystemLogData');
@@ -35,10 +37,17 @@ class SystemLogTile extends StatelessWidget {
3537
child: Row(
3638
crossAxisAlignment: CrossAxisAlignment.start,
3739
children: [
38-
Padding(
39-
padding: const EdgeInsets.only(top: 2, right: 8),
40-
child: Icon(icon, size: 18, color: iconColor),
41-
),
40+
if (showTimestamp) ...[
41+
Padding(
42+
padding: const EdgeInsets.only(top: 2, right: 8),
43+
child: Text(_formatTs(entry.ts), style: subStyle),
44+
),
45+
] else ...[
46+
Padding(
47+
padding: const EdgeInsets.only(top: 2, right: 8),
48+
child: Icon(icon, size: 18, color: iconColor),
49+
),
50+
],
4251
Expanded(
4352
child: Column(
4453
crossAxisAlignment: CrossAxisAlignment.start,
@@ -58,8 +67,14 @@ class SystemLogTile extends StatelessWidget {
5867
}
5968

6069
class JsLogTile extends StatelessWidget {
61-
const JsLogTile({super.key, required this.entry});
70+
const JsLogTile(
71+
{super.key,
72+
required this.entry,
73+
this.showTimestamp = false,
74+
this.requestName});
6275
final TerminalEntry entry;
76+
final bool showTimestamp;
77+
final String? requestName;
6378
@override
6479
Widget build(BuildContext context) {
6580
assert(entry.js != null, 'JS tile requires JsLogData');
@@ -83,21 +98,37 @@ class JsLogTile extends StatelessWidget {
8398
case TerminalLevel.debug:
8499
break;
85100
}
101+
final bodyParts = <String>[];
102+
if (requestName != null && requestName!.isNotEmpty) {
103+
bodyParts.add('[$requestName]');
104+
}
105+
// Add JS level/context prefix to disambiguate
106+
if (j.context != null && j.context!.isNotEmpty) {
107+
bodyParts.add('(${j.context})');
108+
}
109+
bodyParts.addAll(j.args);
110+
final bodyText = bodyParts.join(' ');
86111
return Container(
87112
color: bg,
88113
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
89114
child: Row(
90115
crossAxisAlignment: CrossAxisAlignment.start,
91116
children: [
92-
if (icon != null) ...[
117+
if (showTimestamp) ...[
118+
Padding(
119+
padding: const EdgeInsets.only(top: 2, right: 8),
120+
child: Text(_formatTs(entry.ts),
121+
style: Theme.of(context).textTheme.bodySmall),
122+
),
123+
] else if (icon != null) ...[
93124
Padding(
94125
padding: const EdgeInsets.only(top: 2, right: 8),
95126
child: Icon(icon, size: 18, color: iconColor),
96127
),
97128
],
98129
Expanded(
99130
child: SelectableText(
100-
j.args.join(' '),
131+
bodyText,
101132
style: Theme.of(context).textTheme.bodyMedium,
102133
),
103134
),
@@ -108,8 +139,14 @@ class JsLogTile extends StatelessWidget {
108139
}
109140

110141
class NetworkLogTile extends StatefulWidget {
111-
const NetworkLogTile({super.key, required this.entry});
142+
const NetworkLogTile(
143+
{super.key,
144+
required this.entry,
145+
this.showTimestamp = false,
146+
this.requestName});
112147
final TerminalEntry entry;
148+
final bool showTimestamp;
149+
final String? requestName;
113150
@override
114151
State<NetworkLogTile> createState() => _NetworkLogTileState();
115152
}
@@ -123,6 +160,11 @@ class _NetworkLogTileState extends State<NetworkLogTile> {
123160
final status = n.responseStatus != null ? '${n.responseStatus}' : null;
124161
final duration =
125162
n.duration != null ? '${n.duration!.inMilliseconds} ms' : null;
163+
final title = [
164+
if (widget.requestName != null && widget.requestName!.isNotEmpty)
165+
'[${widget.requestName}]',
166+
methodUrl,
167+
].join(' ');
126168
return Column(
127169
children: [
128170
InkWell(
@@ -131,9 +173,18 @@ class _NetworkLogTileState extends State<NetworkLogTile> {
131173
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
132174
child: Row(
133175
children: [
176+
if (widget.showTimestamp) ...[
177+
Padding(
178+
padding: const EdgeInsets.only(right: 8),
179+
child: Text(
180+
_formatTs(widget.entry.ts),
181+
style: Theme.of(context).textTheme.bodySmall,
182+
),
183+
),
184+
],
134185
Expanded(
135186
child: Text(
136-
methodUrl,
187+
title,
137188
maxLines: 1,
138189
overflow: TextOverflow.ellipsis,
139190
style: Theme.of(context).textTheme.bodyMedium,
@@ -220,3 +271,11 @@ class NetworkDetails extends StatelessWidget {
220271
return SelectableText(lines);
221272
}
222273
}
274+
275+
String _formatTs(DateTime ts) {
276+
// Show only time (HH:mm:ss) for compactness
277+
final h = ts.hour.toString().padLeft(2, '0');
278+
final m = ts.minute.toString().padLeft(2, '0');
279+
final s = ts.second.toString().padLeft(2, '0');
280+
return '$h:$m:$s';
281+
}

0 commit comments

Comments
 (0)