Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Filter pins on map to match filters/search for storied sites #4459

1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased
## Added
- Filter pins on map to match filters/search for storied sites [#4458](https://github.com/rokwire/illinois-app/issues/4458)
- Add search to Bottomsheet [#4456](https://github.com/rokwire/illinois-app/issues/4456)
## [6.0.56] - 2024-11-04
### Fixed
Expand Down
2 changes: 1 addition & 1 deletion assets/strings.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1153,7 +1153,7 @@

"panel.explore.storied_sites.title": "Storied Sites",
"panel.explore.storied_sites.load.error": "Failed to load stored site details",
"panel.explore.storied_sites.check_in.try_again": "Check-in failed. Please try again.",
"panel.explore.storied_sites.check_in.try_again": "Check-in failed. Please sign in and try again.",
"panel.explore.storied_sites.check_in.failed": "Check-in failed due to an error.",
"panel.explore.storied_sites.one.day": "You can only check in once per day.",
"panel.explore.storied_sites.failed.clear": "Failed to clear check-in date. Please try again.",
Expand Down
2 changes: 1 addition & 1 deletion assets/strings.es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1152,7 +1152,7 @@

"panel.explore.storied_sites.title": "Storied Sites",
"panel.explore.storied_sites.load.error": "Failed to load stored site details",
"panel.explore.storied_sites.check_in.try_again": "Check-in failed. Please try again.",
"panel.explore.storied_sites.check_in.try_again": "Check-in failed. Please sign in and try again.",
"panel.explore.storied_sites.check_in.failed": "Check-in failed due to an error.",
"panel.explore.storied_sites.one.day": "You can only check in once per day.",
"panel.explore.storied_sites.failed.clear": "Failed to clear check-in date. Please try again.",
Expand Down
2 changes: 1 addition & 1 deletion assets/strings.zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1152,7 +1152,7 @@

"panel.explore.storied_sites.title": "Storied Sites",
"panel.explore.storied_sites.load.error": "Failed to load stored site details",
"panel.explore.storied_sites.check_in.try_again": "Check-in failed. Please try again.",
"panel.explore.storied_sites.check_in.try_again": "Check-in failed. Please sign in and try again.",
"panel.explore.storied_sites.check_in.failed": "Check-in failed due to an error.",
"panel.explore.storied_sites.one.day": "You can only check in once per day.",
"panel.explore.storied_sites.failed.clear": "Failed to clear check-in date. Please try again.",
Expand Down
96 changes: 64 additions & 32 deletions lib/ui/explore/ExploreMapPanel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ class _ExploreMapPanelState extends State<ExploreMapPanel>
bool _filtersDropdownVisible = false;

List<Explore>? _explores;
List<Explore>? _filteredExplores;
bool _exploreProgress = false;
Future<List<Explore>?>? _exploreTask;

Expand Down Expand Up @@ -375,7 +376,7 @@ class _ExploreMapPanelState extends State<ExploreMapPanel>
}
else if (name == Storage.notifySettingChanged) {
if (param == Storage.debugMapThresholdDistanceKey) {
_buildMapContentData(_explores, pinnedExplore: _pinnedMapExplore, updateCamera: false, zoom: _lastMarkersUpdateZoom, showProgress: true);
_buildMapContentData(_filteredExplores ?? _explores, pinnedExplore: _pinnedMapExplore, updateCamera: false, zoom: _lastMarkersUpdateZoom, showProgress: true);
}
else if (param == Storage.debugMapShowLevelsKey) {
setStateIfMounted(() { });
Expand Down Expand Up @@ -467,17 +468,20 @@ class _ExploreMapPanelState extends State<ExploreMapPanel>
ExploreStoriedSightsBottomSheet(
key: _storiedSightsKey,
places: _explores?.whereType<Place>().toList() ?? [],
onPlaceSelected: (places_model.Place place) {
_centerMapOnExplore(place, zoom: false);
_selectMapExplore(place);
},
onPlaceSelected: _onStoriedSitesPlaceSelected,
onFilteredPlacesChanged: _onStoriedSitesFilteredPlacesChanged,
onBackPressed: _onStoriedSitesBackPressed,
),
_buildExploreTypesDropDownContainer(),
]),
)
]);
}

