203 lines
5.9 KiB
Dart
203 lines
5.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
/// Custom bottom navigation bar implementing adaptive tab persistence
|
|
/// with contextual badge indicators for the transit app
|
|
class CustomBottomBar extends StatefulWidget {
|
|
final int currentIndex;
|
|
final Function(int) onTap;
|
|
|
|
const CustomBottomBar({
|
|
super.key,
|
|
required this.currentIndex,
|
|
required this.onTap,
|
|
});
|
|
|
|
@override
|
|
State<CustomBottomBar> createState() => _CustomBottomBarState();
|
|
}
|
|
|
|
class _CustomBottomBarState extends State<CustomBottomBar> {
|
|
// Navigation items with routes and icons
|
|
final List<BottomNavigationBarItem> _navigationItems = [
|
|
const BottomNavigationBarItem(
|
|
icon: Icon(Icons.map_outlined),
|
|
activeIcon: Icon(Icons.map),
|
|
label: 'Mapa',
|
|
),
|
|
const BottomNavigationBarItem(
|
|
icon: Icon(Icons.schedule_outlined),
|
|
activeIcon: Icon(Icons.schedule),
|
|
label: 'Horarios',
|
|
),
|
|
const BottomNavigationBarItem(
|
|
icon: Icon(Icons.local_offer_outlined),
|
|
activeIcon: Icon(Icons.local_offer),
|
|
label: 'Cupones',
|
|
),
|
|
const BottomNavigationBarItem(
|
|
icon: Icon(Icons.local_taxi_outlined),
|
|
activeIcon: Icon(Icons.local_taxi),
|
|
label: 'Taxi',
|
|
),
|
|
];
|
|
|
|
final List<String> _routes = [
|
|
'/map-screen',
|
|
'/schedules-screen',
|
|
'/coupons-screen',
|
|
'/taxi-screen',
|
|
];
|
|
|
|
void _handleTap(int index) {
|
|
if (index != widget.currentIndex) {
|
|
// Haptic feedback for tab selection
|
|
HapticFeedback.lightImpact();
|
|
|
|
// Navigate to selected route
|
|
Navigator.pushNamed(context, _routes[index]);
|
|
|
|
// Call the onTap callback
|
|
widget.onTap(index);
|
|
}
|
|
}
|
|
|
|
Widget _buildNavigationItem(BottomNavigationBarItem item, int index) {
|
|
final bool isSelected = index == widget.currentIndex;
|
|
final theme = Theme.of(context);
|
|
|
|
return Expanded(
|
|
child: InkWell(
|
|
onTap: () => _handleTap(index),
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Icon with micro-interaction confirmation
|
|
AnimatedScale(
|
|
scale: isSelected ? 1.1 : 1.0,
|
|
duration: const Duration(milliseconds: 200),
|
|
curve: Curves.easeInOut,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? theme.colorScheme.secondary.withValues(alpha: 0.1)
|
|
: Colors.transparent,
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: _buildIconWithBadge(
|
|
isSelected ? item.activeIcon : item.icon,
|
|
index,
|
|
isSelected,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
// Label with smooth transition
|
|
AnimatedDefaultTextStyle(
|
|
duration: const Duration(milliseconds: 200),
|
|
style: theme.textTheme.labelSmall!.copyWith(
|
|
color: isSelected
|
|
? theme.colorScheme.primary
|
|
: theme.colorScheme.onSurface.withValues(alpha: 0.6),
|
|
fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
|
|
),
|
|
child: Text(item.label!),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildIconWithBadge(Widget icon, int index, bool isSelected) {
|
|
final theme = Theme.of(context);
|
|
|
|
// Smart notification badges - contextual indicators
|
|
bool showBadge = false;
|
|
String? badgeText;
|
|
|
|
switch (index) {
|
|
case 0: // Map
|
|
// Show badge when there are nearby bus updates
|
|
showBadge = false; // Would be dynamic based on app state
|
|
break;
|
|
case 1: // Schedules
|
|
// Show badge when there are schedule changes
|
|
showBadge = false; // Would be dynamic based on app state
|
|
break;
|
|
case 2: // Coupons
|
|
// Show badge when there are new coupons available
|
|
showBadge = true; // Example: new coupons available
|
|
badgeText = '3';
|
|
break;
|
|
case 3: // Taxi
|
|
// Show badge for new taxi services or favorites
|
|
showBadge = false; // Would be dynamic based on app state
|
|
break;
|
|
}
|
|
|
|
if (!showBadge) {
|
|
return IconTheme(
|
|
data: IconThemeData(
|
|
color: isSelected
|
|
? theme.colorScheme.primary
|
|
: theme.colorScheme.onSurface.withValues(alpha: 0.6),
|
|
size: 24,
|
|
),
|
|
child: icon,
|
|
);
|
|
}
|
|
|
|
return Badge(
|
|
label: badgeText != null ? Text(badgeText) : null,
|
|
backgroundColor: theme.colorScheme.error,
|
|
textColor: theme.colorScheme.onError,
|
|
smallSize: badgeText == null ? 8 : null,
|
|
child: IconTheme(
|
|
data: IconThemeData(
|
|
color: isSelected
|
|
? theme.colorScheme.primary
|
|
: theme.colorScheme.onSurface.withValues(alpha: 0.6),
|
|
size: 24,
|
|
),
|
|
child: icon,
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: theme.colorScheme.surface,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: theme.shadowColor.withValues(alpha: 0.1),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, -2),
|
|
),
|
|
],
|
|
),
|
|
child: SafeArea(
|
|
child: Container(
|
|
height: 72,
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
child: Row(
|
|
children: List.generate(
|
|
_navigationItems.length,
|
|
(index) => _buildNavigationItem(_navigationItems[index], index),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|