diff --git a/lib/core/screens/session_list_screen.dart b/lib/core/screens/session_list_screen.dart index 1fabe8e..8436175 100644 --- a/lib/core/screens/session_list_screen.dart +++ b/lib/core/screens/session_list_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import '../services/connection_manager.dart'; import 'chat_screen.dart'; import 'settings_screen.dart'; @@ -54,8 +55,14 @@ class _SessionListScreenState extends State { try { final sessions = await _client.getSessions(); if (!mounted) return; + final prefs = await SharedPreferences.getInstance(); + final key = 'excluded_session_sources_${widget.connection.id}'; + final excluded = prefs.getStringList(key) ?? []; + final filtered = + sessions.where((s) => !excluded.contains(s.source)).toList(); + if (!mounted) return; setState(() { - _sessions = sessions; + _sessions = filtered; _loading = false; }); } catch (e) { diff --git a/lib/core/screens/settings_screen.dart b/lib/core/screens/settings_screen.dart index 900b256..02e5ecc 100644 --- a/lib/core/screens/settings_screen.dart +++ b/lib/core/screens/settings_screen.dart @@ -322,6 +322,11 @@ class _SettingsScreenState extends State { _VoicePicker(), const SizedBox(height: 16), + // ---- Section: Session Sources ---- + _buildSectionHeader('Session Sources'), + _SessionSourcesFilter(connectionId: widget.connection.id), + const SizedBox(height: 16), + // ---- Section: Connection ---- _buildSectionHeader('Connection'), Card( @@ -692,4 +697,88 @@ class _VoicePickerState extends State<_VoicePicker> { onChanged: _set, ); } -} \ No newline at end of file +} + +/// Checkbox list of session sources. Unchecked sources are filtered +/// client-side from the fetched session list by `Session.source`. +class _SessionSourcesFilter extends StatefulWidget { + final String connectionId; + const _SessionSourcesFilter({required this.connectionId}); + + @override + State<_SessionSourcesFilter> createState() => _SessionSourcesFilterState(); +} + +class _SessionSourcesFilterState extends State<_SessionSourcesFilter> { + /// Known session source types. Hermes Gateway persists `session.source` for + /// every session. Sources not in this list are always shown (whitelisted). + static const Map _knownSources = { + 'acp': 'Autonomous agents', + 'api_server': 'External API clients', + 'cli': 'Command-line chats', + 'cron': 'Scheduled tasks', + 'desktop': 'Desktop app', + 'discord': 'Discord chats', + 'gateway': 'Gateway API access', + 'mobile': 'Phone or tablet', + 'signal': 'Signal messages', + 'slack': 'Slack chats', + 'telegram': 'Telegram messages', + 'tool': 'Developer tool calls', + 'tui': 'Terminal sessions', + 'whatsapp': 'WhatsApp messages', + }; + + Set _excluded = {}; + + String get _prefsKey => + 'excluded_session_sources_${widget.connectionId}'; + + @override + void initState() { + super.initState(); + _load(); + } + + Future _load() async { + final prefs = await SharedPreferences.getInstance(); + setState(() { + _excluded = + prefs.getStringList(_prefsKey)?.toSet() ?? {}; + }); + } + + Future _toggle(String source, bool enabled) async { + final prefs = await SharedPreferences.getInstance(); + setState(() { + if (enabled) { + _excluded.remove(source); + } else { + _excluded.add(source); + } + }); + await prefs.setStringList(_prefsKey, _excluded.toList()); + } + + @override + Widget build(BuildContext context) { + return Card( + child: Column( + children: _knownSources.entries.map((entry) { + final source = entry.key; + final label = entry.value; + final isVisible = !_excluded.contains(source); + return CheckboxListTile( + title: Text(label), + subtitle: Text(source, + style: const TextStyle(fontSize: 12, color: Colors.grey)), + value: isVisible, + onChanged: (val) => _toggle(source, val ?? true), + dense: true, + controlAffinity: ListTileControlAffinity.leading, + ); + }).toList(), + ), + ); + } +}