Skip to content

Comments

Explorer fixes#315

Merged
pantelisss merged 11 commits intomainfrom
feature/fe-2021-explorer-fixes
Oct 14, 2025
Merged

Explorer fixes#315
pantelisss merged 11 commits intomainfrom
feature/fe-2021-explorer-fixes

Conversation

@pantelisss
Copy link
Collaborator

@pantelisss pantelisss commented Oct 9, 2025

Why?

Handle some cases in the explorer tab as described in the task

How?

  • Initially hidden stations counter text
  • Instead of "0 stations in the area", show "no stations in the area".
  • Fixed the way we get the visible area bounds in order to calculate the stations in the area.

Testing

  • Ensure the label is initially hidden
  • In an area with no stations, ensure the text is the expected "no stations in the area".
  • Ensure the stations counter is the expected when the visible area contains the antemeridian line

Additional context

fixes fe-2021

Summary by CodeRabbit

  • New Features

    • Map now reports and displays the number of visible stations (count-based) with smooth fade-in/out animations.
    • Stations count and description are optional and shown only when available.
  • Bug Fixes

    • Avoid unnecessary station updates when no devices are present, reducing flicker and wasted work.
  • Localization

    • Added localized “No stations in this area” message.
  • Tests

    • Updated unit tests to reflect optional/missing stations count.

@pantelisss pantelisss requested a review from PavlosTze October 9, 2025 14:17
@coderabbitai
Copy link

coderabbitai bot commented Oct 9, 2025

Walkthrough

Replaces visible-bounds callback with a rendered-features station-count flow: MapBoxMapView now aggregates deviceCount from point annotations on map idle and notifies as a stations count; Explorer and Search view models/views updated to consume an optional stations count and show localized "no stations" text.

Changes

Cohort / File(s) Summary
Map visible → stations count
PresentationLayer/UIComponents/MapBox/MapBoxMapView.swift
On map idle queries rendered features across layers, aggregates deviceCount from customData on point annotations and calls didChangeVisibleStationsCount(_:count:) (protocol/delegate signature updated). Minor symbol layer textSize stop change and small formatting tweak.
Map constants / annotation data
PresentationLayer/Utils/MapBox/MapBoxConstants.swift, PresentationLayer/UIComponents/Screens/Explorer/ExplorerFactory.swift
Added MapBoxConstants.customData; point annotations now include id and customData["deviceCount"] to carry per-point deviceCount.
Explorer view-model gating
PresentationLayer/UIComponents/Screens/Explorer/ExplorerViewModel.swift
Replaced map-bounds callback with stations-count flow: calls searchViewModel.updateStations(count:) only when explorerData.totalDevices > 0.
Search view-model changes
PresentationLayer/UIComponents/Screens/Explorer/Search/ExplorerSearchViewModel.swift, WeatherXMTests/PresentationLayer/ViewModels/ExplorerSearchViewModelTests.swift
stationsCount changed from String to String?; added stationsCountDescription: String?; updateStations sets stationsCount = nil when count == 0 and populates localized description. Tests updated initial expectation to nil.
Search view UI
PresentationLayer/UIComponents/Screens/Explorer/Search/SearchView+Content.swift
nonActiveView now conditionally renders stationsCount and stationsCountDescription using optional binding, ZStack/HStack layout, and opacity/explicit animations for transitions.
Localization
wxm-ios/Resources/Localizable/Localizable.xcstrings, wxm-ios/Resources/Localizable/LocalizableString+Search.swift
Added search_no_stations_in_area localization string and LocalizableString.Search.noStationsInArea enum case mapping.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant MapView as MapBoxMapView
    participant Mapbox as MapboxMap
    participant Coordinator
    participant ExplorerVM
    participant SearchVM as ExplorerSearchViewModel
    participant SearchView

    User->>MapView: pan/zoom -> map idle
    MapView->>Mapbox: read cameraState & rendered features
    Note right of MapView: aggregate `deviceCount` from feature.customData
    MapView->>Coordinator: didChangeVisibleStationsCount(count)
    Coordinator->>ExplorerVM: didUpdateStationsCount(count)

    alt explorerData.totalDevices > 0
        ExplorerVM->>SearchVM: updateStations(count)
        alt count == 0
            SearchVM->>SearchView: stationsCount = nil
            SearchVM->>SearchView: stationsCountDescription = LocalizableString.Search.noStationsInArea
        else
            SearchVM->>SearchView: stationsCount = "N"
            SearchVM->>SearchView: stationsCountDescription = LocalizableString.Search.stationsInArea
        end
    else
        ExplorerVM--x SearchVM: skip updateStations
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Map improvements #265 — Prior edits to MapBoxMapView/MapViewController delegate handling of visible bounds; this PR replaces that bounds callback with a stations-count callback.
  • Buy station promotional card #267 — Related adjustments to MapViewControllerDelegate and map-visible notification flow; touches the same delegate APIs.
  • Stations in the area UI element #261 — Overlapping changes to Explorer and map-visible/station-count plumbing and annotation metadata.