void _onStoriedSitesBackPressed() {
_selectMapExplore(null);
}

// Map Widget

Widget _buildContent() {
Expand All @@ -501,7 +505,10 @@ class _ExploreMapPanelState extends State<ExploreMapPanel>
Widget _buildMapContent() {
return Stack(children: [
_buildMapView(),
_buildMapExploreBar(),
Visibility(
visible: _selectedMapType != ExploreMapType.StoriedSites,
child: _buildMapExploreBar()
),
Visibility(visible: _markersProgress, child:
Positioned.fill(child:
Center(child:
Expand Down Expand Up @@ -579,7 +586,7 @@ class _ExploreMapPanelState extends State<ExploreMapPanel>
_lastMarkersUpdateZoom = value;
}
else if ((_lastMarkersUpdateZoom! - value).abs() > _groupMarkersUpdateThresoldDelta) {
_buildMapContentData(_explores, pinnedExplore: _pinnedMapExplore, updateCamera: false, zoom: value, showProgress: true);
_buildMapContentData(_filteredExplores ?? _explores, pinnedExplore: _pinnedMapExplore, updateCamera: false, zoom: value, showProgress: true);
}
});
}
Expand Down Expand Up @@ -616,17 +623,31 @@ class _ExploreMapPanelState extends State<ExploreMapPanel>
if (_selectedMapType == ExploreMapType.StoriedSites) {
if (origin is Place) {
_storiedSightsKey.currentState?.selectPlace(origin);
// _centerMapOnExplore(origin);
_selectMapExplore(origin);
} else if (origin is List<Explore>) {
List<places_model.Place> places = origin.cast<places_model.Place>();
_storiedSightsKey.currentState?.selectPlaces(places);
_centerMapOnExplore(places);
_selectMapExplore(places);
}
} else {
_selectMapExplore(origin);
}
}

void _onStoriedSitesPlaceSelected(places_model.Place place) {
_centerMapOnExplore(place, zoom: false);
_selectMapExplore(place);
}

void _onStoriedSitesFilteredPlacesChanged(List<places_model.Place>? filteredExplores) {
if (!DeepCollectionEquality().equals(_filteredExplores, filteredExplores)) {
_mapController?.getZoomLevel().then((double value) {
_filteredExplores = (filteredExplores != null) ? List.from(filteredExplores) : null;
_buildMapContentData(_filteredExplores ?? _explores, pinnedExplore: _pinnedMapExplore, updateCamera: false, showProgress: true, zoom: value);
});
}
}

void _centerMapOnExplore(dynamic explore, {bool zoom = true}) async {
LatLng? targetPosition;
Expand Down Expand Up @@ -821,16 +842,20 @@ class _ExploreMapPanelState extends State<ExploreMapPanel>
}
else if (_selectedMapExplore != null) {
_pinMapExplore(null);
_selectedMapExplore = null;
_mapExploreBarAnimationController?.reverse().then((_) {
setStateIfMounted(() {
_selectedMapExplore = null;
});
setStateIfMounted(() {});
_updateSelectedMapStopRoutes();
});
}
else {
} else {
_pinMapExplore(null);
}
if (_selectedMapType == ExploreMapType.StoriedSites) {
// Get the current zoom level
_mapController?.getZoomLevel().then((double value) {
_buildMapContentData(_filteredExplores ?? _explores, updateCamera: false, showProgress: false, zoom: value, forceRefresh: true);
});
}
_logAnalyticsSelect(explore);
}

Expand Down Expand Up @@ -1880,16 +1905,18 @@ class _ExploreMapPanelState extends State<ExploreMapPanel>
if (mounted && (exploreTask == _exploreTask)) {
setState(() {
_explores = explores;
_filteredExplores = null;
_exploreTask = null;
_exploreProgress = false;
_mapKey = UniqueKey(); // force map rebuild
});
_selectMapExplore(null);
_displayContentPopups();
}
}
}
}


