import 'package:flutter/material.dart'; import 'package:lucide_icons/lucide_icons.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../providers/knowledge_provider.dart'; import '../../widgets/knowledge_item_card.dart'; // Type tab configuration: (label, noteType filter value) const _kTabs = [ (label: 'All', type: null), (label: 'Notes', type: 'note'), (label: 'People', type: 'person'), (label: 'Places', type: 'place'), (label: 'Lists', type: 'list'), (label: 'Tasks', type: 'task'), ]; class KnowledgeScreen extends ConsumerStatefulWidget { const KnowledgeScreen({super.key}); @override ConsumerState createState() => _KnowledgeScreenState(); } class _KnowledgeScreenState extends ConsumerState with SingleTickerProviderStateMixin { late final TabController _tabController; late final ScrollController _scrollController; bool _searchActive = false; final _searchController = TextEditingController(); var _debounce = DateTime.now(); @override void initState() { super.initState(); _tabController = TabController(length: _kTabs.length, vsync: this); _scrollController = ScrollController(); _scrollController.addListener(_onScroll); // Trigger initial load WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(knowledgeProvider.notifier).refresh(); }); _tabController.addListener(_onTabChanged); } @override void dispose() { _tabController.dispose(); _scrollController.dispose(); _searchController.dispose(); super.dispose(); } void _onTabChanged() { if (!_tabController.indexIsChanging) return; final newType = _kTabs[_tabController.index].type; ref.read(knowledgeProvider.notifier).setTypeFilter(newType); } void _onScroll() { final pos = _scrollController.position; if (pos.pixels >= pos.maxScrollExtent - 300) { ref.read(knowledgeProvider.notifier).hydrateNext(); } } void _onSearchChanged(String q) { final now = DateTime.now(); _debounce = now; Future.delayed(const Duration(milliseconds: 400), () { if (_debounce == now && mounted) { ref.read(knowledgeProvider.notifier).setSearch(q); } }); } String _tabLabel(int index) => _kTabs[index].label; @override Widget build(BuildContext context) { final state = ref.watch(knowledgeProvider); return Scaffold( appBar: AppBar( title: _searchActive ? TextField( controller: _searchController, autofocus: true, decoration: const InputDecoration( hintText: 'Search knowledge…', border: InputBorder.none, ), onChanged: _onSearchChanged, ) : const Text('Knowledge'), actions: [ IconButton( icon: Icon(_searchActive ? LucideIcons.x : LucideIcons.search), onPressed: () { setState(() => _searchActive = !_searchActive); if (!_searchActive) { _searchController.clear(); ref.read(knowledgeProvider.notifier).setSearch(null); } }, ), ], bottom: TabBar( controller: _tabController, isScrollable: true, tabAlignment: TabAlignment.start, tabs: List.generate( _kTabs.length, (i) => Tab(text: _tabLabel(i)), ), ), ), body: Column( children: [ // Tag filter chips if (state.availableTags.isNotEmpty) SizedBox( height: 48, child: ListView.separated( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), itemCount: state.availableTags.length, separatorBuilder: (_, _) => const SizedBox(width: 6), itemBuilder: (_, i) { final tag = state.availableTags[i]; final active = state.activeTags.contains(tag); return FilterChip( label: Text(tag), selected: active, onSelected: (_) => ref.read(knowledgeProvider.notifier).toggleTag(tag), visualDensity: VisualDensity.compact, ); }, ), ), // Main list Expanded( child: _buildList(state), ), ], ), floatingActionButton: FloatingActionButton( onPressed: () => _onFabTapped(context), tooltip: 'New', child: const Icon(LucideIcons.pencil), ), ); } Widget _buildList(KnowledgeState state) { if (state.isLoadingIds && state.ids.isEmpty) { return const Center(child: CircularProgressIndicator()); } if (state.error != null && state.ids.isEmpty) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Failed to load', style: Theme.of(context).textTheme.bodyLarge), const SizedBox(height: 8), TextButton( onPressed: () => ref.read(knowledgeProvider.notifier).refresh(), child: const Text('Retry'), ), ], ), ); } final items = state.orderedItems; if (items.isEmpty && !state.isLoadingIds && !state.isLoadingBatch) { return Center( child: Text( 'No ${_kTabs[_tabController.index].label.toLowerCase()} yet.', style: Theme.of(context).textTheme.bodyLarge, ), ); } return RefreshIndicator( onRefresh: () => ref.read(knowledgeProvider.notifier).refresh(), child: LayoutBuilder( builder: (context, constraints) { final cols = constraints.maxWidth >= 900 ? 3 : constraints.maxWidth >= 600 ? 2 : 1; if (cols == 1) { return ListView.separated( controller: _scrollController, itemCount: items.length + (state.isLoadingBatch ? 1 : 0), separatorBuilder: (_, _) => const Divider(height: 1), itemBuilder: (_, i) { if (i >= items.length) { return const Padding( padding: EdgeInsets.all(16), child: Center(child: CircularProgressIndicator()), ); } return KnowledgeItemCard(item: items[i]); }, ); } return CustomScrollView( controller: _scrollController, slivers: [ SliverPadding( padding: const EdgeInsets.all(8), sliver: SliverGrid( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: cols, crossAxisSpacing: 8, mainAxisSpacing: 8, childAspectRatio: 1.8, ), delegate: SliverChildBuilderDelegate( (_, i) { if (i >= items.length) { return const Center( child: CircularProgressIndicator()); } return KnowledgeItemCard(item: items[i]) .buildGridCard(context); }, childCount: items.length + (state.isLoadingBatch ? 1 : 0), ), ), ), ], ); }, ), ); } void _onFabTapped(BuildContext context) { final currentType = _kTabs[_tabController.index].type; if (currentType == 'task') { context.push('/tasks/new'); return; } _showTypePicker(context); } void _showTypePicker(BuildContext context) { showModalBottomSheet( context: context, builder: (sheetContext) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ _TypePickerRow( icon: LucideIcons.fileText, label: 'Note', description: 'General note or document', onTap: () { Navigator.pop(sheetContext); context.push('/notes/new', extra: {'noteType': 'note'}); }, ), _TypePickerRow( icon: LucideIcons.user, label: 'Person', description: 'Contact, colleague, or reference person', onTap: () { Navigator.pop(sheetContext); context.push('/notes/new', extra: {'noteType': 'person'}); }, ), _TypePickerRow( icon: LucideIcons.mapPin, label: 'Place', description: 'Location, venue, or place of interest', onTap: () { Navigator.pop(sheetContext); context.push('/notes/new', extra: {'noteType': 'place'}); }, ), _TypePickerRow( icon: LucideIcons.listChecks, label: 'List', description: 'Checklist or structured list', onTap: () { Navigator.pop(sheetContext); context.push('/notes/new', extra: {'noteType': 'list'}); }, ), ], ), ), ); } } class _TypePickerRow extends StatelessWidget { final IconData icon; final String label; final String description; final VoidCallback onTap; const _TypePickerRow({ required this.icon, required this.label, required this.description, required this.onTap, }); @override Widget build(BuildContext context) { return ListTile( leading: Icon(icon), title: Text(label), subtitle: Text(description), onTap: onTap, ); } }