diff --git a/lib/api/api_provider.dart b/lib/api/api_provider.dart index 0b11499..a991727 100644 --- a/lib/api/api_provider.dart +++ b/lib/api/api_provider.dart @@ -49,8 +49,7 @@ AudiobookshelfApi audiobookshelfApi(Ref ref, Uri? baseUrl) { /// if the user is not authenticated throw an error @Riverpod(keepAlive: true) AudiobookshelfApi authenticatedApi(Ref ref) { - final apiSettings = ref.watch(apiSettingsProvider); - final user = apiSettings.activeUser; + final user = ref.watch(apiSettingsProvider.select((s) => s.activeUser)); if (user == null) { _logger.severe('No active user can not provide authenticated api'); throw StateError('No active user'); diff --git a/lib/api/api_provider.g.dart b/lib/api/api_provider.g.dart index 23c630c..619a729 100644 --- a/lib/api/api_provider.g.dart +++ b/lib/api/api_provider.g.dart @@ -170,7 +170,7 @@ class _AudiobookshelfApiProviderElement Uri? get baseUrl => (origin as AudiobookshelfApiProvider).baseUrl; } -String _$authenticatedApiHash() => r'5cf3329fe3074e3a09e266b4bae78b53e9c01220'; +String _$authenticatedApiHash() => r'284be2c39823c20fb70035a136c430862c28fa27'; /// get the api instance for the authenticated user /// diff --git a/lib/api/library_provider.dart b/lib/api/library_provider.dart index a138a7a..0903015 100644 --- a/lib/api/library_provider.dart +++ b/lib/api/library_provider.dart @@ -4,12 +4,14 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shelfsdk/audiobookshelf_api.dart' show Library; import 'package:vaani/api/api_provider.dart' show authenticatedApiProvider; +import 'package:vaani/settings/api_settings_provider.dart' + show apiSettingsProvider; part 'library_provider.g.dart'; final _logger = Logger('LibraryProvider'); @riverpod -Future currentLibrary(Ref ref, String id) async { +Future library(Ref ref, String id) async { final api = ref.watch(authenticatedApiProvider); final library = await api.libraries.get(libraryId: id); if (library == null) { @@ -24,9 +26,21 @@ Future currentLibrary(Ref ref, String id) async { _logger.warning('No library found in the list of libraries'); return null; } + _logger.fine('Fetched library: ${library}'); return library.library; } +@riverpod +Future currentLibrary(Ref ref) async { + final libraryId = + ref.watch(apiSettingsProvider.select((s) => s.activeLibraryId)); + if (libraryId == null) { + _logger.warning('No active library id found'); + return null; + } + return await ref.watch(libraryProvider(libraryId).future); +} + @riverpod class Libraries extends _$Libraries { @override @@ -37,6 +51,8 @@ class Libraries extends _$Libraries { _logger.warning('Failed to fetch libraries'); return []; } + _logger.fine('Fetched ${libraries.length} libraries'); + ref.keepAlive(); return libraries; } } diff --git a/lib/api/library_provider.g.dart b/lib/api/library_provider.g.dart index 3161d34..8f22251 100644 --- a/lib/api/library_provider.g.dart +++ b/lib/api/library_provider.g.dart @@ -6,7 +6,7 @@ part of 'library_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$currentLibraryHash() => r'f37904b8b43c88a523696d1ed7acf871c3e3326f'; +String _$libraryHash() => r'b62d976f8ab83b2f5823a0fb7dac52fde8fcbffc'; /// Copied from Dart SDK class _SystemHash { @@ -29,27 +29,27 @@ class _SystemHash { } } -/// See also [currentLibrary]. -@ProviderFor(currentLibrary) -const currentLibraryProvider = CurrentLibraryFamily(); +/// See also [library]. +@ProviderFor(library) +const libraryProvider = LibraryFamily(); -/// See also [currentLibrary]. -class CurrentLibraryFamily extends Family> { - /// See also [currentLibrary]. - const CurrentLibraryFamily(); +/// See also [library]. +class LibraryFamily extends Family> { + /// See also [library]. + const LibraryFamily(); - /// See also [currentLibrary]. - CurrentLibraryProvider call( + /// See also [library]. + LibraryProvider call( String id, ) { - return CurrentLibraryProvider( + return LibraryProvider( id, ); } @override - CurrentLibraryProvider getProviderOverride( - covariant CurrentLibraryProvider provider, + LibraryProvider getProviderOverride( + covariant LibraryProvider provider, ) { return call( provider.id, @@ -68,32 +68,31 @@ class CurrentLibraryFamily extends Family> { _allTransitiveDependencies; @override - String? get name => r'currentLibraryProvider'; + String? get name => r'libraryProvider'; } -/// See also [currentLibrary]. -class CurrentLibraryProvider extends AutoDisposeFutureProvider { - /// See also [currentLibrary]. - CurrentLibraryProvider( +/// See also [library]. +class LibraryProvider extends AutoDisposeFutureProvider { + /// See also [library]. + LibraryProvider( String id, ) : this._internal( - (ref) => currentLibrary( - ref as CurrentLibraryRef, + (ref) => library( + ref as LibraryRef, id, ), - from: currentLibraryProvider, - name: r'currentLibraryProvider', + from: libraryProvider, + name: r'libraryProvider', debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') ? null - : _$currentLibraryHash, - dependencies: CurrentLibraryFamily._dependencies, - allTransitiveDependencies: - CurrentLibraryFamily._allTransitiveDependencies, + : _$libraryHash, + dependencies: LibraryFamily._dependencies, + allTransitiveDependencies: LibraryFamily._allTransitiveDependencies, id: id, ); - CurrentLibraryProvider._internal( + LibraryProvider._internal( super._createNotifier, { required super.name, required super.dependencies, @@ -107,12 +106,12 @@ class CurrentLibraryProvider extends AutoDisposeFutureProvider { @override Override overrideWith( - FutureOr Function(CurrentLibraryRef provider) create, + FutureOr Function(LibraryRef provider) create, ) { return ProviderOverride( origin: this, - override: CurrentLibraryProvider._internal( - (ref) => create(ref as CurrentLibraryRef), + override: LibraryProvider._internal( + (ref) => create(ref as LibraryRef), from: from, name: null, dependencies: null, @@ -125,12 +124,12 @@ class CurrentLibraryProvider extends AutoDisposeFutureProvider { @override AutoDisposeFutureProviderElement createElement() { - return _CurrentLibraryProviderElement(this); + return _LibraryProviderElement(this); } @override bool operator ==(Object other) { - return other is CurrentLibraryProvider && other.id == id; + return other is LibraryProvider && other.id == id; } @override @@ -144,20 +143,37 @@ class CurrentLibraryProvider extends AutoDisposeFutureProvider { @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element -mixin CurrentLibraryRef on AutoDisposeFutureProviderRef { +mixin LibraryRef on AutoDisposeFutureProviderRef { /// The parameter `id` of this provider. String get id; } -class _CurrentLibraryProviderElement - extends AutoDisposeFutureProviderElement with CurrentLibraryRef { - _CurrentLibraryProviderElement(super.provider); +class _LibraryProviderElement extends AutoDisposeFutureProviderElement + with LibraryRef { + _LibraryProviderElement(super.provider); @override - String get id => (origin as CurrentLibraryProvider).id; + String get id => (origin as LibraryProvider).id; } -String _$librariesHash() => r'a79954d0b68a8265859c577e36d5596620a72843'; +String _$currentLibraryHash() => r'658498a531e04a01e2b3915a3319101285601118'; + +/// See also [currentLibrary]. +@ProviderFor(currentLibrary) +final currentLibraryProvider = AutoDisposeFutureProvider.internal( + currentLibrary, + name: r'currentLibraryProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$currentLibraryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef CurrentLibraryRef = AutoDisposeFutureProviderRef; +String _$librariesHash() => r'95ebd4d1ac0cc2acf7617dc22895eff0ca30600f'; /// See also [Libraries]. @ProviderFor(Libraries) diff --git a/lib/features/library_browser/view/library_browser_page.dart b/lib/features/library_browser/view/library_browser_page.dart index d12de57..4327b17 100644 --- a/lib/features/library_browser/view/library_browser_page.dart +++ b/lib/features/library_browser/view/library_browser_page.dart @@ -1,47 +1,83 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:vaani/router/router.dart'; +import 'package:vaani/api/library_provider.dart' show currentLibraryProvider; +import 'package:vaani/features/you/view/widgets/library_switch_chip.dart' + show showLibrarySwitcher; +import 'package:vaani/router/router.dart' show Routes; +import 'package:vaani/shared/icons/abs_icons.dart' show AbsIcons; +import 'package:vaani/shared/widgets/not_implemented.dart' + show showNotImplementedToast; class LibraryBrowserPage extends HookConsumerWidget { const LibraryBrowserPage({super.key}); - @override Widget build(BuildContext context, WidgetRef ref) { + final currentLibrary = ref.watch(currentLibraryProvider).valueOrNull; + + // Determine the icon to use, with a fallback + final IconData libraryIconData = + AbsIcons.getIconByName(currentLibrary?.icon) ?? Icons.library_books; + + // Determine the title text + final String appBarTitle = '${currentLibrary?.name ?? 'Your'} Library'; + return Scaffold( - appBar: AppBar( - title: const Text('Library'), - backgroundColor: Colors.transparent, - ), - // a list redirecting to authors, genres, and series pages - body: ListView( - children: [ - ListTile( - title: const Text('Authors'), - leading: const Icon(Icons.person), - trailing: const Icon(Icons.chevron_right), - onTap: () {}, + // Use CustomScrollView to enable slivers + body: CustomScrollView( + slivers: [ + SliverAppBar( + pinned: true, + // floating: true, // Optional: uncomment if you want floating behavior + // snap: + // true, // Optional: uncomment if you want snapping behavior (usually with floating: true) + leading: IconButton( + icon: Icon(libraryIconData), + tooltip: 'Switch Library', // Helpful tooltip for users + onPressed: () { + showLibrarySwitcher(context, ref); + }, + ), + title: Text(appBarTitle), ), - ListTile( - title: const Text('Genres'), - leading: const Icon(Icons.category), - trailing: const Icon(Icons.chevron_right), - onTap: () {}, - ), - ListTile( - title: const Text('Series'), - leading: const Icon(Icons.list), - trailing: const Icon(Icons.chevron_right), - onTap: () {}, - ), - // Downloads - ListTile( - title: const Text('Downloads'), - leading: const Icon(Icons.download), - trailing: const Icon(Icons.chevron_right), - onTap: () { - GoRouter.of(context).pushNamed(Routes.downloads.name); - }, + SliverList( + delegate: SliverChildListDelegate( + [ + ListTile( + title: const Text('Authors'), + leading: const Icon(Icons.person), + trailing: const Icon(Icons.chevron_right), + onTap: () { + showNotImplementedToast(context); + }, + ), + ListTile( + title: const Text('Genres'), + leading: const Icon(Icons.category), + trailing: const Icon(Icons.chevron_right), + onTap: () { + showNotImplementedToast(context); + }, + ), + ListTile( + title: const Text('Series'), + leading: const Icon(Icons.list), + trailing: const Icon(Icons.chevron_right), + onTap: () { + showNotImplementedToast(context); + }, + ), + // Downloads + ListTile( + title: const Text('Downloads'), + leading: const Icon(Icons.download), + trailing: const Icon(Icons.chevron_right), + onTap: () { + GoRouter.of(context).pushNamed(Routes.downloads.name); + }, + ), + ], + ), ), ], ), diff --git a/lib/features/logging/view/logs_page.dart b/lib/features/logging/view/logs_page.dart index b774963..74d1ad3 100644 --- a/lib/features/logging/view/logs_page.dart +++ b/lib/features/logging/view/logs_page.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; diff --git a/lib/features/you/view/widgets/library_switch_chip.dart b/lib/features/you/view/widgets/library_switch_chip.dart new file mode 100644 index 0000000..1463b8f --- /dev/null +++ b/lib/features/you/view/widgets/library_switch_chip.dart @@ -0,0 +1,224 @@ +import 'package:flutter/material.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shelfsdk/audiobookshelf_api.dart' show Library; +import 'package:vaani/api/library_provider.dart'; +import 'package:vaani/settings/api_settings_provider.dart' + show apiSettingsProvider; +import 'package:vaani/shared/icons/abs_icons.dart'; +import 'dart:io' show Platform; + +import 'package:flutter/foundation.dart'; +import 'package:vaani/main.dart' show appLogger; + +class LibrarySwitchChip extends HookConsumerWidget { + const LibrarySwitchChip({ + super.key, + required this.libraries, + }); + final List libraries; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final apiSettings = ref.watch(apiSettingsProvider); + + return ActionChip( + avatar: Icon( + AbsIcons.getIconByName( + apiSettings.activeLibraryId != null + ? libraries + .firstWhere( + (lib) => lib.id == apiSettings.activeLibraryId, + ) + .icon + : libraries.first.icon, + ), + ), // Replace with your icon + label: const Text('Change Library'), + // Enable only if libraries are loaded and not empty + onPressed: libraries.isNotEmpty + ? () => showLibrarySwitcher( + context, + ref, + ) + : null, // Disable if no libraries + ); + } +} + +// --- Helper Function to Show the Switcher --- +void showLibrarySwitcher( + BuildContext context, + WidgetRef ref, +) { + final content = _LibrarySelectionContent(); + + // --- Platform-Specific UI --- + bool isDesktop = false; + if (!kIsWeb) { + // dart:io Platform is not available on web + isDesktop = Platform.isLinux || Platform.isMacOS || Platform.isWindows; + } else { + // Basic web detection (might need refinement based on screen size) + // Consider using MediaQuery for a size-based check instead for web/tablet + final size = MediaQuery.of(context).size; + isDesktop = size.width > 600; // Example threshold for "desktop-like" layout + } + + if (isDesktop) { + // --- Desktop: Use AlertDialog --- + showDialog( + context: context, + builder: (dialogContext) => AlertDialog( + title: const Text('Select Library'), + content: SizedBox( + // Constrain size for dialogs + width: 300, // Adjust as needed + // Make content scrollable if list is long + child: Scrollbar(child: content), + ), + actions: [ + TextButton( + onPressed: () { + // Invalidate the provider to trigger a refetch + ref.invalidate(librariesProvider); + Navigator.pop(dialogContext); + }, + child: const Text('Refresh'), + ), + TextButton( + onPressed: () => Navigator.pop(dialogContext), + child: const Text('Cancel'), + ), + ], + ), + ); + } else { + // --- Mobile/Tablet: Use BottomSheet --- + showModalBottomSheet( + context: context, + // Make it scrollable and control height + isScrollControlled: true, + constraints: BoxConstraints( + maxHeight: + MediaQuery.of(context).size.height * 0.6, // Max 60% of screen + ), + builder: (sheetContext) => Padding( + // Add padding within the bottom sheet + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, // Take minimum necessary height + children: [ + const Text( + 'Select Library', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + const Divider(), + Flexible( + // Allow the list to take remaining space and scroll + child: Scrollbar(child: content), + ), + const SizedBox(height: 10), + ElevatedButton.icon( + icon: const Icon(Icons.refresh), + label: const Text('Refresh'), + onPressed: () { + // Invalidate the provider to trigger a refetch + ref.invalidate(librariesProvider); + }, + ), + ], + ), + ), + ); + } +} + +// --- Widget for the Selection List Content (Reusable) --- +class _LibrarySelectionContent extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final librariesAsyncValue = ref.watch(librariesProvider); + final currentLibraryId = ref.watch( + apiSettingsProvider.select((settings) => settings.activeLibraryId), + ); + final errorColor = Theme.of(context).colorScheme.error; + return librariesAsyncValue.when( + // --- Loading State --- + loading: () => const Center(child: CircularProgressIndicator()), + + // --- Error State --- + error: (error, stackTrace) => Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.error_outline, color: errorColor), + const SizedBox(height: 10), + Text( + 'Error loading libraries: $error', + textAlign: TextAlign.center, + style: TextStyle(color: errorColor), + ), + const SizedBox(height: 16), + ElevatedButton.icon( + icon: const Icon(Icons.refresh), + label: const Text('Retry'), + onPressed: () { + // Invalidate the provider to trigger a refetch + ref.invalidate(librariesProvider); + }, + ), + ], + ), + ), + ), + + // --- Data State --- + data: (libraries) { + // Handle case where data loaded successfully but is empty + if (libraries.isEmpty) { + return const Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: Text('No libraries available.'), + ), + ); + } + + // Build the list if libraries are available + return Scrollbar( + // Add scrollbar for potentially long lists + child: ListView.builder( + shrinkWrap: true, // Important for Dialog/BottomSheet sizing + itemCount: libraries.length, + itemBuilder: (context, index) { + final library = libraries[index]; + final bool isSelected = library.id == currentLibraryId; + + return ListTile( + title: Text(library.name), + leading: Icon(AbsIcons.getIconByName(library.icon)), + selected: isSelected, + trailing: isSelected ? const Icon(Icons.check) : null, + onTap: () { + appLogger.info( + 'Selected library: ${library.name} (ID: ${library.id})'); + // Get current settings state + final currentSettings = ref.read(apiSettingsProvider); + // Update the active library ID + ref.read(apiSettingsProvider.notifier).updateState( + currentSettings.copyWith(activeLibraryId: library.id), + ); + // Close the dialog/bottom sheet + Navigator.pop(context); + }, + ); + }, + ), + ); + }, + ); + } +} diff --git a/lib/features/you/view/you_page.dart b/lib/features/you/view/you_page.dart index 69eeecc..cfe15cd 100644 --- a/lib/features/you/view/you_page.dart +++ b/lib/features/you/view/you_page.dart @@ -1,19 +1,12 @@ -import 'dart:io' show Platform; - -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shelfsdk/audiobookshelf_api.dart' show Library; import 'package:vaani/api/api_provider.dart'; import 'package:vaani/api/library_provider.dart' show librariesProvider; import 'package:vaani/features/player/view/mini_player_bottom_padding.dart'; -import 'package:vaani/main.dart' show appLogger; +import 'package:vaani/features/you/view/widgets/library_switch_chip.dart'; import 'package:vaani/router/router.dart'; -import 'package:vaani/settings/api_settings_provider.dart' - show apiSettingsProvider; import 'package:vaani/settings/constants.dart'; -import 'package:vaani/shared/icons/abs_icons.dart'; import 'package:vaani/shared/utils.dart'; import 'package:vaani/shared/widgets/not_implemented.dart'; import 'package:vaani/shared/widgets/vaani_logo.dart'; @@ -27,8 +20,6 @@ class YouPage extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final api = ref.watch(authenticatedApiProvider); final librariesAsyncValue = ref.watch(librariesProvider); - // Get current settings to know the active library ID later - final apiSettings = ref.watch(apiSettingsProvider); return Scaffold( appBar: AppBar( // title: const Text('You'), @@ -77,31 +68,8 @@ class YouPage extends HookConsumerWidget { }, ), librariesAsyncValue.when( - data: (libraries) => ActionChip( - avatar: Icon( - AbsIcons.getIconByName( - apiSettings.activeLibraryId != null - ? libraries - .firstWhere( - (lib) => - lib.id == - apiSettings.activeLibraryId, - ) - .icon - : libraries.first.icon, - ), - ), // Replace with your icon - label: const Text('Change Library'), - // Enable only if libraries are loaded and not empty - onPressed: libraries.isNotEmpty - ? () => _showLibrarySwitcher( - context, - ref, - libraries, - apiSettings.activeLibraryId, - ) - : null, // Disable if no libraries - ), + data: (libraries) => + LibrarySwitchChip(libraries: libraries), loading: () => const ActionChip( avatar: SizedBox( width: 18, @@ -208,124 +176,6 @@ class YouPage extends HookConsumerWidget { } } -// --- Helper Function to Show the Switcher --- -void _showLibrarySwitcher( - BuildContext context, - WidgetRef ref, - List libraries, // Pass loaded libraries - String? currentLibraryId, // Pass current ID -) { - final content = _LibrarySelectionContent( - libraries: libraries, - currentLibraryId: currentLibraryId, - ); - - // --- Platform-Specific UI --- - bool isDesktop = false; - if (!kIsWeb) { - // dart:io Platform is not available on web - isDesktop = Platform.isLinux || Platform.isMacOS || Platform.isWindows; - } else { - // Basic web detection (might need refinement based on screen size) - // Consider using MediaQuery for a size-based check instead for web/tablet - final size = MediaQuery.of(context).size; - isDesktop = size.width > 600; // Example threshold for "desktop-like" layout - } - - if (isDesktop) { - // --- Desktop: Use AlertDialog --- - showDialog( - context: context, - builder: (dialogContext) => AlertDialog( - title: const Text('Select Library'), - content: SizedBox( - // Constrain size for dialogs - width: 300, // Adjust as needed - // Make content scrollable if list is long - child: Scrollbar(child: content), - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(dialogContext), - child: const Text('Cancel'), - ), - ], - ), - ); - } else { - // --- Mobile/Tablet: Use BottomSheet --- - showModalBottomSheet( - context: context, - // Make it scrollable and control height - isScrollControlled: true, - constraints: BoxConstraints( - maxHeight: - MediaQuery.of(context).size.height * 0.6, // Max 60% of screen - ), - builder: (sheetContext) => Padding( - // Add padding within the bottom sheet - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisSize: MainAxisSize.min, // Take minimum necessary height - children: [ - const Text( - 'Select Library', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - const Divider(), - Flexible( - // Allow the list to take remaining space and scroll - child: Scrollbar(child: content), - ), - ], - ), - ), - ); - } -} - -// --- Widget for the Selection List Content (Reusable) --- -class _LibrarySelectionContent extends ConsumerWidget { - final List libraries; - final String? currentLibraryId; - - const _LibrarySelectionContent({ - required this.libraries, - this.currentLibraryId, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return ListView.builder( - shrinkWrap: true, // Important for Dialog/BottomSheet sizing - itemCount: libraries.length, - itemBuilder: (context, index) { - final library = libraries[index]; - final bool isSelected = library.id == currentLibraryId; - - return ListTile( - title: Text(library.name), - leading: Icon(AbsIcons.getIconByName(library.icon)), - selected: isSelected, // Makes the tile visually selected - onTap: () { - appLogger - .info('Selected library: ${library.name} (ID: ${library.id})'); - // Get current settings state - final currentSettings = ref.read(apiSettingsProvider); - // Update the active library ID - ref.read(apiSettingsProvider.notifier).updateState( - currentSettings.copyWith(activeLibraryId: library.id), - ); - // Close the dialog/bottom sheet - Navigator.pop(context); - }, - ); - }, - ); - } -} - class UserBar extends HookConsumerWidget { const UserBar({ super.key, diff --git a/lib/router/scaffold_with_nav_bar.dart b/lib/router/scaffold_with_nav_bar.dart index 0a89162..27c3355 100644 --- a/lib/router/scaffold_with_nav_bar.dart +++ b/lib/router/scaffold_with_nav_bar.dart @@ -2,12 +2,15 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:miniplayer/miniplayer.dart'; +import 'package:vaani/api/library_provider.dart' show currentLibraryProvider; import 'package:vaani/features/explore/providers/search_controller.dart'; import 'package:vaani/features/player/providers/player_form.dart'; import 'package:vaani/features/player/view/audiobook_player.dart'; import 'package:vaani/features/player/view/player_when_expanded.dart'; +import 'package:vaani/features/you/view/widgets/library_switch_chip.dart'; import 'package:vaani/main.dart'; import 'package:vaani/router/router.dart'; +import 'package:vaani/shared/icons/abs_icons.dart' show AbsIcons; // stack to track changes in navigationShell.currentIndex // home is always at index 0 and at the start and should be the last before popping @@ -111,17 +114,39 @@ class ScaffoldWithNavBar extends HookConsumerWidget { // world scenario, the items would most likely be generated from the // branches of the shell route, which can be fetched using // `navigationShell.route.branches`. - destinations: _navigationItems - .map( - (item) => NavigationDestination( - icon: Icon(item.icon), - selectedIcon: item.activeIcon != null - ? Icon(item.activeIcon) - : Icon(item.icon), - label: item.name, - ), - ) - .toList(), + destinations: _navigationItems.map((item) { + final isDestinationLibrary = item.name == 'Library'; + var currentLibrary = + ref.watch(currentLibraryProvider).valueOrNull; + final libraryIcon = AbsIcons.getIconByName( + currentLibrary?.icon, + ); + final destinationWidget = NavigationDestination( + icon: Icon( + isDestinationLibrary ? libraryIcon ?? item.icon : item.icon, + ), + selectedIcon: Icon( + isDestinationLibrary + ? libraryIcon ?? item.activeIcon + : item.activeIcon, + ), + label: isDestinationLibrary + ? currentLibrary?.name ?? item.name + : item.name, + tooltip: item.tooltip, + ); + if (isDestinationLibrary) { + return GestureDetector( + onSecondaryTap: () => showLibrarySwitcher(context, ref), + onDoubleTap: () => showLibrarySwitcher(context, ref), + child: + destinationWidget, // Wrap the actual NavigationDestination + ); + } else { + // Return the unwrapped destination for other items + return destinationWidget; + } + }).toList(), selectedIndex: navigationShell.currentIndex, onDestinationSelected: (int index) => _onTap(context, index, ref), ), @@ -191,16 +216,19 @@ const _navigationItems = [ name: 'Library', icon: Icons.book_outlined, activeIcon: Icons.book, + tooltip: 'Browse your library', ), _NavigationItem( name: 'Explore', icon: Icons.search_outlined, activeIcon: Icons.search, + tooltip: 'Search and Explore', ), _NavigationItem( name: 'You', icon: Icons.account_circle_outlined, activeIcon: Icons.account_circle, + tooltip: 'Your Profile and Settings', ), ]; @@ -208,10 +236,12 @@ class _NavigationItem { const _NavigationItem({ required this.name, required this.icon, - this.activeIcon, + required this.activeIcon, + this.tooltip, }); final String name; final IconData icon; - final IconData? activeIcon; + final IconData activeIcon; + final String? tooltip; }