diff --git a/lib/screens/libraries_screen.dart b/lib/screens/libraries_screen.dart index d72c97af..52d19c99 100644 --- a/lib/screens/libraries_screen.dart +++ b/lib/screens/libraries_screen.dart @@ -12,7 +12,6 @@ import '../utils/provider_extensions.dart'; import '../widgets/media_card.dart'; import '../widgets/desktop_app_bar.dart'; import '../widgets/app_bar_back_button.dart'; -import '../widgets/context_menu_wrapper.dart'; import '../services/storage_service.dart'; import '../services/settings_service.dart'; import '../mixins/refreshable.dart'; @@ -43,7 +42,6 @@ class _LibrariesScreenState extends State PlexSort? _selectedSort; bool _isSortDescending = false; bool _isInitialLoad = true; - bool _isReorderMode = false; @override void initState() { @@ -406,44 +404,35 @@ class _LibrariesScreenState extends State } } - Future _hideLibrary(PlexLibrary library) async { - // Hide library using provider + Future _toggleLibraryVisibility(PlexLibrary library) async { final hiddenLibrariesProvider = Provider.of( context, listen: false, ); - await hiddenLibrariesProvider.hideLibrary(library.key); + final isHidden = hiddenLibrariesProvider.hiddenLibraryKeys.contains(library.key); - // Reload libraries to update the visible list - await _loadLibraries(); + if (isHidden) { + await hiddenLibrariesProvider.unhideLibrary(library.key); + } else { + // Check if we're hiding the currently selected library + final isCurrentlySelected = _selectedLibraryKey == library.key; - // Show snackbar with undo option - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Hidden "${library.title}"'), - action: SnackBarAction( - label: 'Undo', - onPressed: () => _unhideLibrary(library.key), - ), - duration: const Duration(seconds: 4), - ), - ); + await hiddenLibrariesProvider.hideLibrary(library.key); + + // If we just hid the selected library, select the first visible one + if (isCurrentlySelected) { + // Compute visible libraries after hiding + final visibleLibraries = _allLibraries + .where((lib) => !hiddenLibrariesProvider.hiddenLibraryKeys.contains(lib.key)) + .toList(); + + if (visibleLibraries.isNotEmpty) { + _loadLibraryContent(visibleLibraries.first.key); + } + } } } - Future _unhideLibrary(String libraryKey) async { - // Unhide library using provider - final hiddenLibrariesProvider = Provider.of( - context, - listen: false, - ); - await hiddenLibrariesProvider.unhideLibrary(libraryKey); - - // Reload libraries to update the visible list - await _loadLibraries(); - } - void _showFiltersBottomSheet() { showModalBottomSheet( context: context, @@ -483,6 +472,29 @@ class _LibrariesScreenState extends State ); } + void _showLibraryManagementSheet() { + final hiddenLibrariesProvider = Provider.of( + context, + listen: false, + ); + + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => _LibraryManagementSheet( + allLibraries: List.from(_allLibraries), + hiddenLibraryKeys: hiddenLibrariesProvider.hiddenLibraryKeys, + onReorder: (reorderedLibraries) { + setState(() { + _allLibraries = reorderedLibraries; + }); + _saveLibraryOrder(); + }, + onToggleVisibility: _toggleLibraryVisibility, + ), + ); + } + @override Widget build(BuildContext context) { // Watch for hidden libraries changes to trigger rebuild @@ -508,15 +520,8 @@ class _LibrariesScreenState extends State actions: [ if (_allLibraries.isNotEmpty) IconButton( - icon: Icon( - _isReorderMode ? Icons.check : Icons.edit, - semanticLabel: _isReorderMode ? 'Done' : 'Reorder', - ), - onPressed: () { - setState(() { - _isReorderMode = !_isReorderMode; - }); - }, + icon: const Icon(Icons.edit, semanticLabel: 'Manage Libraries'), + onPressed: _showLibraryManagementSheet, ), if (_sortOptions.isNotEmpty) IconButton( @@ -593,66 +598,7 @@ class _LibrariesScreenState extends State horizontal: 16, vertical: 8, ), - child: _isReorderMode - ? ReorderableListView.builder( - scrollDirection: Axis.horizontal, - onReorder: _reorderLibraries, - itemCount: _allLibraries.length, - proxyDecorator: (child, index, animation) { - return Material( - elevation: 4, - borderRadius: BorderRadius.circular(8), - child: child, - ); - }, - itemBuilder: (context, index) { - final library = _allLibraries[index]; - final isSelected = library.key == _selectedLibraryKey; - final isHidden = hiddenKeys.contains(library.key); - final t = tokens(context); - return Container( - key: ValueKey(library.key), - margin: const EdgeInsets.only(right: 8), - child: Chip( - label: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.drag_handle, - size: 16, - color: isSelected ? t.bg : t.text, - ), - const SizedBox(width: 6), - Icon( - _getLibraryIcon(library.type), - size: 16, - color: isSelected ? t.bg : t.text, - ), - const SizedBox(width: 6), - Text(library.title), - if (isHidden) ...[ - const SizedBox(width: 6), - Icon( - Icons.visibility_off, - size: 16, - color: isSelected ? t.bg : t.text, - ), - ], - ], - ), - backgroundColor: isSelected ? t.text : t.surface, - side: BorderSide(color: t.outline), - labelStyle: TextStyle( - color: isSelected ? t.bg : t.text, - fontWeight: isSelected - ? FontWeight.w600 - : FontWeight.w400, - ), - ), - ); - }, - ) - : SingleChildScrollView( + child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: List.generate(visibleLibraries.length, (index) { @@ -661,49 +607,35 @@ class _LibrariesScreenState extends State final t = tokens(context); return Padding( padding: const EdgeInsets.only(right: 8), - child: ContextMenuWrapper( - menuItems: [ - ContextMenuItem( - value: 'hide', - icon: Icons.visibility_off, - label: 'Hide "${library.title}"', - ), - ], - onMenuItemSelected: (value) { - if (value == 'hide') { - _hideLibrary(library); + child: ChoiceChip( + label: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + _getLibraryIcon(library.type), + size: 16, + color: isSelected ? t.bg : t.text, + ), + const SizedBox(width: 6), + Text(library.title), + ], + ), + selected: isSelected, + onSelected: (selected) { + if (selected) { + _loadLibraryContent(library.key); } }, - child: ChoiceChip( - label: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - _getLibraryIcon(library.type), - size: 16, - color: isSelected ? t.bg : t.text, - ), - const SizedBox(width: 6), - Text(library.title), - ], - ), - selected: isSelected, - onSelected: (selected) { - if (selected) { - _loadLibraryContent(library.key); - } - }, - backgroundColor: t.surface, - selectedColor: t.text, - side: BorderSide(color: t.outline), - labelStyle: TextStyle( - color: isSelected ? t.bg : t.text, - fontWeight: isSelected - ? FontWeight.w600 - : FontWeight.w400, - ), - showCheckmark: false, + backgroundColor: t.surface, + selectedColor: t.text, + side: BorderSide(color: t.outline), + labelStyle: TextStyle( + color: isSelected ? t.bg : t.text, + fontWeight: isSelected + ? FontWeight.w600 + : FontWeight.w400, ), + showCheckmark: false, ), ); }), @@ -1292,3 +1224,149 @@ class _SortBottomSheetState extends State<_SortBottomSheet> { ); } } + +class _LibraryManagementSheet extends StatefulWidget { + final List allLibraries; + final Set hiddenLibraryKeys; + final Function(List) onReorder; + final Function(PlexLibrary) onToggleVisibility; + + const _LibraryManagementSheet({ + required this.allLibraries, + required this.hiddenLibraryKeys, + required this.onReorder, + required this.onToggleVisibility, + }); + + @override + State<_LibraryManagementSheet> createState() => _LibraryManagementSheetState(); +} + +class _LibraryManagementSheetState extends State<_LibraryManagementSheet> { + late List _tempLibraries; + + @override + void initState() { + super.initState(); + _tempLibraries = List.from(widget.allLibraries); + } + + void _reorderLibraries(int oldIndex, int newIndex) { + setState(() { + if (newIndex > oldIndex) { + newIndex -= 1; + } + final library = _tempLibraries.removeAt(oldIndex); + _tempLibraries.insert(newIndex, library); + }); + // Apply immediately + widget.onReorder(_tempLibraries); + } + + IconData _getLibraryIcon(String type) { + switch (type.toLowerCase()) { + case 'movie': + return Icons.movie; + case 'show': + return Icons.tv; + case 'artist': + return Icons.music_note; + case 'photo': + return Icons.photo; + default: + return Icons.folder; + } + } + + @override + Widget build(BuildContext context) { + // Watch provider to rebuild when hidden libraries change + final hiddenLibrariesProvider = context.watch(); + final hiddenLibraryKeys = hiddenLibrariesProvider.hiddenLibraryKeys; + + return DraggableScrollableSheet( + initialChildSize: 0.7, + minChildSize: 0.5, + maxChildSize: 0.95, + expand: false, + builder: (context, scrollController) { + return Column( + children: [ + // Header + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: Theme.of(context).dividerColor), + ), + ), + child: Row( + children: [ + const Icon(Icons.edit), + const SizedBox(width: 12), + const Expanded( + child: Text( + 'Manage Libraries', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.pop(context), + ), + ], + ), + ), + + // Reorderable library list + Expanded( + child: ReorderableListView.builder( + scrollController: scrollController, + onReorder: _reorderLibraries, + itemCount: _tempLibraries.length, + padding: const EdgeInsets.symmetric(vertical: 8), + buildDefaultDragHandles: false, + itemBuilder: (context, index) { + final library = _tempLibraries[index]; + final isHidden = hiddenLibraryKeys.contains(library.key); + + return ReorderableDragStartListener( + key: ValueKey(library.key), + index: index, + child: Opacity( + opacity: isHidden ? 0.5 : 1.0, + child: ListTile( + leading: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.drag_indicator, + color: Theme.of(context).textTheme.bodyMedium?.color?.withOpacity(0.5), + ), + const SizedBox(width: 8), + Icon(_getLibraryIcon(library.type)), + ], + ), + title: Text(library.title), + trailing: IconButton( + icon: Icon( + isHidden ? Icons.visibility_off : Icons.visibility, + ), + onPressed: () => widget.onToggleVisibility(library), + tooltip: isHidden ? 'Show library' : 'Hide library', + ), + ), + ), + ); + }, + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 96927186..d3e372e0 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -3,11 +3,8 @@ import 'package:provider/provider.dart'; import 'package:hotkey_manager/hotkey_manager.dart'; import '../providers/theme_provider.dart'; import '../providers/settings_provider.dart'; -import '../providers/plex_client_provider.dart'; -import '../providers/hidden_libraries_provider.dart'; import '../services/settings_service.dart' as settings; import '../services/keyboard_shortcuts_service.dart'; -import '../models/plex_library.dart'; import '../widgets/desktop_app_bar.dart'; import '../widgets/hotkey_recorder_widget.dart'; import 'about_screen.dart'; @@ -46,21 +43,6 @@ class _SettingsScreenState extends State { }); } - Future _unhideLibrary(String libraryKey) async { - // Unhide library using provider - final hiddenLibrariesProvider = Provider.of( - context, - listen: false, - ); - await hiddenLibrariesProvider.unhideLibrary(libraryKey); - - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Library shown')), - ); - } - } - @override Widget build(BuildContext context) { if (_isLoading) { @@ -77,8 +59,6 @@ class _SettingsScreenState extends State { delegate: SliverChildListDelegate([ _buildAppearanceSection(), const SizedBox(height: 24), - _buildLibraryManagementSection(), - const SizedBox(height: 24), _buildVideoPlaybackSection(), const SizedBox(height: 24), _buildKeyboardShortcutsSection(), @@ -151,92 +131,6 @@ class _SettingsScreenState extends State { ); } - Widget _buildLibraryManagementSection() { - // Watch for hidden libraries changes to trigger rebuild - final hiddenLibrariesProvider = context.watch(); - final hiddenKeys = hiddenLibrariesProvider.hiddenLibraryKeys; - - return Card( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'Library Management', - style: Theme.of( - context, - ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold), - ), - ), - if (hiddenKeys.isEmpty) - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), - child: Text( - 'No hidden libraries', - style: TextStyle(color: Colors.grey[600]), - ), - ) - else - // Use FutureBuilder to fetch library details for hidden keys - FutureBuilder>( - future: _fetchHiddenLibraries(hiddenKeys), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Padding( - padding: EdgeInsets.all(16), - child: Center(child: CircularProgressIndicator()), - ); - } - - if (snapshot.hasError || !snapshot.hasData) { - return Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), - child: Text( - 'Error loading hidden libraries', - style: TextStyle(color: Colors.grey[600]), - ), - ); - } - - final hiddenLibraries = snapshot.data!; - return Column( - children: hiddenLibraries.map((library) { - return ListTile( - leading: const Icon(Icons.visibility_off), - title: Text(library.title), - subtitle: Text('${library.type} library'), - trailing: TextButton( - onPressed: () => _unhideLibrary(library.key), - child: const Text('Show'), - ), - ); - }).toList(), - ); - }, - ), - ], - ), - ); - } - - Future> _fetchHiddenLibraries(Set hiddenKeys) async { - final clientProvider = Provider.of( - context, - listen: false, - ); - final client = clientProvider.client; - - if (client == null) { - return []; - } - - final allLibraries = await client.getLibraries(); - return allLibraries - .where((lib) => hiddenKeys.contains(lib.key)) - .toList(); - } - Widget _buildVideoPlaybackSection() { return Card( child: Column(