import 'dart:io'; import 'package:desktop_drop/desktop_drop.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import '../../../backend/api/commands.dart' as commands; import '../../../backend/discovery/model.dart'; class SendFilesModal extends StatefulWidget { const SendFilesModal({ super.key, required this.peer, required this.targetIp, this.initialPaths = const [], }); final Peer peer; final String targetIp; final List initialPaths; @override State createState() => _SendFilesModalState(); } class _SendFilesModalState extends State { late final List _paths = [...widget.initialPaths]; bool _dragging = false; Future _submit() async { if (_paths.isEmpty) { return; } final pending = List.from(_paths); if (mounted) { Navigator.of(context).pop(); } for (final path in pending) { final entityType = FileSystemEntity.typeSync(path); if (entityType == FileSystemEntityType.directory) { commands.sendFolder( target: widget.peer, targetIp: widget.targetIp, folderPath: path, ); } else { commands.sendFile( target: widget.peer, targetIp: widget.targetIp, filePath: path, ); } } } Future _pickFiles() async { final result = await FilePicker.platform.pickFiles(allowMultiple: true); if (result == null || !mounted) { return; } final selected = result.paths.whereType().where( (p) => p.isNotEmpty, ); setState(() { for (final path in selected) { if (!_paths.contains(path)) { _paths.add(path); } } }); } Future _pickFolder() async { final folder = await FilePicker.platform.getDirectoryPath(); if (folder == null || folder.isEmpty || !mounted) { return; } setState(() { if (!_paths.contains(folder)) { _paths.add(folder); } }); } bool get _enableDragDrop => Platform.isLinux || Platform.isWindows || Platform.isMacOS; @override Widget build(BuildContext context) { final theme = Theme.of(context); return AlertDialog( title: Text('发送文件给 ${widget.peer.name}'), content: SizedBox( width: 640, child: Column( mainAxisSize: MainAxisSize.min, children: [ if (_enableDragDrop) DropTarget( onDragEntered: (_) => setState(() => _dragging = true), onDragExited: (_) => setState(() => _dragging = false), onDragDone: (details) { setState(() { _dragging = false; _paths.addAll( details.files .map((f) => f.path) .where((p) => p.isNotEmpty) .where((p) => !_paths.contains(p)), ); }); }, child: Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all( color: _dragging ? theme.colorScheme.primary : theme.colorScheme.outlineVariant, width: _dragging ? 2 : 1, ), color: theme.colorScheme.surfaceContainerHighest.withValues( alpha: 0.35, ), ), child: Column( children: [ Icon( _dragging ? Icons.file_download_done_rounded : Icons.file_upload_rounded, size: 36, ), const SizedBox(height: 8), Text(_dragging ? '松手即可添加文件' : '将文件或文件夹拖到这里'), ], ), ), ) else Container( width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all(color: theme.colorScheme.outlineVariant), color: theme.colorScheme.surfaceContainerHighest.withValues( alpha: 0.35, ), ), child: const Column( children: [ Icon(Icons.file_upload_rounded, size: 36), SizedBox(height: 8), ], ), ), const SizedBox(height: 12), Row( children: [ Expanded( child: OutlinedButton.icon( onPressed: _pickFiles, icon: const Icon(Icons.attach_file_rounded), label: const Text('选择文件'), ), ), const SizedBox(width: 8), Expanded( child: OutlinedButton.icon( onPressed: _pickFolder, icon: const Icon(Icons.folder_open_rounded), label: const Text('选择文件夹'), ), ), ], ), const SizedBox(height: 12), ConstrainedBox( constraints: const BoxConstraints(maxHeight: 260), child: _paths.isEmpty ? const Center( child: Padding( padding: EdgeInsets.symmetric(vertical: 24), child: Text('暂无待发送文件'), ), ) : ListView.separated( shrinkWrap: true, itemCount: _paths.length, separatorBuilder: (_, _) => const Divider(height: 1), itemBuilder: (context, index) { final path = _paths[index]; final isDirectory = FileSystemEntity.typeSync(path) == FileSystemEntityType.directory; return ListTile( dense: true, leading: Icon( isDirectory ? Icons.folder_copy_rounded : Icons.insert_drive_file_rounded, ), title: Text( path.split(Platform.pathSeparator).last, maxLines: 1, overflow: TextOverflow.ellipsis, ), subtitle: Text( path, maxLines: 1, overflow: TextOverflow.ellipsis, ), trailing: IconButton( icon: const Icon(Icons.delete_outline_rounded), onPressed: () => setState(() => _paths.removeAt(index)), ), ); }, ), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('取消'), ), FilledButton.icon( onPressed: _paths.isEmpty ? null : _submit, icon: const Icon(Icons.send_rounded), label: Text('发送 (${_paths.length})'), ), ], ); } }