dzocesrce

Untitled

Apr 8th, 2026
67
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.75 KB | None | 0 0
  1. import 'package:flutter/material.dart';
  2.  
  3. import '../core/services/map_firestore_service.dart';
  4. import '../core/utils/app_error_messages.dart';
  5. import '../models/bus_line_model.dart';
  6. import '../models/line_route_model.dart';
  7. import '../models/location_model.dart';
  8.  
  9. class MapProvider extends ChangeNotifier {
  10. final MapFirestoreService _mapFirestoreService;
  11.  
  12. MapProvider({MapFirestoreService? mapFirestoreService})
  13. : _mapFirestoreService = mapFirestoreService ?? MapFirestoreService();
  14.  
  15. List<BusLineModel> _busLines = [];
  16. List<LineRouteModel> _lineRoutes = [];
  17. List<LocationModel> _locations = [];
  18.  
  19. final Set<String> _selectedLineIds = {};
  20. String _selectedDirection = 'west_to_east';
  21.  
  22. bool _isLoading = false;
  23. String? _errorMessage;
  24. AppErrorType _errorType = AppErrorType.server;
  25.  
  26. List<BusLineModel> get busLines => _busLines;
  27. List<LineRouteModel> get lineRoutes => _lineRoutes;
  28. List<LocationModel> get locations => _locations;
  29. Set<String> get selectedLineIds => _selectedLineIds;
  30. String get selectedDirection => _selectedDirection;
  31. bool get isLoading => _isLoading;
  32. String? get errorMessage => _errorMessage;
  33. AppErrorType get errorType => _errorType;
  34.  
  35. bool get hasData =>
  36. _busLines.isNotEmpty && _lineRoutes.isNotEmpty && _locations.isNotEmpty;
  37.  
  38. List<LineRouteModel> get selectedRoutes {
  39. return _lineRoutes.where((route) {
  40. return _selectedLineIds.contains(route.lineId) &&
  41. route.direction == _selectedDirection &&
  42. route.isActive;
  43. }).toList();
  44. }
  45.  
  46. List<LocationModel> get selectedRouteStops {
  47. final allStopIds = selectedRoutes.expand((route) => route.stopIdsOrdered).toSet();
  48.  
  49. return _locations.where((location) {
  50. return allStopIds.contains(location.id);
  51. }).toList();
  52. }
  53.  
  54. Future<void> loadMapData() async {
  55. _isLoading = true;
  56. _errorMessage = null;
  57. _errorType = AppErrorType.server;
  58. notifyListeners();
  59.  
  60. try {
  61. final busLines = await _mapFirestoreService.getBusLines();
  62. final locations = await _mapFirestoreService.getLocations();
  63. final lineRoutes = await _mapFirestoreService.getLineRoutes();
  64.  
  65. _busLines = busLines;
  66. _locations = locations;
  67. _lineRoutes = lineRoutes;
  68.  
  69. _selectedLineIds
  70. ..clear()
  71. ..addAll(_busLines.map((line) => line.id));
  72.  
  73. if (!hasData) {
  74. _errorMessage = "I couldn't load the app data right now. Please try again in a moment.";
  75. }
  76. } catch (e) {
  77. _busLines = [];
  78. _locations = [];
  79. _lineRoutes = [];
  80. _selectedLineIds.clear();
  81.  
  82. final errorInfo = AppErrorMessages.fromError(
  83. e,
  84. context: 'transport map',
  85. );
  86. _errorMessage = errorInfo.message;
  87. _errorType = errorInfo.type;
  88. } finally {
  89. _isLoading = false;
  90. notifyListeners();
  91. }
  92. }
  93.  
  94. List<int> getLineNumbersForStop(String stopId) {
  95. final lineIds = _lineRoutes
  96. .where(
  97. (route) => route.stopIdsOrdered.contains(stopId) && route.isActive,
  98. )
  99. .map((route) => route.lineId)
  100. .toSet();
  101.  
  102. final numbers = _busLines
  103. .where((line) => lineIds.contains(line.id))
  104. .map((line) => line.number)
  105. .toList();
  106.  
  107. numbers.sort();
  108. return numbers;
  109. }
  110.  
  111. String getShortDescription(String? description, {int maxLength = 30}) {
  112. if (description == null || description.trim().isEmpty) {
  113. return '';
  114. }
  115.  
  116. final trimmed = description.trim();
  117. if (trimmed.length <= maxLength) {
  118. return trimmed;
  119. }
  120.  
  121. return '${trimmed.substring(0, maxLength)}...';
  122. }
  123.  
  124. void toggleLineSelection(String lineId) {
  125. if (_selectedLineIds.contains(lineId)) {
  126. if (_selectedLineIds.length > 1) {
  127. _selectedLineIds.remove(lineId);
  128. }
  129. } else {
  130. _selectedLineIds.add(lineId);
  131. }
  132. notifyListeners();
  133. }
  134.  
  135. void selectDirection(String direction) {
  136. _selectedDirection = direction;
  137. notifyListeners();
  138. }
  139. }
  140.  
  141.  
  142.  
  143. import 'package:flutter/material.dart';
  144. import 'package:shared_preferences/shared_preferences.dart';
  145.  
  146. import '../core/services/notifications_firestore_service.dart';
  147. import '../core/utils/app_error_messages.dart';
  148. import '../models/app_notification_model.dart';
  149.  
  150. class NotificationsProvider extends ChangeNotifier {
  151. static const String _readIdsKey = 'read_notification_ids';
  152.  
  153. final NotificationsFirestoreService _service;
  154.  
  155. NotificationsProvider({NotificationsFirestoreService? service})
  156. : _service = service ?? NotificationsFirestoreService();
  157.  
  158. List<AppNotificationModel> _notifications = [];
  159. Set<String> _readIds = {};
  160. bool _isLoading = false;
  161. String? _errorMessage;
  162. AppErrorType _errorType = AppErrorType.server;
  163.  
  164. List<AppNotificationModel> get notifications => _notifications;
  165. bool get isLoading => _isLoading;
  166. String? get errorMessage => _errorMessage;
  167. AppErrorType get errorType => _errorType;
  168.  
  169. bool isRead(String id) => _readIds.contains(id);
  170.  
  171. bool get hasUnread {
  172. return _notifications.any((notification) => !_readIds.contains(notification.id));
  173. }
  174.  
  175. Future<void> initialize() async {
  176. await _loadReadIds();
  177. await loadNotifications();
  178. }
  179.  
  180. Future<void> _loadReadIds() async {
  181. final prefs = await SharedPreferences.getInstance();
  182. final stored = prefs.getStringList(_readIdsKey) ?? [];
  183. _readIds = stored.toSet();
  184. notifyListeners();
  185. }
  186.  
  187. Future<void> _saveReadIds() async {
  188. final prefs = await SharedPreferences.getInstance();
  189. await prefs.setStringList(_readIdsKey, _readIds.toList());
  190. }
  191.  
  192. Future<void> loadNotifications() async {
  193. _isLoading = true;
  194. _errorMessage = null;
  195. _errorType = AppErrorType.server;
  196. notifyListeners();
  197.  
  198. try {
  199. final result = await _service.getNotifications(limit: 20);
  200. _notifications = result;
  201. } catch (e) {
  202. final errorInfo = AppErrorMessages.fromError(
  203. e,
  204. context: 'notifications',
  205. );
  206. _errorMessage = errorInfo.message;
  207. _errorType = errorInfo.type;
  208. _notifications = [];
  209. } finally {
  210. _isLoading = false;
  211. notifyListeners();
  212. }
  213. }
  214.  
  215. Future<void> markAsRead(String id) async {
  216. if (_readIds.contains(id)) return;
  217.  
  218. _readIds.add(id);
  219. await _saveReadIds();
  220. notifyListeners();
  221. }
  222.  
  223. String getPreviewText(String text, {int maxLength = 70}) {
  224. final trimmed = text.trim();
  225. if (trimmed.length <= maxLength) return trimmed;
  226. return '${trimmed.substring(0, maxLength)}...';
  227. }
  228.  
  229. String formatNotificationDate(DateTime? date) {
  230. if (date == null) return '';
  231.  
  232. final now = DateTime.now();
  233. final today = DateTime(now.year, now.month, now.day);
  234. final target = DateTime(date.year, date.month, date.day);
  235. final difference = today.difference(target).inDays;
  236.  
  237. if (difference == 0) return 'Today';
  238. if (difference == 1) return 'Yesterday';
  239.  
  240. final day = date.day.toString().padLeft(2, '0');
  241. final month = date.month.toString().padLeft(2, '0');
  242. final year = date.year.toString();
  243.  
  244. return '$day.$month.$year';
  245. }
  246.  
  247. String formatNotificationTime(DateTime? date) {
  248. if (date == null) return '';
  249.  
  250. final hour = date.hour.toString().padLeft(2, '0');
  251. final minute = date.minute.toString().padLeft(2, '0');
  252.  
  253. return '$hour:$minute';
  254. }
  255. }
  256.  
  257.  
  258.  
  259. import 'package:flutter/material.dart';
  260.  
  261. import '../core/services/info_firestore_service.dart';
  262. import '../core/utils/app_error_messages.dart';
  263. import '../models/info_slide_model.dart';
  264.  
  265. class InfoProvider extends ChangeNotifier {
  266. final InfoFirestoreService _service;
  267.  
  268. InfoProvider({InfoFirestoreService? service})
  269. : _service = service ?? InfoFirestoreService();
  270.  
  271. List<InfoSlideModel> _slides = [];
  272. bool _isLoading = false;
  273. String? _errorMessage;
  274. AppErrorType _errorType = AppErrorType.server;
  275.  
  276. List<InfoSlideModel> get slides => _slides;
  277. bool get isLoading => _isLoading;
  278. String? get errorMessage => _errorMessage;
  279. AppErrorType get errorType => _errorType;
  280.  
  281. Future<void> loadSlides() async {
  282. _isLoading = true;
  283. _errorMessage = null;
  284. _errorType = AppErrorType.server;
  285. notifyListeners();
  286.  
  287. try {
  288. final fetchedSlides = await _service.getInfoSlides();
  289. _slides = fetchedSlides;
  290. } catch (e) {
  291. final errorInfo = AppErrorMessages.fromError(
  292. e,
  293. context: 'app info',
  294. );
  295. _errorMessage = errorInfo.message;
  296. _errorType = errorInfo.type;
  297. _slides = [];
  298. } finally {
  299. _isLoading = false;
  300. notifyListeners();
  301. }
  302. }
  303. }
  304.  
  305.  
  306.  
  307. import 'dart:async';
  308.  
  309. import 'package:flutter/material.dart';
  310.  
  311. import '../core/services/arrivals_firestore_service.dart';
  312. import '../core/utils/app_error_messages.dart';
  313. import '../models/upcoming_arrival_model.dart';
  314.  
  315. class ArrivalsProvider extends ChangeNotifier {
  316. final ArrivalsFirestoreService _service;
  317.  
  318. ArrivalsProvider({ArrivalsFirestoreService? service})
  319. : _service = service ?? ArrivalsFirestoreService();
  320.  
  321. List<UpcomingArrivalModel> _upcomingArrivals = [];
  322. bool _isLoading = false;
  323. String? _errorMessage;
  324. AppErrorType _errorType = AppErrorType.server;
  325.  
  326. String? _currentStopId;
  327. String? _currentDirection;
  328.  
  329. Timer? _alignmentTimer;
  330. Timer? _refreshTimer;
  331.  
  332. List<UpcomingArrivalModel> get upcomingArrivals => _upcomingArrivals;
  333. bool get isLoading => _isLoading;
  334. String? get errorMessage => _errorMessage;
  335. AppErrorType get errorType => _errorType;
  336.  
  337. Future<void> loadArrivals({
  338. required String stopId,
  339. required String direction,
  340. }) async {
  341. _currentStopId = stopId;
  342. _currentDirection = direction;
  343.  
  344. _isLoading = true;
  345. _errorMessage = null;
  346. _errorType = AppErrorType.server;
  347. notifyListeners();
  348.  
  349. try {
  350. final now = DateTime.now();
  351.  
  352. final relevantTimetables = await _service.getArrivalsForStop(
  353. stopId: stopId,
  354. direction: direction,
  355. );
  356.  
  357. final List<UpcomingArrivalModel> arrivals = [];
  358.  
  359. for (final timetable in relevantTimetables) {
  360. for (final departureTime in timetable.departureTimes) {
  361. final parts = departureTime.split(':');
  362. if (parts.length != 2) continue;
  363.  
  364. final hour = int.tryParse(parts[0]);
  365. final minute = int.tryParse(parts[1]);
  366.  
  367. if (hour == null || minute == null) continue;
  368.  
  369. final todayDeparture = DateTime(
  370. now.year,
  371. now.month,
  372. now.day,
  373. hour,
  374. minute,
  375. );
  376.  
  377. final minutesUntil = todayDeparture.difference(now).inMinutes;
  378.  
  379. if (minutesUntil >= 0) {
  380. arrivals.add(
  381. UpcomingArrivalModel(
  382. routeId: timetable.routeId,
  383. stopId: timetable.stopId,
  384. lineId: timetable.lineId,
  385. minutesUntilArrival: minutesUntil,
  386. ),
  387. );
  388. }
  389. }
  390. }
  391.  
  392. arrivals.sort(
  393. (a, b) => a.minutesUntilArrival.compareTo(b.minutesUntilArrival),
  394. );
  395.  
  396. _upcomingArrivals = arrivals.take(5).toList();
  397. } catch (e) {
  398. final errorInfo = AppErrorMessages.fromError(
  399. e,
  400. context: 'arrival times',
  401. );
  402. _errorMessage = errorInfo.message;
  403. _errorType = errorInfo.type;
  404. _upcomingArrivals = [];
  405. } finally {
  406. _isLoading = false;
  407. notifyListeners();
  408. }
  409. }
  410.  
  411. void startAutoRefresh() {
  412. stopAutoRefresh();
  413.  
  414. final now = DateTime.now();
  415. final nextMinute = DateTime(
  416. now.year,
  417. now.month,
  418. now.day,
  419. now.hour,
  420. now.minute + 1,
  421. );
  422. final delay = nextMinute.difference(now);
  423.  
  424. _alignmentTimer = Timer(delay, () async {
  425. if (_currentStopId != null && _currentDirection != null) {
  426. await loadArrivals(
  427. stopId: _currentStopId!,
  428. direction: _currentDirection!,
  429. );
  430. }
  431.  
  432. _refreshTimer = Timer.periodic(const Duration(minutes: 1), (_) {
  433. if (_currentStopId != null && _currentDirection != null) {
  434. loadArrivals(
  435. stopId: _currentStopId!,
  436. direction: _currentDirection!,
  437. );
  438. }
  439. });
  440. });
  441. }
  442.  
  443. void stopAutoRefresh() {
  444. _alignmentTimer?.cancel();
  445. _alignmentTimer = null;
  446.  
  447. _refreshTimer?.cancel();
  448. _refreshTimer = null;
  449. }
  450.  
  451. String formatArrivalText(int minutes) {
  452. if (minutes <= 1) return 'Now';
  453.  
  454. if (minutes >= 180) {
  455. final hours = minutes ~/ 60;
  456. return '${hours}h. +';
  457. }
  458.  
  459. return '$minutes min.';
  460. }
  461.  
  462. bool isArrivingSoon(int minutes) {
  463. return minutes <= 1 || minutes < 10;
  464. }
  465.  
  466. @override
  467. void dispose() {
  468. stopAutoRefresh();
  469. super.dispose();
  470. }
  471. }
Advertisement
Add Comment
Please, Sign In to add comment