Future<void> _refreshExplores() async {
Future<List<Explore>?> exploreTask = _loadExplores();
List<Explore>? explores = await (_exploreTask = exploreTask);
Expand All @@ -1899,6 +1926,7 @@ class _ExploreMapPanelState extends State<ExploreMapPanel>
if (mounted && (exploreTask == _exploreTask)) {
setState(() {
_explores = explores;
_filteredExplores = null;
_exploreProgress = false;
_exploreTask = null;
});
Expand Down Expand Up @@ -2083,6 +2111,7 @@ class _ExploreMapPanelState extends State<ExploreMapPanel>
if (mounted) {
setState(() {
_explores = explores;
_filteredExplores = null;
});
}
});
Expand Down Expand Up @@ -2113,7 +2142,7 @@ class _ExploreMapPanelState extends State<ExploreMapPanel>

// Map Content

Future<void> _buildMapContentData(List<Explore>? explores, {Explore? pinnedExplore, bool updateCamera = false, bool showProgress = false, double? zoom}) async {
Future<void> _buildMapContentData(List<Explore>? explores, {Explore? pinnedExplore, bool updateCamera = false, bool showProgress = false, double? zoom, bool forceRefresh = false}) async {
LatLngBounds? exploresBounds = ExploreMap.boundsOfList(explores);

CameraUpdate? targetCameraUpdate;
Expand Down Expand Up @@ -2149,8 +2178,8 @@ class _ExploreMapPanelState extends State<ExploreMapPanel>
thresoldDistance = 0;
exploreMarkerGroups = (explores != null) ? <dynamic>{ ExploreMap.validFromList(explores) } : null;
}
if (!DeepCollectionEquality().equals(_exploreMarkerGroups, exploreMarkerGroups)) {

if (forceRefresh || !DeepCollectionEquality().equals(_exploreMarkerGroups, exploreMarkerGroups)) {
Future<Set<Marker>> buildMarkersTask = _buildMarkers(context, exploreGroups: exploreMarkerGroups, pinnedExplore: pinnedExplore);
_buildMarkersTask = buildMarkersTask;
if (showProgress && mounted) {
Expand Down Expand Up @@ -2335,26 +2364,29 @@ class _ExploreMapPanelState extends State<ExploreMapPanel>
if (explore is MTDStop) {
String markerAsset = 'images/map-marker-mtd-stop.png';
markerIcon = _markerIconCache[markerAsset] ??
(_markerIconCache[markerAsset] = await BitmapDescriptor.fromAssetImage(imageConfiguration, markerAsset));
(_markerIconCache[markerAsset] = await BitmapDescriptor.fromAssetImage(imageConfiguration, markerAsset));
markerAnchor = Offset(0.5, 0.5);
}
else {
} else {
Color? exploreColor = explore?.mapMarkerColor;
markerIcon = (exploreColor != null) ? BitmapDescriptor.defaultMarkerWithHue(ColorUtils.hueFromColor(exploreColor).toDouble()) : BitmapDescriptor.defaultMarker;
if (_selectedMapType == ExploreMapType.StoriedSites && explore == _selectedMapExplore) {
exploreColor = Styles().colors.fillColorSecondary;
}
markerIcon = (exploreColor != null)
? BitmapDescriptor.defaultMarkerWithHue(ColorUtils.hueFromColor(exploreColor).toDouble())
: BitmapDescriptor.defaultMarker;
markerAnchor = Offset(0.5, 1);
}
return Marker(
markerId: MarkerId("${markerPosition.latitude.toStringAsFixed(6)}:${markerPosition.latitude.toStringAsFixed(6)}"),
position: markerPosition,
icon: markerIcon,
anchor: markerAnchor,
consumeTapEvents: true,
onTap: () => _onTapMarker(explore),
infoWindow: InfoWindow(
title: explore?.mapMarkerTitle,
snippet: explore?.mapMarkerSnippet,
anchor: markerAnchor)
);
markerId: MarkerId("${markerPosition.latitude.toStringAsFixed(6)}:${markerPosition.longitude.toStringAsFixed(6)}"),
position: markerPosition,
icon: markerIcon,
anchor: markerAnchor,
consumeTapEvents: true,
onTap: () => _onTapMarker(explore),
infoWindow: InfoWindow(
title: explore?.mapMarkerTitle,
snippet: explore?.mapMarkerSnippet,
anchor: markerAnchor));
}
return null;
}
Expand Down
56 changes: 32 additions & 24 deletions lib/ui/explore/ExploreStoriedSightsBottomSheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import 'package:flutter_markdown/flutter_markdown.dart';
class ExploreStoriedSightsBottomSheet extends StatefulWidget {
final List<places_model.Place> places;
final Function(places_model.Place) onPlaceSelected;
final void Function(List<places_model.Place>? filteredPlaces)? onFilteredPlacesChanged;
final VoidCallback? onBackPressed;

ExploreStoriedSightsBottomSheet({Key? key, required this.places, required this.onPlaceSelected}) : super(key: key);
ExploreStoriedSightsBottomSheet({Key? key, required this.places, required this.onPlaceSelected, this.onFilteredPlacesChanged, this.onBackPressed}) : super(key: key);

@override
ExploreStoriedSightsBottomSheetState createState() => ExploreStoriedSightsBottomSheetState();
Expand Down Expand Up @@ -185,6 +187,7 @@ class ExploreStoriedSightsBottomSheetState extends State<ExploreStoriedSightsBot
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
widget.onBackPressed?.call();
}))];

