import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../models/project.dart'; import '../providers/providers.dart'; /// Bottom sheet for starting a new session: search your PAI projects (like PAI /// search), or open the home directory / any arbitrary directory. /// /// The list comes from [projectsProvider], which the chat screen populates by /// sending the `projects` command when this sheet opens. class NewSessionSheet extends ConsumerStatefulWidget { /// Open a project. rehome=false → new session; rehome=true → re-home the /// current tab to this project's directory. final void Function(Project project, bool rehome) onLaunchProject; /// Open an arbitrary directory ("~" for home). rehome as above. final void Function(String path, String name, bool rehome) onLaunchPath; const NewSessionSheet({ super.key, required this.onLaunchProject, required this.onLaunchPath, }); @override ConsumerState createState() => _NewSessionSheetState(); } class _NewSessionSheetState extends ConsumerState { final _searchController = TextEditingController(); String _query = ''; bool _rehome = false; // false = new session, true = re-home the current tab @override void dispose() { _searchController.dispose(); super.dispose(); } List _filtered(List projects) { final list = [...projects] ..sort( (a, b) => b.lastActive.compareTo(a.lastActive), ); // most recent first final q = _query.trim().toLowerCase(); if (q.isEmpty) return list; return list .where( (p) => p.name.toLowerCase().contains(q) || p.slug.toLowerCase().contains(q) || p.path.toLowerCase().contains(q), ) .toList(); } void _launchProject(Project p) { Navigator.of(context).pop(); widget.onLaunchProject(p, _rehome); } void _launchPath(String path, String name) { Navigator.of(context).pop(); widget.onLaunchPath(path, name, _rehome); } Future _promptCustomDir() async { final controller = TextEditingController(); final dir = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Open a directory'), content: TextField( controller: controller, autofocus: true, decoration: const InputDecoration( hintText: '/Users/you/dev/apps/youdrill', ), onSubmitted: (v) => Navigator.pop(ctx, v), ), actions: [ TextButton( onPressed: () => Navigator.pop(ctx), child: const Text('Cancel'), ), TextButton( onPressed: () => Navigator.pop(ctx, controller.text), child: const Text('Open'), ), ], ), ); controller.dispose(); final path = dir?.trim() ?? ''; if (path.isEmpty) return; final parts = path.split('/').where((s) => s.isNotEmpty).toList(); _launchPath(path, parts.isNotEmpty ? parts.last : 'Session'); } @override Widget build(BuildContext context) { final projects = ref.watch(projectsProvider); final filtered = _filtered(projects); return Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: DraggableScrollableSheet( expand: false, initialChildSize: 0.7, minChildSize: 0.4, maxChildSize: 0.92, builder: (context, scrollController) { return Column( children: [ // Grabber Container( width: 40, height: 4, margin: const EdgeInsets.symmetric(vertical: 10), decoration: BoxDecoration( color: Colors.grey.withAlpha(120), borderRadius: BorderRadius.circular(2), ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), child: SegmentedButton( segments: const [ ButtonSegment( value: false, label: Text('New session'), icon: Icon(Icons.add, size: 18), ), ButtonSegment( value: true, label: Text('Switch this tab'), icon: Icon(Icons.swap_horiz, size: 18), ), ], selected: {_rehome}, showSelectedIcon: false, onSelectionChanged: (s) => setState(() => _rehome = s.first), ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), child: TextField( controller: _searchController, autofocus: true, textInputAction: TextInputAction.search, decoration: InputDecoration( hintText: 'Search projects…', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), isDense: true, ), onChanged: (v) => setState(() => _query = v), ), ), Expanded( child: ListView( controller: scrollController, children: [ ListTile( leading: const Icon(Icons.home_outlined), title: const Text('Home directory'), subtitle: const Text('New session in ~'), onTap: () => _launchPath('~', 'Home'), ), ListTile( leading: const Icon(Icons.folder_open_outlined), title: const Text('Open a directory…'), subtitle: const Text('Start in any path'), onTap: _promptCustomDir, ), const Divider(height: 1), if (projects.isEmpty) const Padding( padding: EdgeInsets.all(24), child: Center(child: Text('Loading projects…')), ) else if (filtered.isEmpty) const Padding( padding: EdgeInsets.all(24), child: Center(child: Text('No matching projects')), ) else ...filtered.map( (p) => ListTile( leading: const Icon(Icons.folder_special_outlined), title: Text(p.name), subtitle: Text( p.path, maxLines: 1, overflow: TextOverflow.ellipsis, ), trailing: p.sessions > 0 ? Text( '${p.sessions}', style: TextStyle( color: Colors.grey.withAlpha(180), fontSize: 13, ), ) : null, onTap: () => _launchProject(p), ), ), ], ), ), ], ); }, ), ); } }