import 'dart:async'; import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/timeline.model.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; import 'package:immich_mobile/providers/infrastructure/people.provider.dart'; import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart'; import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'; import 'package:immich_mobile/providers/tab.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/routing/router.dart'; @RoutePage() class TabShellPage extends ConsumerStatefulWidget { const TabShellPage({super.key}); @override ConsumerState createState() => _TabShellPageState(); } class _TabShellPageState extends ConsumerState { @override Widget build(BuildContext context) { final isScreenLandscape = context.orientation == Orientation.landscape; final isReadonlyModeEnabled = ref.watch(readonlyModeProvider); final navigationDestinations = [ NavigationDestination( label: 'photos'.tr(), icon: const Icon(Icons.photo_library_outlined), selectedIcon: Icon(Icons.photo_library, color: context.primaryColor), ), NavigationDestination( label: 'search'.tr(), icon: const Icon(Icons.search_rounded), selectedIcon: Icon(Icons.search, color: context.primaryColor), enabled: !isReadonlyModeEnabled, ), NavigationDestination( label: 'albums'.tr(), icon: const Icon(Icons.photo_album_outlined), selectedIcon: Icon(Icons.photo_album_rounded, color: context.primaryColor), enabled: !isReadonlyModeEnabled, ), NavigationDestination( label: 'library'.tr(), icon: const Icon(Icons.space_dashboard_outlined), selectedIcon: Icon(Icons.space_dashboard_rounded, color: context.primaryColor), enabled: !isReadonlyModeEnabled, ), ]; Widget navigationRail(TabsRouter tabsRouter) { return NavigationRail( destinations: navigationDestinations .map( (e) => NavigationRailDestination( icon: e.icon, label: Text(e.label), selectedIcon: e.selectedIcon, disabled: !e.enabled, ), ) .toList(), onDestinationSelected: (index) => _onNavigationSelected(tabsRouter, index, ref), selectedIndex: tabsRouter.activeIndex, labelType: NavigationRailLabelType.all, groupAlignment: 0.0, ); } return AutoTabsRouter( routes: [const MainTimelineRoute(), DriftSearchRoute(), const DriftAlbumsRoute(), const DriftLibraryRoute()], duration: const Duration(milliseconds: 600), transitionBuilder: (context, child, animation) => FadeTransition(opacity: animation, child: child), builder: (context, child) { final tabsRouter = AutoTabsRouter.of(context); return PopScope( canPop: tabsRouter.activeIndex == 0, onPopInvokedWithResult: (didPop, _) => !didPop ? tabsRouter.setActiveIndex(0) : null, child: Scaffold( resizeToAvoidBottomInset: false, body: isScreenLandscape ? Row( children: [ navigationRail(tabsRouter), const VerticalDivider(), Expanded(child: child), ], ) : child, bottomNavigationBar: _BottomNavigationBar(tabsRouter: tabsRouter, destinations: navigationDestinations), ), ); }, ); } } void _onNavigationSelected(TabsRouter router, int index, WidgetRef ref) { // On Photos page menu tapped if (router.activeIndex == 0 && index == 0) { EventStream.shared.emit(const ScrollToTopEvent()); } if (index == 0) { ref.invalidate(driftMemoryFutureProvider); } // On Search page tapped if (router.activeIndex == 1 && index == 1) { ref.read(searchInputFocusProvider).requestFocus(); } // Album page if (index == 2) { ref.read(remoteAlbumProvider.notifier).refresh(); } // Library page if (index == 3) { ref.invalidate(localAlbumProvider); ref.invalidate(driftGetAllPeopleProvider); } ref.read(hapticFeedbackProvider.notifier).selectionClick(); router.setActiveIndex(index); ref.read(tabProvider.notifier).state = TabEnum.values[index]; } class _BottomNavigationBar extends ConsumerStatefulWidget { const _BottomNavigationBar({required this.tabsRouter, required this.destinations}); final List destinations; final TabsRouter tabsRouter; @override ConsumerState createState() => _BottomNavigationBarState(); } class _BottomNavigationBarState extends ConsumerState<_BottomNavigationBar> { bool hideNavigationBar = false; StreamSubscription? _eventSubscription; @override void initState() { super.initState(); _eventSubscription = EventStream.shared.listen(_onEvent); } void _onEvent(MultiSelectToggleEvent event) { setState(() { hideNavigationBar = event.isEnabled; }); } @override void dispose() { _eventSubscription?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { final isScreenLandscape = context.orientation == Orientation.landscape; if (isScreenLandscape || hideNavigationBar) { return const SizedBox.shrink(); } return NavigationBar( selectedIndex: widget.tabsRouter.activeIndex, onDestinationSelected: (index) => _onNavigationSelected(widget.tabsRouter, index, ref), destinations: widget.destinations, ); } }