diff --git a/lib/screens/terminal/terminal_page.dart b/lib/screens/terminal/terminal_page.dart index c5dba262..e1595061 100644 --- a/lib/screens/terminal/terminal_page.dart +++ b/lib/screens/terminal/terminal_page.dart @@ -9,6 +9,7 @@ import '../../widgets/field_search.dart'; import '../../widgets/terminal_tiles.dart'; import '../../widgets/empty_message.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; +import '../../widgets/terminal_level_filter_menu.dart'; class TerminalPage extends ConsumerStatefulWidget { const TerminalPage({super.key}); @@ -45,7 +46,65 @@ class _TerminalPageState extends ConsumerState { return Scaffold( body: Column( children: [ - _buildToolbar(context, allEntries), + Padding( + padding: const EdgeInsets.fromLTRB(12, 8, 12, 8), + child: Row( + children: [ + Expanded( + child: SearchField( + controller: _searchCtrl, + hintText: 'Search logs', + onChanged: (_) => setState(() {}), + ), + ), + const SizedBox(width: 8), + // Filter button + TerminalLevelFilterMenu( + selected: _selectedLevels, + onChanged: (set) => setState(() { + _selectedLevels + ..clear() + ..addAll(set); + }), + ), + const SizedBox(width: 4), + Tooltip( + message: 'Show timestamps', + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: _showTimestamps, + onChanged: (v) => + setState(() => _showTimestamps = v ?? false), + ), + const Text('Timestamp', style: TextStyle(fontSize: 12)), + ], + ), + ), + + const Spacer(), + // Clear button + ADIconButton( + tooltip: 'Clear logs', + icon: Icons.delete_outline, + iconSize: 22, + onPressed: () { + ref.read(terminalStateProvider.notifier).clear(); + }, + ), + const SizedBox(width: 4), + // Copy all button + CopyButton( + showLabel: false, + toCopy: ref + .read(terminalStateProvider.notifier) + .serializeAll(entries: allEntries), + ), + ], + ), + ), const Divider(height: 1), Expanded( child: filtered.isEmpty @@ -105,68 +164,6 @@ class _TerminalPageState extends ConsumerState { ); } - Widget _buildToolbar(BuildContext context, List allEntries) { - return Padding( - padding: const EdgeInsets.fromLTRB(12, 8, 12, 8), - child: Row( - children: [ - Expanded( - child: SearchField( - controller: _searchCtrl, - hintText: 'Search logs', - onChanged: (_) => setState(() {}), - ), - ), - const SizedBox(width: 8), - // Filter button - _FilterMenu( - selected: _selectedLevels, - onChanged: (set) => setState(() { - _selectedLevels - ..clear() - ..addAll(set); - }), - ), - const SizedBox(width: 4), - Tooltip( - message: 'Show timestamps', - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Checkbox( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - value: _showTimestamps, - onChanged: (v) => - setState(() => _showTimestamps = v ?? false), - ), - const Text('Timestamp', style: TextStyle(fontSize: 12)), - ], - ), - ), - - const Spacer(), - // Clear button - ADIconButton( - tooltip: 'Clear logs', - icon: Icons.delete_outline, - iconSize: 22, - onPressed: () { - ref.read(terminalStateProvider.notifier).clear(); - }, - ), - const SizedBox(width: 4), - // Copy all button - CopyButton( - showLabel: false, - toCopy: ref - .read(terminalStateProvider.notifier) - .serializeAll(entries: allEntries), - ), - ], - ), - ); - } - List _applyFilters(List entries) { final q = _searchCtrl.text.trim().toLowerCase(); bool matches(TerminalEntry e) { @@ -181,121 +178,3 @@ class _TerminalPageState extends ConsumerState { return entries.where(matches).toList(growable: false); } } - -class _FilterMenu extends StatelessWidget { - const _FilterMenu({ - required this.selected, - required this.onChanged, - }); - - final Set selected; - final ValueChanged> onChanged; - - void _toggleLevel(TerminalLevel level) { - final next = selected.contains(level) - ? (selected.toSet()..remove(level)) - : (selected.toSet()..add(level)); - onChanged(next); - } - - @override - Widget build(BuildContext context) { - final all = TerminalLevel.values.toSet(); - return PopupMenuButton<_MenuAction>( - tooltip: 'Filter', - icon: const Icon(Icons.filter_alt), - onSelected: (action) { - switch (action) { - case _MenuAction.toggleDebug: - _toggleLevel(TerminalLevel.debug); - break; - case _MenuAction.toggleInfo: - _toggleLevel(TerminalLevel.info); - break; - case _MenuAction.toggleWarn: - _toggleLevel(TerminalLevel.warn); - break; - case _MenuAction.toggleError: - _toggleLevel(TerminalLevel.error); - break; - case _MenuAction.selectAll: - onChanged(all); - break; - case _MenuAction.clearAll: - onChanged({}); - break; - } - }, - itemBuilder: (context) => [ - CheckedPopupMenuItem<_MenuAction>( - value: _MenuAction.toggleError, - checked: selected.contains(TerminalLevel.error), - child: const ListTile( - dense: true, - contentPadding: EdgeInsets.zero, - leading: Icon(Icons.error_outline_rounded, color: Colors.red), - title: Text('Errors'), - ), - ), - CheckedPopupMenuItem<_MenuAction>( - value: _MenuAction.toggleWarn, - checked: selected.contains(TerminalLevel.warn), - child: const ListTile( - dense: true, - contentPadding: EdgeInsets.zero, - leading: Icon(Icons.warning_amber_outlined, color: Colors.amber), - title: Text('Warnings'), - ), - ), - CheckedPopupMenuItem<_MenuAction>( - value: _MenuAction.toggleInfo, - checked: selected.contains(TerminalLevel.info), - child: const ListTile( - dense: true, - contentPadding: EdgeInsets.zero, - leading: Icon(Icons.info_outline, color: Colors.blue), - title: Text('Info'), - ), - ), - CheckedPopupMenuItem<_MenuAction>( - value: _MenuAction.toggleDebug, - checked: selected.contains(TerminalLevel.debug), - child: const ListTile( - dense: true, - contentPadding: EdgeInsets.zero, - leading: Icon(Icons.bug_report_outlined), - title: Text('Debug'), - ), - ), - const PopupMenuDivider(), - PopupMenuItem<_MenuAction>( - value: _MenuAction.selectAll, - child: const ListTile( - dense: true, - contentPadding: EdgeInsets.zero, - leading: Icon(Icons.select_all), - title: Text('Select all'), - ), - ), - PopupMenuItem<_MenuAction>( - value: _MenuAction.clearAll, - child: const ListTile( - dense: true, - contentPadding: EdgeInsets.zero, - leading: Icon(Icons.clear_all), - title: Text('Clear selection'), - ), - ), - ], - ); - } -} - -enum _MenuAction { - toggleDebug, - toggleInfo, - toggleWarn, - toggleError, - selectAll, - clearAll, -} diff --git a/lib/widgets/terminal_level_filter_menu.dart b/lib/widgets/terminal_level_filter_menu.dart new file mode 100644 index 00000000..bb232178 --- /dev/null +++ b/lib/widgets/terminal_level_filter_menu.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import '../consts.dart'; + +class TerminalLevelFilterMenu extends StatelessWidget { + const TerminalLevelFilterMenu({ + super.key, + required this.selected, + required this.onChanged, + }); + + final Set selected; + final ValueChanged> onChanged; + + void _toggleLevel(TerminalLevel level) { + final next = selected.contains(level) + ? (selected.toSet()..remove(level)) + : (selected.toSet()..add(level)); + onChanged(next); + } + + @override + Widget build(BuildContext context) { + final all = TerminalLevel.values.toSet(); + return PopupMenuButton<_MenuAction>( + tooltip: 'Filter', + icon: const Icon(Icons.filter_alt), + onSelected: (action) { + switch (action) { + case _MenuAction.toggleDebug: + _toggleLevel(TerminalLevel.debug); + break; + case _MenuAction.toggleInfo: + _toggleLevel(TerminalLevel.info); + break; + case _MenuAction.toggleWarn: + _toggleLevel(TerminalLevel.warn); + break; + case _MenuAction.toggleError: + _toggleLevel(TerminalLevel.error); + break; + case _MenuAction.selectAll: + onChanged(all); + break; + case _MenuAction.clearAll: + onChanged({}); + break; + } + }, + itemBuilder: (context) => [ + CheckedPopupMenuItem<_MenuAction>( + value: _MenuAction.toggleError, + checked: selected.contains(TerminalLevel.error), + child: const ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + leading: Icon(Icons.error_outline_rounded, color: Colors.red), + title: Text('Errors'), + ), + ), + CheckedPopupMenuItem<_MenuAction>( + value: _MenuAction.toggleWarn, + checked: selected.contains(TerminalLevel.warn), + child: const ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + leading: Icon(Icons.warning_amber_outlined, color: Colors.amber), + title: Text('Warnings'), + ), + ), + CheckedPopupMenuItem<_MenuAction>( + value: _MenuAction.toggleInfo, + checked: selected.contains(TerminalLevel.info), + child: const ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + leading: Icon(Icons.info_outline, color: Colors.blue), + title: Text('Info'), + ), + ), + CheckedPopupMenuItem<_MenuAction>( + value: _MenuAction.toggleDebug, + checked: selected.contains(TerminalLevel.debug), + child: const ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + leading: Icon(Icons.bug_report_outlined), + title: Text('Debug'), + ), + ), + const PopupMenuDivider(), + PopupMenuItem<_MenuAction>( + value: _MenuAction.selectAll, + child: const ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + leading: Icon(Icons.select_all), + title: Text('Select all'), + ), + ), + PopupMenuItem<_MenuAction>( + value: _MenuAction.clearAll, + child: const ListTile( + dense: true, + contentPadding: EdgeInsets.zero, + leading: Icon(Icons.clear_all), + title: Text('Clear selection'), + ), + ), + ], + ); + } +} + +enum _MenuAction { + toggleDebug, + toggleInfo, + toggleWarn, + toggleError, + selectAll, + clearAll, +}