import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:accessible_terminal/state/terminal_provider.dart'; import 'package:dartssh2/dartssh2.dart'; import 'file_editor_view.dart'; class FileBrowserView extends ConsumerStatefulWidget { final String initialPath; const FileBrowserView({super.key, this.initialPath = '.'}); @override ConsumerState createState() => _FileBrowserViewState(); } class _FileBrowserViewState extends ConsumerState { late String _currentPath; List? _files; bool _isLoading = true; String? _error; @override void initState() { super.initState(); _currentPath = widget.initialPath; _loadDirectory(); } Future _loadDirectory() async { setState(() { _isLoading = true; _error = null; }); try { final sshService = ref.read(sshServiceProvider); final files = await sshService.listDirectory(_currentPath); // Sort: Directories first, then alphabetically files.sort((a, b) { if (a.attr.isDirectory && !b.attr.isDirectory) return -1; if (!a.attr.isDirectory && b.attr.isDirectory) return 1; return a.filename.toLowerCase().compareTo(b.filename.toLowerCase()); }); if (mounted) { setState(() { _files = files.where((f) => f.filename != '.' && f.filename != '..').toList(); _isLoading = false; }); } } catch (e) { if (mounted) { setState(() { _error = e.toString(); _isLoading = false; }); } } } void _navigate(String filename) { setState(() { if (_currentPath == '/') { _currentPath = '/$filename'; } else { _currentPath = '$_currentPath/$filename'.replaceAll('//', '/'); } }); _loadDirectory(); } void _goUp() { if (_currentPath == '/' || _currentPath == '.') return; final parts = _currentPath.split('/'); parts.removeLast(); setState(() { _currentPath = parts.isEmpty ? '/' : parts.join('/'); if (_currentPath.isEmpty) _currentPath = '/'; }); _loadDirectory(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(_currentPath), actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: _loadDirectory, tooltip: 'Refresh', ), ], ), body: _buildBody(), ); } Widget _buildBody() { if (_isLoading) { return const Center(child: CircularProgressIndicator()); } if (_error != null) { return Center( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error, color: Colors.red, size: 48), const SizedBox(height: 16), Text('Error: $_error', textAlign: TextAlign.center), const SizedBox(height: 16), ElevatedButton(onPressed: _loadDirectory, child: const Text('Retry')), ], ), ), ); } return Column( children: [ if (_currentPath != '/' && _currentPath != '.') ListTile( leading: const Icon(Icons.arrow_upward), title: const Text('.. (Go Up)'), onTap: _goUp, ), Expanded( child: ListView.builder( itemCount: _files?.length ?? 0, itemBuilder: (context, index) { final file = _files![index]; final isDir = file.attr.isDirectory; return ListTile( leading: Icon(isDir ? Icons.folder : Icons.insert_drive_file), title: Text(file.filename), subtitle: isDir ? null : Text(_formatSize(file.attr.size ?? 0)), onTap: () { if (isDir) { _navigate(file.filename); } else { _openFile(file.filename); } }, ); }, ), ), ], ); } String _formatSize(int bytes) { if (bytes < 1024) return '$bytes B'; if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB'; return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB'; } void _openFile(String filename) { final fullPath = _currentPath == '/' ? '/$filename' : '$_currentPath/$filename'; Navigator.push( context, MaterialPageRoute( builder: (context) => FileEditorView(path: fullPath), ), ).then((_) => _loadDirectory()); // Refresh on return in case of changes } }