Initial commit: SIBU 2.0 MISSION

This commit is contained in:
2026-02-21 09:53:31 -05:00
commit 0c7aa53c8b
400 changed files with 67708 additions and 0 deletions

View File

@ -0,0 +1,202 @@
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),
),
),
),
),
);
}
}