207 lines
5.8 KiB
Dart
207 lines
5.8 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:fluttertoast/fluttertoast.dart';
|
|
|
|
import '../models/route_model.dart';
|
|
import './transportation_service.dart';
|
|
|
|
/// Global app state service to manage selected route across the entire app
|
|
class AppStateService extends ChangeNotifier {
|
|
static final AppStateService _instance = AppStateService._internal();
|
|
factory AppStateService() => _instance;
|
|
AppStateService._internal();
|
|
|
|
final TransportationService _transportationService = TransportationService();
|
|
|
|
// Global state
|
|
String? _selectedRouteId;
|
|
String? _selectedRouteName;
|
|
List<RouteModel> _allRoutes = [];
|
|
bool _isLoadingRoutes = false;
|
|
String? _error;
|
|
|
|
// Getters
|
|
String? get selectedRouteId => _selectedRouteId;
|
|
String? get selectedRouteName => _selectedRouteName;
|
|
List<RouteModel> get allRoutes => List.unmodifiable(_allRoutes);
|
|
bool get isLoadingRoutes => _isLoadingRoutes;
|
|
String? get error => _error;
|
|
bool get hasSelectedRoute =>
|
|
_selectedRouteId != null && _selectedRouteName != null;
|
|
|
|
/// Initialize app state - call this on app start
|
|
Future<void> initialize() async {
|
|
await loadRoutes();
|
|
}
|
|
|
|
/// Load all routes from Supabase
|
|
Future<void> loadRoutes() async {
|
|
if (_isLoadingRoutes) return;
|
|
|
|
_isLoadingRoutes = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
final result = await _transportationService.getRoutesWithCount();
|
|
final routes = result['data'] as List<RouteModel>;
|
|
final count = result['count'] as int;
|
|
final error = result['error'] as String?;
|
|
|
|
if (error != null) {
|
|
print('Error loading routes: $error');
|
|
// Handle error but continue with empty list
|
|
_allRoutes = [];
|
|
} else {
|
|
_allRoutes = routes;
|
|
|
|
// Auto-select first route if none selected and routes available
|
|
if (routes.isNotEmpty && _selectedRouteId == null) {
|
|
await selectRoute(routes.first.id);
|
|
}
|
|
}
|
|
|
|
_isLoadingRoutes = false;
|
|
notifyListeners();
|
|
|
|
// Show toast if no routes found as specified in requirements
|
|
if (_allRoutes.isEmpty) {
|
|
_showNoRoutesToast();
|
|
}
|
|
} catch (e) {
|
|
print('Exception loading routes: $e');
|
|
_allRoutes = [];
|
|
_isLoadingRoutes = false;
|
|
notifyListeners();
|
|
|
|
// Show error toast for exceptions
|
|
Fluttertoast.showToast(
|
|
msg: "Error loading routes: ${e.toString()}",
|
|
toastLength: Toast.LENGTH_LONG,
|
|
gravity: ToastGravity.CENTER,
|
|
backgroundColor: Colors.red,
|
|
textColor: Colors.white,
|
|
fontSize: 16.0,
|
|
);
|
|
}
|
|
}
|
|
|
|
void _showNoRoutesToast() {
|
|
Fluttertoast.showToast(
|
|
msg: "No routes found",
|
|
toastLength: Toast.LENGTH_LONG,
|
|
gravity: ToastGravity.CENTER,
|
|
backgroundColor: Colors.orange,
|
|
textColor: Colors.white,
|
|
fontSize: 16.0,
|
|
);
|
|
}
|
|
|
|
/// Select a route and update global state
|
|
Future<void> selectRoute(String routeId) async {
|
|
if (routeId == _selectedRouteId) return;
|
|
|
|
try {
|
|
// Find the route in our cached routes
|
|
final route = _allRoutes.firstWhere(
|
|
(r) => r.id == routeId,
|
|
orElse: () => throw Exception('Route not found: $routeId'),
|
|
);
|
|
|
|
_selectedRouteId = routeId;
|
|
_selectedRouteName = route.displayName;
|
|
_error = null;
|
|
|
|
debugPrint('✅ Route selected: $routeId - $_selectedRouteName');
|
|
notifyListeners();
|
|
} catch (e) {
|
|
_error = 'Error selecting route: $e';
|
|
debugPrint('❌ Error selecting route: $e');
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
/// Clear selected route
|
|
void clearSelectedRoute() {
|
|
_selectedRouteId = null;
|
|
_selectedRouteName = null;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Get selected route model
|
|
RouteModel? getSelectedRoute() {
|
|
if (_selectedRouteId == null) return null;
|
|
try {
|
|
return _allRoutes.firstWhere((r) => r.id == _selectedRouteId!);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Private method to select first available route with stops
|
|
Future<void> _selectFirstAvailableRoute() async {
|
|
for (final route in _allRoutes) {
|
|
try {
|
|
final stops =
|
|
await _transportationService.getRouteStopsOrderedBySeq(route.id);
|
|
if (stops.isNotEmpty) {
|
|
_selectedRouteId = route.id;
|
|
_selectedRouteName = route.displayName;
|
|
debugPrint(
|
|
'✅ Auto-selected route: ${route.id} - ${route.displayName}');
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
debugPrint('⚠️ Error checking stops for route ${route.id}: $e');
|
|
}
|
|
}
|
|
|
|
// Fallback: select first route even if no stops
|
|
if (_allRoutes.isNotEmpty) {
|
|
final firstRoute = _allRoutes.first;
|
|
_selectedRouteId = firstRoute.id;
|
|
_selectedRouteName = firstRoute.displayName;
|
|
debugPrint(
|
|
'✅ Fallback: selected first route: ${firstRoute.id} - ${firstRoute.displayName}');
|
|
}
|
|
}
|
|
|
|
/// Refresh routes and maintain selection if possible
|
|
Future<void> refreshRoutes() async {
|
|
final previousSelectedId = _selectedRouteId;
|
|
await loadRoutes();
|
|
|
|
// Try to restore previous selection
|
|
if (previousSelectedId != null &&
|
|
_allRoutes.any((r) => r.id == previousSelectedId)) {
|
|
await selectRoute(previousSelectedId);
|
|
}
|
|
}
|
|
|
|
/// Get route by ID
|
|
RouteModel? getRouteById(String routeId) {
|
|
try {
|
|
return _allRoutes.firstWhere((r) => r.id == routeId);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Check if route exists
|
|
bool hasRoute(String routeId) {
|
|
return _allRoutes.any((r) => r.id == routeId);
|
|
}
|
|
|
|
/// Show toast message for no routes found
|
|
void showNoRoutesToast(BuildContext context) {
|
|
if (_allRoutes.isEmpty) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('No routes found'),
|
|
backgroundColor: Colors.orange,
|
|
duration: Duration(seconds: 3),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
} |