double _calculateFilterButtonsHeight() {
Expand Down Expand Up @@ -665,39 +668,44 @@ class ExploreStoriedSightsBottomSheetState extends State<ExploreStoriedSightsBot
}).toList();
}

// Apply selected filters
if (_selectedFilters.isNotEmpty) {
if (_selectedFilters.contains(_customSelectionFilterKey)) {
filteredPlaces = filteredPlaces.where((place) => _customPlaces!.contains(place)).toList();
} else {
filteredPlaces = filteredPlaces.where((place) {
bool matchesFilter = false;
if (_selectedFilters.contains('Visited')) {
if (place.userData?.visited != null && place.userData!.visited!.isNotEmpty) {
if (_selectedFilters.length > 1) {
matchesFilter = _matchesOtherFilters(place);
} else {
matchesFilter = true;
}
// Apply custom places filter if selected
if (_selectedFilters.contains(_customSelectionFilterKey)) {
filteredPlaces = filteredPlaces.where((place) => _customPlaces!.contains(place)).toList();
}

Set<String> filtersToApply = Set.from(_selectedFilters);
filtersToApply.remove(_customSelectionFilterKey);

if (filtersToApply.isNotEmpty) {
filteredPlaces = filteredPlaces.where((place) {
bool matchesFilter = false;
if (filtersToApply.contains('Visited')) {
if (place.userData?.visited != null && place.userData!.visited!.isNotEmpty) {
if (filtersToApply.length > 1) {
matchesFilter = _matchesOtherFilters(place, filters: filtersToApply);
} else {
matchesFilter = false;
matchesFilter = true;
}
} else {
matchesFilter = place.tags != null && place.tags!.any((tag) => _selectedFilters.contains(tag));
matchesFilter = false;
}
return matchesFilter;
}).toList();
}
} else {
matchesFilter = place.tags != null && place.tags!.any((tag) => filtersToApply.contains(tag));
}
return matchesFilter;
}).toList();
}

setState(() {
_storiedSights = filteredPlaces;
});
}

bool _matchesOtherFilters(places_model.Place place) {
widget.onFilteredPlacesChanged?.call((_storiedSights.length < _allPlaces.length) ? _storiedSights : null);
}

Set<String> otherFilters = Set.from(_selectedFilters)..remove('Visited');
bool _matchesOtherFilters(places_model.Place place, {Set<String>? filters}) {
Set<String> otherFilters = filters ?? Set.from(_selectedFilters)..remove('Visited');
otherFilters.remove('Visited');
if (place.tags == null || place.tags!.isEmpty) {
return false;
}
Expand Down Expand Up @@ -1127,7 +1135,7 @@ class _ExploreStoriedSightWidgetState extends State<ExploreStoriedSightWidget> {
_placeCheckInDates.remove(now);
});

AppToast.showMessage(Localization().getStringEx('panel.explore.storied_sites.check_in.try_again', 'Check-in failed. Please try again.'));
AppToast.showMessage(Localization().getStringEx('panel.explore.storied_sites.check_in.try_again', 'Check-in failed. Please sign in and try again.'));
}
} catch (e) {
if (mounted) {
Expand Down