import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:url_launcher/url_launcher.dart'; import 'models/responsive_policy.dart'; class ScaffoldWithNavBar extends StatefulWidget { const ScaffoldWithNavBar({super.key, required this.child}); final Widget child; @override State createState() => _ScaffoldWithNavBarState(); } class _ScaffoldWithNavBarState extends State { bool _isLoggedIn = false; late String initials; @override void initState() { super.initState(); init(); } Future init() async { var prefs = await SharedPreferences.getInstance(); if (prefs.getString("token") != null) { setState(() { _isLoggedIn = true; initials = prefs.getString("initials")!; }); } } @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth > ResponsivePolicy.md) { return Scaffold( body: SafeArea( child: Row( children: [ SizedBox( width: 256, child: Column( children: [ Expanded( child: NavigationRail( extended: true, leading: Padding( padding: const EdgeInsets.symmetric( vertical: 12.0, ), child: Image( image: AssetImage('assets/velocity.png'), width: 150, ), ), labelType: NavigationRailLabelType.none, indicatorColor: Theme.of( context, ).colorScheme.primary.withAlpha(30), onDestinationSelected: (index) { _handleTap(index, context); }, selectedIndex: _calculateSelectedIndex(context), destinations: _buildNavigationRailDestinations(), ), ), Divider( height: 1, thickness: 1, color: Theme.of( context, ).colorScheme.primary.withAlpha(30), ), _buildFooterLinks(context), ], ), ), VerticalDivider( thickness: 1, width: 1, color: Theme.of(context).colorScheme.primary.withAlpha(30), ), Expanded(child: widget.child), ], ), ), ); } return Scaffold( body: widget.child, bottomNavigationBar: NavigationBar( indicatorColor: Theme.of(context).colorScheme.primary.withAlpha(30), onDestinationSelected: (index) { _handleTap(index, context); }, selectedIndex: _calculateSelectedIndex(context), destinations: _buildNavigationDestinations(), ), ); }, ); } List _buildNavigationRailDestinations() { List destinations = []; for (var destination in _buildDestinationData()) { destinations.add( NavigationRailDestination( icon: destination["icon"] as Widget, selectedIcon: destination["selectedIcon"] as Widget, label: Text(destination["label"], style: TextStyle(fontSize: 16)), ), ); } return destinations; } List _buildNavigationDestinations() { List destinations = []; for (var destination in _buildDestinationData()) { destinations.add( NavigationDestination( icon: destination["icon"] as Widget, selectedIcon: destination["selectedIcon"] as Widget, label: destination["label"] as String, ), ); } return destinations; } List> _buildDestinationData() { return [ { "icon": Icon(Icons.home_outlined), "selectedIcon": Icon(Icons.home), "label": 'Home', }, { "icon": Icon(Icons.group_outlined), "selectedIcon": Icon(Icons.group), "label": 'Groups', }, { "icon": Icon(Icons.history_outlined), "selectedIcon": Icon(Icons.history), "label": 'History', }, ]; } void _handleTap(int index, BuildContext context) { switch (index) { case 0: context.go('/home'); break; case 1: context.go('/groups'); break; case 2: context.go('/history'); break; } } int _calculateSelectedIndex(BuildContext context) { final String location = GoRouterState.of(context).uri.path; if (location.startsWith('/groups')) return 1; if (location.startsWith('/history')) return 2; return 0; } /// Footer link entries shown at the bottom of the web navigation rail. /// Each entry pairs the visible label with the external URL it should /// open when tapped. static const List> _footerLinkEntries = [ {'label': 'About', 'url': 'https://velocityafrica.net'}, {'label': 'FAQs', 'url': 'https://velocityafrica.net'}, {'label': 'Privacy', 'url': 'https://qantra.co.zw/privacy'}, {'label': 'Terms', 'url': 'https://qantra.co.zw/terms'}, ]; /// Opens the supplied [url] in an external browser using /// `url_launcher`. Errors are swallowed because the navbar footer /// is non-critical UI. Future _openExternalLink(String url) async { final uri = Uri.tryParse(url); if (uri == null) return; try { final launched = await launchUrl( uri, mode: LaunchMode.externalApplication, ); if (!launched && mounted) { await launchUrl(uri, mode: LaunchMode.platformDefault); } } catch (_) { // Intentionally silent — footer links are best-effort. } } /// Builds the footer section at the bottom of the web navigation rail. /// Renders the About / FAQs / Privacy / Terms links (each opening /// the configured external URL) followed by a copyright line. Widget _buildFooterLinks(BuildContext context) { final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; final mutedTextColor = isDark ? Colors.white60 : Colors.black54; final linkTextColor = isDark ? Colors.white : Colors.black87; return Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Wrap( spacing: 6, runSpacing: 4, crossAxisAlignment: WrapCrossAlignment.center, children: [ for (var i = 0; i < _footerLinkEntries.length; i++) ...[ InkWell( onTap: () => _openExternalLink(_footerLinkEntries[i]['url']!), borderRadius: BorderRadius.circular(4), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 4, vertical: 2, ), child: Text( _footerLinkEntries[i]['label']!, style: TextStyle( fontSize: 12, color: linkTextColor, fontWeight: FontWeight.w500, ), ), ), ), if (i != _footerLinkEntries.length - 1) Text( '·', style: TextStyle( fontSize: 12, color: mutedTextColor, fontWeight: FontWeight.bold, ), ), ], ], ), const SizedBox(height: 8), Text( '© 2026 Velocity Africa. All rights reserved.', style: TextStyle(fontSize: 10, color: mutedTextColor), ), ], ), ); } }