import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:go_router/go_router.dart'; import '../../services/secure_chat_service.dart'; import '../../theme/app_theme.dart'; import '../notifications/notifications_screen.dart'; import '../compose/compose_screen.dart'; import '../search/search_screen.dart'; import '../beacon/beacon_screen.dart'; import '../quips/create/quip_creation_flow.dart'; import '../secure_chat/secure_chat_full_screen.dart'; import '../../widgets/radial_menu_overlay.dart'; /// Root shell for the main tabs. The active tab is controlled by GoRouter's /// [StatefulNavigationShell] so navigation state and tab selection stay in sync. class HomeShell extends StatefulWidget { final StatefulNavigationShell navigationShell; const HomeShell({super.key, required this.navigationShell}); @override State createState() => _HomeShellState(); } class _HomeShellState extends State with WidgetsBindingObserver { bool _isRadialMenuVisible = false; final SecureChatService _chatService = SecureChatService(); @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _chatService.startBackgroundSync(); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _chatService.stopBackgroundSync(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { _chatService.startBackgroundSync(); } else if (state == AppLifecycleState.paused) { _chatService.stopBackgroundSync(); } } @override Widget build(BuildContext context) { final currentIndex = widget.navigationShell.currentIndex; return Scaffold( appBar: _buildAppBar(), body: Stack( children: [ NavigationShellScope( currentIndex: currentIndex, child: widget.navigationShell, ), RadialMenuOverlay( isVisible: _isRadialMenuVisible, onDismiss: () => setState(() => _isRadialMenuVisible = false), onPostTap: () { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const ComposeScreen(), ), ); }, onQuipTap: () { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const QuipCreationFlow(), ), ); }, onBeaconTap: () { Navigator.of(context).push( MaterialPageRoute( builder: (_) => const BeaconScreen(), ), ); }, ), ], ), floatingActionButton: Transform.translate( offset: const Offset(0, 12), child: GestureDetector( onTap: () => setState(() => _isRadialMenuVisible = !_isRadialMenuVisible), child: Container( width: 56, height: 56, decoration: BoxDecoration( color: AppTheme.navyBlue, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: AppTheme.navyBlue.withOpacity(0.4), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: const Icon( Icons.add, color: Colors.white, size: 32, ), ), ), ), floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, bottomNavigationBar: Padding( padding: const EdgeInsets.only(bottom: 2), child: BottomAppBar( notchMargin: 8.0, padding: EdgeInsets.zero, height: 58, clipBehavior: Clip.antiAlias, shape: const CircularNotchedRectangle(), child: ClipRRect( borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), child: Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildNavBarItem( icon: Icons.home_outlined, activeIcon: Icons.home, index: 0, label: 'Home', ), _buildNavBarItem( icon: Icons.explore_outlined, activeIcon: Icons.explore, index: 1, label: 'Discover', ), const SizedBox(width: 48), _buildNavBarItem( icon: Icons.play_circle_outline, activeIcon: Icons.play_circle, index: 2, label: 'Quips', ), _buildNavBarItem( icon: Icons.person_outline, activeIcon: Icons.person, index: 3, label: 'Profile', ), ], ), ), ), ), ), ); } PreferredSizeWidget _buildAppBar() { return AppBar( title: Image.asset( 'assets/images/toplogo.png', height: 38, fit: BoxFit.contain, ), centerTitle: false, elevation: 0, backgroundColor: AppTheme.scaffoldBg, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(16), bottomRight: Radius.circular(16), ), ), actions: [ IconButton( icon: Icon(Icons.search, color: AppTheme.navyBlue), tooltip: 'Search', onPressed: () { Navigator.of(context).push( MaterialPageRoute(builder: (_) => const SearchScreen()), ); }, ), IconButton( icon: Icon(Icons.chat_bubble_outline, color: AppTheme.navyBlue), tooltip: 'Messages', onPressed: () { Navigator.of(context, rootNavigator: true).push( MaterialPageRoute( builder: (_) => const SecureChatFullScreen(), fullscreenDialog: true, ), ); }, ), IconButton( icon: Icon(Icons.notifications_none, color: AppTheme.navyBlue), tooltip: 'Notifications', onPressed: () { showGeneralDialog( context: context, barrierDismissible: true, barrierLabel: 'Notifications', barrierColor: Colors.black54, transitionDuration: const Duration(milliseconds: 250), pageBuilder: (context, _, __) { final height = MediaQuery.of(context).size.height; return Align( alignment: Alignment.topCenter, child: SafeArea( bottom: false, child: ClipRRect( borderRadius: const BorderRadius.vertical( bottom: Radius.circular(20), ), child: SizedBox( height: height * 0.9, child: const NotificationsScreen(), ), ), ), ); }, transitionBuilder: (context, animation, _, child) { final curve = CurvedAnimation( parent: animation, curve: Curves.easeOutCubic, ); return SlideTransition( position: Tween( begin: const Offset(0, -1), end: Offset.zero, ).animate(curve), child: child, ); }, ); }, ), const SizedBox(width: 4), ], ); } Widget _buildNavBarItem({ required IconData icon, required IconData activeIcon, required int index, required String label, }) { final isActive = widget.navigationShell.currentIndex == index; return Expanded( child: InkWell( onTap: () => widget.navigationShell.goBranch( index, initialLocation: index == widget.navigationShell.currentIndex, ), child: Container( height: double.infinity, alignment: Alignment.center, child: Icon( isActive ? activeIcon : icon, color: isActive ? AppTheme.navyBlue : Colors.grey, size: 26, ), ), ), ); } } /// Provides the current navigation shell index to descendants that need to /// react (e.g. pausing quip playback when the tab is not active). class NavigationShellScope extends InheritedWidget { final int currentIndex; const NavigationShellScope({ super.key, required this.currentIndex, required super.child, }); static NavigationShellScope? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); } @override bool updateShouldNotify(covariant NavigationShellScope oldWidget) { return currentIndex != oldWidget.currentIndex; } }