diff --git a/lib/pages/media_library_page.dart b/lib/pages/media_library_page.dart index 83a553f..a849dbe 100644 --- a/lib/pages/media_library_page.dart +++ b/lib/pages/media_library_page.dart @@ -257,6 +257,8 @@ class _MediaLibraryPageState extends ConsumerState { onTap: () => ref .read(mediaLibraryProvider.notifier) .selectLibrary(library.id), + onEdit: () => + _showEditLibraryDialog(context, ref, library), onDelete: () => ref .read(mediaLibraryProvider.notifier) .deleteLibrary(library.id), @@ -537,6 +539,22 @@ class _MediaLibraryPageState extends ConsumerState { ); } + static void _showEditLibraryDialog( + BuildContext context, + WidgetRef ref, + MediaLibraryDefinition library, + ) { + showShadDialog( + context: context, + builder: (_) => _CreateMediaLibraryDialog( + initialRootID: library.rootID, + initialPath: library.rootPath, + initialName: library.name, + editingLibrary: library, + ), + ); + } + void _saveApiKey() { final key = _apiKeyController.text.trim(); StorageManager.set(StorageKeys.tmdbApiKey, key); @@ -591,11 +609,13 @@ class _CreateMediaLibraryDialog extends ConsumerStatefulWidget { final String? initialRootID; final String initialPath; final String initialName; + final MediaLibraryDefinition? editingLibrary; const _CreateMediaLibraryDialog({ required this.initialRootID, required this.initialPath, required this.initialName, + this.editingLibrary, }); @override @@ -616,17 +636,26 @@ class _CreateMediaLibraryDialogState final _browserPath = []; var _folders = []; + bool get _isEditing => widget.editingLibrary != null; + @override void initState() { super.initState(); - _sources = [ - MediaLibrarySource( - id: 'initial-source', - rootID: widget.initialRootID, - path: widget.initialPath, - ), - ]; + _sources = + widget.editingLibrary?.sources ?? + [ + MediaLibrarySource( + id: 'initial-source', + rootID: widget.initialRootID, + path: widget.initialPath, + ), + ]; _nameController = TextEditingController(text: widget.initialName); + if (_isEditing) { + _kind = widget.editingLibrary!.kind; + _recursive = widget.editingLibrary!.recursive; + _minSizeController.text = widget.editingLibrary!.minimumSizeMB.toString(); + } } @override @@ -730,26 +759,38 @@ class _CreateMediaLibraryDialogState }); } - void _create(BuildContext context) { - ref - .read(mediaLibraryProvider.notifier) - .createLibrary( + Future _save(BuildContext context) async { + final notifier = ref.read(mediaLibraryProvider.notifier); + if (_isEditing) { + await notifier.updateLibrary( + widget.editingLibrary!.copyWith( name: _nameController.text, - rootID: _sources.isEmpty ? null : _sources.first.rootID, - rootPath: _sources.isEmpty ? '未配置目录' : _sources.first.path, sources: _sources, kind: _kind, recursive: _recursive, minimumSizeMB: int.tryParse(_minSizeController.text.trim()) ?? 50, - ); - Navigator.of(context).pop(); + updatedAt: DateTime.now(), + ), + ); + } else { + await notifier.createLibrary( + name: _nameController.text, + rootID: _sources.isEmpty ? null : _sources.first.rootID, + rootPath: _sources.isEmpty ? '未配置目录' : _sources.first.path, + sources: _sources, + kind: _kind, + recursive: _recursive, + minimumSizeMB: int.tryParse(_minSizeController.text.trim()) ?? 50, + ); + } + if (context.mounted) Navigator.of(context).pop(); } @override Widget build(BuildContext context) { final cs = ShadTheme.of(context).colorScheme; return ShadDialog( - title: Text(_isBrowsing ? '选择云盘文件夹' : '创建媒体库'), + title: Text(_isBrowsing ? '选择云盘文件夹' : (_isEditing ? '管理媒体库' : '创建媒体库')), description: Text( _isBrowsing ? '进入目标目录后,选择该目录作为媒体库来源。' : '媒体库会从指定目录扫描视频文件。', ), @@ -771,9 +812,9 @@ class _CreateMediaLibraryDialogState child: const Text('取消'), ), ShadButton( - onPressed: _sources.isEmpty ? null : () => _create(context), + onPressed: _sources.isEmpty ? null : () => _save(context), leading: const Icon(Icons.add_rounded, size: 16), - child: const Text('创建媒体库'), + child: Text(_isEditing ? '保存并扫描' : '创建媒体库'), ), ], child: _isBrowsing ? _folderBrowser(cs) : _form(cs), @@ -1039,12 +1080,14 @@ class _LibraryRow extends StatelessWidget { final MediaLibraryDefinition library; final bool selected; final VoidCallback onTap; + final VoidCallback onEdit; final VoidCallback onDelete; const _LibraryRow({ required this.library, required this.selected, required this.onTap, + required this.onEdit, required this.onDelete, }); @@ -1104,6 +1147,14 @@ class _LibraryRow extends StatelessWidget { ], ), ), + ShadTooltip( + builder: (_) => const Text('管理媒体库'), + child: ShadButton.ghost( + size: ShadButtonSize.sm, + onPressed: onEdit, + child: const Icon(Icons.edit_outlined, size: 15), + ), + ), ShadTooltip( builder: (_) => const Text('删除媒体库'), child: ShadButton.destructive( diff --git a/lib/providers/media_library_provider.dart b/lib/providers/media_library_provider.dart index 5d08471..c267261 100644 --- a/lib/providers/media_library_provider.dart +++ b/lib/providers/media_library_provider.dart @@ -179,6 +179,20 @@ class MediaLibraryNotifier extends StateNotifier { ); } + Future updateLibrary(MediaLibraryDefinition library) async { + if (library.name.trim().isEmpty || library.sources.isEmpty) return; + final libraries = state.libraries + .map((item) => item.id == library.id ? library : item) + .toList(); + await _saveLibraries(libraries); + state = state.copyWith( + libraries: libraries, + selectedLibraryID: library.id, + items: _loadItems(library.id), + statusMessage: '媒体库「${library.name}」已更新', + ); + } + Future scanSelectedLibrary() async { final library = state.selectedLibrary; if (library == null || _api == null || state.isScanning) return;