Suggested reviewers

  • PavlosTze

Poem

I’m a rabbit on the map, I hop and count with glee,
If stations near me twinkle, I’ll show “N” to thee.
If none are in the area, a gentle line will say:
“No stations in this area” — then I hop away. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title “Explorer fixes” is overly generic and fails to convey the specific changes made to station counter logic and visible area bounds handling in the Explorer tab; it uses non-descriptive wording that does not inform a teammate what was fixed. A reviewer scanning history would not understand the primary intent or scope of the updates from this title alone. Consider a more descriptive title that highlights the main changes, such as “Update Explorer station counter logic and antimeridian bounds handling.”
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed The pull request description follows the repository template by clearly stating why the changes were made, how they were implemented, and detailed testing steps, and it includes an additional context section. The only template section not present is screenshots, but it is optional for non-visual updates. Overall the description aligns well with the required structure.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/fe-2021-explorer-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b905494 and 52b0be0.

📒 Files selected for processing (6)
  • PresentationLayer/UIComponents/MapBox/MapBoxMapView.swift (2 hunks)
  • PresentationLayer/UIComponents/Screens/Explorer/ExplorerViewModel.swift (1 hunks)
  • PresentationLayer/UIComponents/Screens/Explorer/Search/ExplorerSearchViewModel.swift (2 hunks)
  • PresentationLayer/UIComponents/Screens/Explorer/Search/SearchView+Content.swift (3 hunks)
  • wxm-ios/Resources/Localizable/Localizable.xcstrings (1 hunks)
  • wxm-ios/Resources/Localizable/LocalizableString+Search.swift (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
PresentationLayer/UIComponents/Screens/Explorer/ExplorerViewModel.swift (2)
WeatherXMTests/PresentationLayer/ViewModels/ExplorerViewModelTests.swift (1)
  • explorerData (59-64)
PresentationLayer/UIComponents/Screens/Explorer/Search/ExplorerSearchViewModel.swift (1)
  • updateStations (111-114)
PresentationLayer/UIComponents/Screens/Explorer/Search/ExplorerSearchViewModel.swift (1)
WeatherXMTests/PresentationLayer/Extensions/NumericTests.swift (1)
  • localizedFormatted (42-47)
🪛 GitHub Check: swiftLint
PresentationLayer/UIComponents/Screens/Explorer/ExplorerViewModel.swift

[warning] 131-131:
Opening Brace Spacing Violation: Opening braces should be preceded by a single space and on the same line as the declaration (opening_brace)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: WeatherXM | Unit tests | wxm-ios - iOS
🔇 Additional comments (9)
PresentationLayer/UIComponents/MapBox/MapBoxMapView.swift (1)

182-184: LGTM! Correctly handles antemeridian crossing.

Using coordinateBoundsUnwrapped instead of coordinateBounds properly handles viewports that cross the antemeridian (±180° longitude). This directly addresses the PR objective to ensure accurate station counting when the visible area contains the International Date Line.

wxm-ios/Resources/Localizable/Localizable.xcstrings (1)

9353-9363: LGTM on the new localization entry.

The key aligns with the existing naming scheme and the copy matches the surrounding search strings.

PresentationLayer/UIComponents/Screens/Explorer/Search/ExplorerSearchViewModel.swift (4)

24-25: LGTM!

Making stationsCount optional enables the UI to conditionally render the count, supporting the "initially hidden" requirement. The addition of stationsCountDescription provides flexibility to show different messages for zero vs. non-zero counts.


112-113: LGTM!

The logic correctly implements the PR requirement to show "no stations in the area" instead of "0 stations in the area" by setting stationsCount to nil and stationsCountDescription to the localized "no stations" message when count is zero.


24-25: Property changes correctly support conditional display.

Making stationsCount optional enables the UI to conditionally render the counter—showing nothing when nil rather than displaying "0". The new stationsCountDescription property provides the appropriate localized text based on whether stations are present.

These changes align with the PR objectives to initially hide the counter and display context-appropriate text.


112-113: Logic correctly implements "no stations" text and conditional hiding.

The implementation properly addresses the PR requirements:

  1. Line 112: Sets stationsCount to nil when the count is 0, which hides the counter in the UI (addressing "initially hide the stations counter text").
  2. Line 113: Uses LocalizableString.Search.noStationsInArea when count is 0, changing the text from "0 stations in the area" to "no stations in the area" as specified.

The use of count.localizedFormatted ensures proper number formatting with locale-specific grouping separators.

wxm-ios/Resources/Localizable/LocalizableString+Search.swift (1)

20-20: LGTM!

The new localization case follows the existing pattern and supports the updated UI requirement for displaying "no stations in area" when the count is zero.

Also applies to: 54-55

PresentationLayer/UIComponents/Screens/Explorer/Search/SearchView+Content.swift (1)

24-97: LGTM!

The refactored nonActiveView correctly implements conditional rendering with smooth transitions:

  • Initially hides both stations count and description (when nil)
  • Shows only description when count is zero ("no stations in area")
  • Shows both count and description when count is positive

The .transition(.opacity.animation(.easeIn)) on both text elements and the .animation(.easeIn, value: viewModel.stationsCount) provide smooth UX when the content appears/disappears.

PresentationLayer/UIComponents/Screens/Explorer/ExplorerViewModel.swift (1)

119-134: Verify antemeridian handling in map bounds logic
No custom longitude-wrapping code or tests were found around lines 119–134 in ExplorerViewModel.swift. Ensure Mapbox’s CoordinateBounds.containsLongitude correctly handles spans across the ±180° meridian and add tests for map views crossing the antemeridian.

@weatherxmdev
Copy link
Collaborator

Code Coverage Summary

Framework Source Files Coverage
DataLayer.framework 48 65.25%
DomainLayer.framework 66 83.38%
Toolkit.framework 30 53.18%
WeatherXM.app 403 14.08%
station-intent.appex 2 83.04%
station-widgetExtension.appex 45 33.79%

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
WeatherXMTests/PresentationLayer/ViewModels/ExplorerSearchViewModelTests.swift (1)

78-83: Expand test coverage for the zero stations case.

The test validates the non-zero count scenario but doesn't cover the "no stations" case, which is a key requirement per the PR objectives. Consider adding tests for:

  1. updateStations(count: 0) — verify stationsCount is nil and stationsCountDescription contains the localized "no stations in area" message
  2. stationsCountDescription property for both zero and non-zero counts
  3. State transitions (e.g., from 1,000 stations to 0)

Example test additions:

@Test func updateStationsWithZeroCount() {
    viewModel.updateStations(count: 0)
    #expect(viewModel.stationsCount == nil)
    #expect(viewModel.stationsCountDescription != nil)
    // Verify the description contains expected localized text
}

@Test func updateStationsTransition() {
    viewModel.updateStations(count: 100)
    #expect(viewModel.stationsCount != nil)
    
    viewModel.updateStations(count: 0)
    #expect(viewModel.stationsCount == nil)
    #expect(viewModel.stationsCountDescription != nil)
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52b0be0 and c14d042.

📒 Files selected for processing (1)
  • WeatherXMTests/PresentationLayer/ViewModels/ExplorerSearchViewModelTests.swift (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: WeatherXM | Unit tests | wxm-ios - iOS
🔇 Additional comments (1)
WeatherXMTests/PresentationLayer/ViewModels/ExplorerSearchViewModelTests.swift (1)

79-79: LGTM! Correctly updated for optional stationsCount.

The test now correctly expects nil for the initial state, aligning with the production change that makes stationsCount optional.

@weatherxmdev
Copy link
Collaborator

Code Coverage Summary

Framework Source Files Coverage
DataLayer.framework 48 65.25%
DomainLayer.framework 66 83.38%
Toolkit.framework 30 53.18%
WeatherXM.app 403 14.08%
station-intent.appex 2 83.04%
station-widgetExtension.appex 45 33.79%

Copy link
Collaborator

@PavlosTze PavlosTze left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure the antimeridian issue is correct, the number in this screenshot for example seems very small.
The code I've been using is this if that helps:

                onExplorerData.value?.publicHexes?.sumOf {
                    val containsLon = if (westLon < eastLon && eastLon - westLon <= 180) {
                        it.center.lon in westLon..eastLon
                    } else {
                        it.center.lon <= westLon || it.center.lon >= eastLon
                    }
                    if (it.center.lat in southLat..northLat && containsLon) {
                        it.deviceCount ?: 0
                    } else {
                        0
                    }
                }
image

@pantelisss
Copy link
Collaborator Author

@PavlosTze, in which simulator did you test it? I can't reproduce it on my device

@PavlosTze
Copy link
Collaborator

@PavlosTze, in which simulator did you test it? I can't reproduce it on my device

It's this, but if it's working OK in a device then maybe it's a simulator issue so we can proceed.
image

@pantelisss pantelisss requested a review from PavlosTze October 10, 2025 13:40
@weatherxmdev
Copy link
Collaborator

Code Coverage Summary

Framework Source Files Coverage
DataLayer.framework 48 65.25%
DomainLayer.framework 66 83.38%
Toolkit.framework 30 53.18%
WeatherXM.app 403 14.09%
station-intent.appex 2 83.04%
station-widgetExtension.appex 45 34.04%

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
PresentationLayer/UIComponents/Screens/Explorer/ExplorerViewModel.swift (1)

119-122: LGTM: gated UI update meets “hide initially”.

Only forwards counts after explorer data is present.

🧹 Nitpick comments (2)
PresentationLayer/UIComponents/Screens/Explorer/ExplorerFactory.swift (1)

35-39: Good alignment of IDs and customData across features.

  • Adds cellIndex to heatmap features and deviceCount into customData on polygons/points; PointAnnotation id aligns with polygon id, enabling dedup by featureId in queries. Looks correct.

Optional: Use MapBoxConstants.heatmapSource for the GeoJSONSource id to avoid string drift elsewhere:

var geoJsonSource = GeoJSONSource(id: MapBoxConstants.heatmapSource)

Also applies to: 70-71, 77-79

PresentationLayer/UIComponents/MapBox/MapBoxMapView.swift (1)

182-205: Visible count aggregation logic looks sound; fix trailing whitespace.

  • Summing deviceCount from rendered features, keyed by featureId, avoids double counting between polygon/point layers. Correct approach for antemeridian cases.

  • SwiftLint flags trailing whitespace at Line 204; remove it.

  • You could restrict layerIds to the point layer for clarity/perf if not needed from polygons/heatmap.

  • Consider early-exiting on empty features to avoid computing intermediates.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34d5c2b and 149c292.

📒 Files selected for processing (4)
  • PresentationLayer/UIComponents/MapBox/MapBoxMapView.swift (5 hunks)
  • PresentationLayer/UIComponents/Screens/Explorer/ExplorerFactory.swift (2 hunks)
  • PresentationLayer/UIComponents/Screens/Explorer/ExplorerViewModel.swift (1 hunks)
  • PresentationLayer/Utils/MapBox/MapBoxConstants.swift (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
PresentationLayer/UIComponents/Screens/Explorer/ExplorerViewModel.swift (1)
PresentationLayer/UIComponents/Screens/Explorer/Search/ExplorerSearchViewModel.swift (1)
  • updateStations (111-114)
PresentationLayer/UIComponents/MapBox/MapBoxMapView.swift (1)
PresentationLayer/UIComponents/Screens/Explorer/ExplorerViewModel.swift (1)
  • didUpdateStationsCount (119-123)
🪛 GitHub Check: swiftLint
PresentationLayer/UIComponents/MapBox/MapBoxMapView.swift

[warning] 204-204:
Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: WeatherXM | Unit tests | wxm-ios - iOS
🔇 Additional comments (5)
PresentationLayer/Utils/MapBox/MapBoxConstants.swift (1)

24-24: LGTM: constant for custom data key.

Matches usage in MapBoxMapView feature.properties lookup.

PresentationLayer/UIComponents/MapBox/MapBoxMapView.swift (4)

100-102: Delegate method rename wired correctly.

Coordinator forwards the visible stations count to the VM.


158-159: No behavioral change.

Whitespace-only adjustment is fine.


298-298: Minor symbol text sizing tweak.

Zoom stop at 8: 0.1 is fine; verify legibility on low-DPI simulators.


397-398: No stale delegate methods remain
All MapViewControllerDelegate conformers (only Coordinator) implement didChangeVisibleStationsCount, and no didChangeVisibleBounds usages were found.

@weatherxmdev
Copy link
Collaborator

Code Coverage Summary

Framework Source Files Coverage
DataLayer.framework 48 65.25%
DomainLayer.framework 66 83.38%
Toolkit.framework 30 53.18%
WeatherXM.app 403 14.09%
station-intent.appex 2 83.04%
station-widgetExtension.appex 45 33.79%

@pantelisss pantelisss requested a review from PavlosTze October 13, 2025 14:20
@pantelisss
Copy link
Collaborator Author

@PavlosTze please take a look about the stations counter functionality we discussed

Copy link
Collaborator

@PavlosTze PavlosTze left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to work fine!

@pantelisss pantelisss merged commit 85bc25b into main Oct 14, 2025
4 checks passed
@pantelisss pantelisss deleted the feature/fe-2021-explorer-fixes branch October 14, 2025 12:33
@coderabbitai coderabbitai bot mentioned this pull request Oct 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants