From 3a6da16d46b35008f4c0a93448aa10e11cc08a35 Mon Sep 17 00:00:00 2001 From: Static Date: Wed, 25 Mar 2026 16:03:04 -0400 Subject: [PATCH 1/3] persistent favorited stops --- lib/screens/map_screen.dart | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index f3ea6d1..b48cd28 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -104,6 +104,7 @@ class _MaizeBusCoreState extends State { Set _displayedPolylines = {}; Set _displayedStopMarkers = {}; + Set _displayedFavoriteStopMarkers = {}; Set _displayedBusMarkers = {}; // Journey overlays for search results Set _displayedJourneyPolylines = {}; @@ -771,14 +772,16 @@ class _MaizeBusCoreState extends State { } if (!_routeStopMarkers.containsKey(routeKey)) { _routeStopMarkers[routeKey] = r.stops - .map( - (stop) => Marker( + .map((stop) { + final isFavorite = _favoriteStops.contains(stop.id); + + final marker = Marker( markerId: MarkerId( 'stop_${stop.id}_${Object.hashAll(r.points)}', ), position: stop.location, flat: true, - icon: _favoriteStops.contains(stop.id) + icon: isFavorite ? (stop.isRide ? _favRideStopIcon ?? BitmapDescriptor.defaultMarkerWithHue( @@ -812,9 +815,12 @@ class _MaizeBusCoreState extends State { }, rotation: stop.rotation, anchor: Offset(0.5, 0.5), - ), - ) - .toSet(); + ); + + // add created stop marker to the favorited markers if it is favorited + if (isFavorite) _displayedFavoriteStopMarkers.add(marker); + return marker; + }).toSet(); } } } @@ -853,7 +859,7 @@ class _MaizeBusCoreState extends State { _routeStopMarkers.forEach((routeKey, markers) { final updated = markers.map((m) { if (m.markerId.value.startsWith('stop_${stpid}_')) { - return Marker( + final marker = Marker( flat: true, markerId: m.markerId, position: m.position, @@ -872,6 +878,15 @@ class _MaizeBusCoreState extends State { rotation: m.rotation, anchor: m.anchor, ); + + // add or remove the marker from the displayed favorite stops + if (!favored) { + _displayedFavoriteStopMarkers.remove(m); + } else { + _displayedFavoriteStopMarkers.add(marker); + } + + return marker; } return m; }).toSet(); @@ -1010,6 +1025,7 @@ class _MaizeBusCoreState extends State { void _updateAllDisplayedMarkers() { _allDisplayedStopMarkers = _displayedStopMarkers + .union(_displayedFavoriteStopMarkers) .union(_displayedBusMarkers) .union(_displayedJourneyMarkers) .union(_searchLocationMarker != null ? {_searchLocationMarker!} : {}); @@ -1078,6 +1094,8 @@ class _MaizeBusCoreState extends State { void _refreshCachedStopMarkers() { // Clear cached stop markers so they'll be recreated with the new icons _routeStopMarkers.clear(); + // also clear persistent favorited stop markers to be refreshed in _cacheRouteOverlays(..) + _displayedFavoriteStopMarkers.clear(); // Re-cache all route overlays with the new icons _cacheRouteOverlays( Provider.of(context, listen: false).routes, @@ -2129,6 +2147,7 @@ class _MaizeBusCoreState extends State { : {}, ) : _displayedStopMarkers + .union(_displayedFavoriteStopMarkers) .union(_displayedJourneyMarkers) .union( _searchLocationMarker != null From a509eed75c60ef313b7750d12eb1bcd56ade3ce1 Mon Sep 17 00:00:00 2001 From: Static Date: Sat, 28 Mar 2026 15:39:37 -0400 Subject: [PATCH 2/3] fix(ui): unfavoriting a ride stop turns stop blue --- lib/screens/map_screen.dart | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index b48cd28..711118f 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -120,6 +120,7 @@ class _MaizeBusCoreState extends State { Marker? _searchLocationMarker; final Set _selectedRoutes = {}; List> _availableRoutes = []; + Map _stopIsRide = {}; // Custom marker icons BitmapDescriptor? _busIcon; @@ -819,6 +820,7 @@ class _MaizeBusCoreState extends State { // add created stop marker to the favorited markers if it is favorited if (isFavorite) _displayedFavoriteStopMarkers.add(marker); + _stopIsRide[stop.id] = stop.isRide; return marker; }).toSet(); } @@ -856,6 +858,7 @@ class _MaizeBusCoreState extends State { // Update cached markers for a specific stop id to reflect favorite/unfavorite void _setStopFavorited(String stpid, bool favored) { // Update all routeStopMarkers entries that match this stop id + final isRide = _stopIsRide[stpid] ?? false; _routeStopMarkers.forEach((routeKey, markers) { final updated = markers.map((m) { if (m.markerId.value.startsWith('stop_${stpid}_')) { @@ -864,15 +867,24 @@ class _MaizeBusCoreState extends State { markerId: m.markerId, position: m.position, icon: favored - ? (_favStopIcon ?? - _stopIcon ?? - BitmapDescriptor.defaultMarkerWithHue( - BitmapDescriptor.hueAzure, - )) - : (_stopIcon ?? - BitmapDescriptor.defaultMarkerWithHue( - BitmapDescriptor.hueAzure, - )), + ? (isRide + ? _favRideStopIcon ?? + BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueAzure, + ) + : _favStopIcon ?? + BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueAzure, + )) + : (isRide + ? _rideStopIcon ?? + BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueAzure, + ) + : _stopIcon ?? + BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueAzure, + )), consumeTapEvents: m.consumeTapEvents, onTap: m.onTap, rotation: m.rotation, From fa79a05a091009a564ba8994d2aec74df7338dcb Mon Sep 17 00:00:00 2001 From: Static Date: Sat, 28 Mar 2026 16:22:29 -0400 Subject: [PATCH 3/3] perf(ui): turned stop vars into maps from stopID to marker, removing duplicate stop markers for optimization --- lib/screens/map_screen.dart | 221 +++++++++++++++++++----------------- 1 file changed, 118 insertions(+), 103 deletions(-) diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 711118f..1dc8b96 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -103,8 +103,8 @@ class _MaizeBusCoreState extends State { static const LatLng _defaultCenter = LatLng(42.276463, -83.7374598); Set _displayedPolylines = {}; - Set _displayedStopMarkers = {}; - Set _displayedFavoriteStopMarkers = {}; + Map _displayedStopMarkers = {}; // maps from stopID to marker + Map _displayedFavoriteStopMarkers = {}; Set _displayedBusMarkers = {}; // Journey overlays for search results Set _displayedJourneyPolylines = {}; @@ -136,7 +136,7 @@ class _MaizeBusCoreState extends State { // Memoization caches final Map _routePolylines = {}; - final Map> _routeStopMarkers = {}; + final Map> _routeStopMarkers = {}; // maps from route to a map of stopID to marker // Whether a journey search overlay is currently active (shows only journey path) bool _journeyOverlayActive = false; // maximum allowed distance (meters) from a stop to a candidate polyline point @@ -772,57 +772,59 @@ class _MaizeBusCoreState extends State { ); } if (!_routeStopMarkers.containsKey(routeKey)) { - _routeStopMarkers[routeKey] = r.stops - .map((stop) { - final isFavorite = _favoriteStops.contains(stop.id); + _routeStopMarkers[routeKey] = {}; + for (final stop in r.stops) { // iterate through all stops in this route + final isFavorite = _favoriteStops.contains(stop.id); + + final marker = Marker( + markerId: MarkerId( + 'stop_${stop.id}_${Object.hashAll(r.points)}', + ), + position: stop.location, + flat: true, + icon: isFavorite + ? (stop.isRide + ? _favRideStopIcon ?? + BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueAzure, + ) + : _favStopIcon ?? + BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueAzure, + )) + : (stop.isRide + ? _rideStopIcon ?? + BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueAzure, + ) + : _stopIcon ?? + BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueAzure, + )), + consumeTapEvents: true, + onTap: () { + try { + Haptics.vibrate(HapticsType.light); + } catch (e) {} - final marker = Marker( - markerId: MarkerId( - 'stop_${stop.id}_${Object.hashAll(r.points)}', - ), - position: stop.location, - flat: true, - icon: isFavorite - ? (stop.isRide - ? _favRideStopIcon ?? - BitmapDescriptor.defaultMarkerWithHue( - BitmapDescriptor.hueAzure, - ) - : _favStopIcon ?? - BitmapDescriptor.defaultMarkerWithHue( - BitmapDescriptor.hueAzure, - )) - : (stop.isRide - ? _rideStopIcon ?? - BitmapDescriptor.defaultMarkerWithHue( - BitmapDescriptor.hueAzure, - ) - : _stopIcon ?? - BitmapDescriptor.defaultMarkerWithHue( - BitmapDescriptor.hueAzure, - )), - consumeTapEvents: true, - onTap: () { - try { - Haptics.vibrate(HapticsType.light); - } catch (e) {} - - _showStopSheet( - stop.id, - stop.name, - stop.location.latitude, - stop.location.longitude, - ); - }, - rotation: stop.rotation, - anchor: Offset(0.5, 0.5), + _showStopSheet( + stop.id, + stop.name, + stop.location.latitude, + stop.location.longitude, ); - - // add created stop marker to the favorited markers if it is favorited - if (isFavorite) _displayedFavoriteStopMarkers.add(marker); - _stopIsRide[stop.id] = stop.isRide; - return marker; - }).toSet(); + }, + rotation: stop.rotation, + anchor: Offset(0.5, 0.5), + ); + _routeStopMarkers[routeKey]?[stop.id] = marker; + + // gets first marker of this stop and adds it to the favorited stop markers + if (isFavorite && !_displayedFavoriteStopMarkers.containsKey(stop.id)) { + _displayedFavoriteStopMarkers[stop.id] = marker; + } + _stopIsRide[stop.id] = stop.isRide; + } } } } @@ -860,62 +862,71 @@ class _MaizeBusCoreState extends State { // Update all routeStopMarkers entries that match this stop id final isRide = _stopIsRide[stpid] ?? false; _routeStopMarkers.forEach((routeKey, markers) { - final updated = markers.map((m) { - if (m.markerId.value.startsWith('stop_${stpid}_')) { - final marker = Marker( - flat: true, - markerId: m.markerId, - position: m.position, - icon: favored - ? (isRide - ? _favRideStopIcon ?? - BitmapDescriptor.defaultMarkerWithHue( - BitmapDescriptor.hueAzure, - ) - : _favStopIcon ?? - BitmapDescriptor.defaultMarkerWithHue( - BitmapDescriptor.hueAzure, - )) - : (isRide - ? _rideStopIcon ?? - BitmapDescriptor.defaultMarkerWithHue( - BitmapDescriptor.hueAzure, - ) - : _stopIcon ?? - BitmapDescriptor.defaultMarkerWithHue( - BitmapDescriptor.hueAzure, - )), - consumeTapEvents: m.consumeTapEvents, - onTap: m.onTap, - rotation: m.rotation, - anchor: m.anchor, - ); + // if marker does not exist in this route, return + if (!markers.containsKey(stpid)) return; + + final m = markers[stpid]!; // get old marker + final newMarker = Marker( + flat: true, + markerId: m.markerId, + position: m.position, + icon: favored + ? (isRide + ? _favRideStopIcon ?? + BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueAzure, + ) + : _favStopIcon ?? + BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueAzure, + )) + : (isRide + ? _rideStopIcon ?? + BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueAzure, + ) + : _stopIcon ?? + BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueAzure, + )), + consumeTapEvents: m.consumeTapEvents, + onTap: m.onTap, + rotation: m.rotation, + anchor: m.anchor, + ); - // add or remove the marker from the displayed favorite stops - if (!favored) { - _displayedFavoriteStopMarkers.remove(m); - } else { - _displayedFavoriteStopMarkers.add(marker); - } + // gets first marker of this stop id and adds it to the favorited stop markers + if (favored && !_displayedFavoriteStopMarkers.containsKey(stpid)) { + _displayedFavoriteStopMarkers[stpid] = newMarker; + } - return marker; - } - return m; - }).toSet(); - _routeStopMarkers[routeKey] = updated; + markers[stpid] = newMarker; // set as new marker }); + // remove favorite stop marker if not favored + if (!favored) { + _displayedFavoriteStopMarkers.remove(stpid); + } + // If displayed, update displayed markers as well setState(() { // Rebuild displayed stop markers based on current selected routes - final selectedStopMarkers = {}; + final selectedStopMarkers = {}; for (final routeId in _selectedRoutes) { final routeVariants = _routePolylines.keys.where( (key) => key.startsWith('${routeId}_'), ); for (final routeKey in routeVariants) { final stops = _routeStopMarkers[routeKey]; - if (stops != null) selectedStopMarkers.addAll(stops); + if (stops == null) continue; + + // iterate through and add the stop markers + // if they are not already in the selected stop markesr + stops.forEach((key, value) { + if (!selectedStopMarkers.containsKey(key)) { + selectedStopMarkers[key] = value; + } + }); } } _displayedStopMarkers = selectedStopMarkers; @@ -925,7 +936,7 @@ class _MaizeBusCoreState extends State { void _updateDisplayedRoutes() { final selectedPolylines = {}; - final selectedStopMarkers = {}; + final selectedStopMarkers = {}; for (final routeId in _selectedRoutes) { // Find all variants of this route @@ -937,9 +948,13 @@ class _MaizeBusCoreState extends State { final polyline = _routePolylines[routeKey]; if (polyline != null) selectedPolylines.add(polyline); final stops = _routeStopMarkers[routeKey]; - if (stops != null) { - selectedStopMarkers.addAll(stops); - } + if (stops == null) continue; + + stops.forEach((key, value) { + if (!selectedStopMarkers.containsKey(key)) { + selectedStopMarkers[key] = value; + } + }); } } @@ -1036,8 +1051,8 @@ class _MaizeBusCoreState extends State { } void _updateAllDisplayedMarkers() { - _allDisplayedStopMarkers = _displayedStopMarkers - .union(_displayedFavoriteStopMarkers) + _allDisplayedStopMarkers = _displayedStopMarkers.values.toSet() + .union(_displayedFavoriteStopMarkers.values.toSet()) .union(_displayedBusMarkers) .union(_displayedJourneyMarkers) .union(_searchLocationMarker != null ? {_searchLocationMarker!} : {}); @@ -2158,8 +2173,8 @@ class _MaizeBusCoreState extends State { ? {_searchLocationMarker!} : {}, ) - : _displayedStopMarkers - .union(_displayedFavoriteStopMarkers) + : _displayedStopMarkers.values.toSet() + .union(_displayedFavoriteStopMarkers.values.toSet()) .union(_displayedJourneyMarkers) .union( _searchLocationMarker != null