// lib/pages/webdav_search_page.dart import 'package:flutter/material.dart'; import '../controllers/playback_controller.dart'; import '../database/song_database.dart'; import '../services/webdav_service.dart'; import '../widgets/song_artwork.dart'; /// 在当前 WebDAV 文件夹内搜索歌曲 class WebDAVSearchPage extends StatefulWidget { final String directoryPath; final List items; const WebDAVSearchPage({ super.key, required this.directoryPath, required this.items, }); @override State createState() => _WebDAVSearchPageState(); } class _WebDAVSearchPageState extends State { final TextEditingController _controller = TextEditingController(); final FocusNode _focusNode = FocusNode(); /// path -> songs 表行 Map> _meta = {}; String _query = ''; List get _files => widget.items.where((item) => !item.isDirectory).toList(); @override void initState() { super.initState(); _loadMeta(); } @override void dispose() { _controller.dispose(); _focusNode.dispose(); super.dispose(); } Future _loadMeta() async { try { final paths = _files.map((item) => item.path).toList(); final meta = await SongDatabase().getSongsByPaths(paths); if (!mounted) return; setState(() => _meta = meta); } catch (e) { debugPrint('⚠️ [Search] 读取元数据失败: $e'); } } String _decode(String input) { try { return Uri.decodeComponent(input); } catch (_) { return input; } } List get _results { final keyword = _query.trim().toLowerCase(); if (keyword.isEmpty) return const []; return _files.where((item) { final name = _decode(item.name).toLowerCase(); final meta = _meta[item.path]; final title = (meta?['title'] as String? ?? '').toLowerCase(); final artist = (meta?['artist'] as String? ?? '').toLowerCase(); return name.contains(keyword) || title.contains(keyword) || artist.contains(keyword); }).toList(); } Future _play(WebDAVItem item) async { try { await PlaybackController().playFromWebDAV( directoryPath: widget.directoryPath, clickedSongPath: item.path, allItems: widget.items, ); if (mounted) Navigator.pop(context); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('播放失败: $e')), ); } } @override Widget build(BuildContext context) { final results = _results; return Scaffold( backgroundColor: const Color(0xFF0E1211), appBar: AppBar( backgroundColor: Colors.transparent, elevation: 0, foregroundColor: Colors.white, titleSpacing: 0, title: TextField( controller: _controller, focusNode: _focusNode, autofocus: true, style: const TextStyle(color: Colors.white, fontSize: 16), cursorColor: const Color(0xFFB8D4D0), decoration: const InputDecoration( hintText: '搜索本文件夹的歌曲', hintStyle: TextStyle(color: Colors.white38, fontSize: 15), border: InputBorder.none, ), onChanged: (value) => setState(() => _query = value), ), actions: [ if (_query.isNotEmpty) IconButton( icon: const Icon(Icons.close, color: Colors.white54), tooltip: '清除', onPressed: () { _controller.clear(); setState(() => _query = ''); }, ), ], ), body: _query.trim().isEmpty ? _buildHint() : results.isEmpty ? _buildEmpty() : ListView.builder( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8), itemCount: results.length, itemBuilder: (context, index) { final item = results[index]; final meta = _meta[item.path]; final metaTitle = (meta?['title'] as String?)?.trim() ?? ''; final metaArtist = (meta?['artist'] as String?)?.trim() ?? ''; final artwork = (meta?['artwork_path'] as String?)?.trim() ?? ''; final name = _decode(item.name); return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 4), visualDensity: VisualDensity.compact, leading: SongArtwork( filePath: artwork.isNotEmpty ? artwork : null, size: 44, fallbackIcon: Icons.audiotrack, ), title: Text( metaTitle.isNotEmpty ? metaTitle : name, style: const TextStyle( color: Colors.white, fontSize: 16), maxLines: 1, overflow: TextOverflow.ellipsis, ), subtitle: Text( metaArtist.isNotEmpty ? metaArtist : (item.size != null ? '${(item.size! / 1024 / 1024).toStringAsFixed(1)} MB' : ''), style: const TextStyle( color: Colors.grey, fontSize: 12), maxLines: 1, overflow: TextOverflow.ellipsis, ), onTap: () => _play(item), ); }, ), ); } Widget _buildHint() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.search, size: 48, color: Colors.grey[700]), const SizedBox(height: 14), Text( '输入歌名、歌手或文件名', style: TextStyle(color: Colors.grey[500], fontSize: 14), ), const SizedBox(height: 6), Text( '只在当前文件夹内搜索', style: TextStyle(color: Colors.grey[700], fontSize: 12), ), ], ), ); } Widget _buildEmpty() { return Center( child: Text( '没有找到「${_query.trim()}」', style: TextStyle(color: Colors.grey[500], fontSize: 14), ), ); } }