Skip to content

Cell capacity feature#318

Merged
pantelisss merged 16 commits intomainfrom
feature/cell_capacity_device_proximity
Oct 15, 2025
Merged

Cell capacity feature#318
pantelisss merged 16 commits intomainfrom
feature/cell_capacity_device_proximity

Conversation

@pantelisss
Copy link
Copy Markdown
Collaborator

@pantelisss pantelisss commented Oct 15, 2025

Why?

The PR for the whole feature

Summary by CodeRabbit

  • New Features

    • Capacity-aware Explorer: cells show capacity polygons, borders and capacity labels; tapping routes to device list including capacity.
    • Capacity checks when claiming/selecting locations present an alert to relocate or proceed.
  • Improvements

    • Enhanced map interactions with polygon/polyline/point annotations and tapped-annotation handling.
    • Station Overview fetches capacity before navigating.
  • Performance

    • Explorer data cached to reduce network requests.
  • Localization

    • Added capacity alert texts, “Read more”, and updated stations count format.

@pantelisss pantelisss requested a review from PavlosTze October 15, 2025 09:16
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 15, 2025

Walkthrough

Adds cell-capacity awareness end-to-end: new PublicHex.capacity and computed flag, explorer service with caching, capacity-aware annotations (polygons/lines/points), plumbing through map components and view models, capacity-based routing and alerts, DI updates, localization keys, and project file additions.

Changes

Cohort / File(s) Summary
Domain entities & extensions
wxm-ios/DomainLayer/.../PublicHex.swift, PresentationLayer/Extensions/DomainExtensions/PublicHex+.swift, wxm-ios/DomainLayer/.../UserDefaults+Constants.swift
Add PublicHex.capacity: Int? and cellCapacityReached: Bool computed property; add UserDefaults.GenericKey.explorerHexes.
Data layer: service & repository
wxm-ios/DataLayer/ExplorerService.swift, wxm-ios/DataLayer/.../ExplorerRepositoryImpl.swift, wxm-ios/DataLayer/DataLayer.xcodeproj/project.pbxproj
Add ExplorerService with 3‑minute cache for getPublicHexes(); inject service into ExplorerRepositoryImpl; wire new file into DataLayer project.
Dependency injection
wxm-ios/Swinject/SwinjectHelper.swift
Register ExplorerService in DI and inject into ExplorerRepositoryImpl.
Explorer data pipeline
PresentationLayer/UIComponents/Screens/Explorer/ExplorerData.swift, .../Explorer/ExplorerFactory.swift, .../Explorer/ExplorerViewModel.swift
Add cellCapacity polygon/line/text point collections; compute capacity visuals and userInfo; extend routeToDeviceListFor to accept capacity: Int.
Explorer stations list
.../ExplorerStationsList/ExplorerStationsListViewModel.swift, .../ExplorerStationsList/ExplorerStationsListView.swift, PresentationLayer/UIComponents/ViewModelsFactory.swift
Add cellCapacity property and initializer param; include capacity in pills/localized string; propagate cellCapacity via factory and preview.
MapBox components
PresentationLayer/UIComponents/MapBox/MapBoxMapView.swift, .../MapBox/MapBoxClaimDevice.swift, PresentationLayer/Utils/MapBox/MapBoxConstants.swift
Extract cellCapacity from annotation userInfo; route with capacity; add polygon/polyline/point annotations and callbacks; add manager IDs/constants.
Select location map
.../SelectLocationMap/SelectLocationMapView.swift, .../SelectLocationMap/SelectLocationMapViewModel.swift
Inject ExplorerUseCase; fetch explorer data async; accept polygon/polyline/point inputs and onAnnotationsChanged callback; track pointed annotations and capacity checks; add navigation/linkDependency.
Claim/select station flows
.../ClaimDevice/Location/ClaimDeviceLocationViewModel.swift, .../SelectStationLocation/SelectStationLocationViewModel.swift
Add guard that shows alert when pointed cell capacity is reached offering relocate or proceed anyway; short‑circuits original immediate confirm.
Navigation & overview
PresentationLayer/Navigation/DeepLinkHandler.swift, .../StationDetails/Overview/OverviewViewModel.swift, .../StationDetails/Overview/OverviewView.swift, PresentationLayer/UIComponents/ViewModelsFactory.swift
Pass cellCapacity through deep links and routing; OverviewViewModel now depends on ExplorerUseCase and fetches capacity before routing; factory resolves and injects ExplorerUseCase.
Localization
wxm-ios/Resources/Localizable/Localizable.xcstrings, wxm-ios/Resources/Localizable/LocalizableConstants.swift, wxm-ios/Resources/Localizable/LocalizableString+ClaimDevice.swift
Add claim-device capacity strings and read_more; change present_stations to two-arg format; add new LocalizableString cases and mappings.
Project wiring & tests
wxm-ios.xcodeproj/project.pbxproj, WeatherXMTests/...
Add PublicHex+.swift to project; update tests and previews to supply new params (explorerUseCase, cellCapacity) and adjust initializers accordingly.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant View as SelectLocationMapView
  participant VM as SelectLocationMapViewModel
  participant Map as MapBoxClaimDevice
  participant Explorer as ExplorerUseCase

  User->>View: Open Select Location
  View->>VM: init(useCase, explorerUseCase, ...)
  VM->>Explorer: getPublicHexes()
  Explorer-->>VM: PublicHexes (with capacity)
  VM-->>View: explorerData (polygons/lines/text)

  User->>Map: Point/tap polygon
  Map-->>VM: didPointAnnotations([PolygonAnnotation])
  VM->>VM: isPointedCellCapacityReached()
  alt Capacity reached
    VM-->>View: Show toast / present alert
  else Capacity available
    VM-->>View: Proceed normally
  end
Loading
sequenceDiagram
  autonumber
  participant User
  participant MapV as Explorer Map/MapBoxMapView
  participant EVM as ExplorerViewModel
  participant Factory as ViewModelsFactory
  participant Router as Router

  User->>MapV: Tap cell annotation
  MapV->>MapV: Extract hexIndex, center, capacity
  MapV->>EVM: routeToDeviceListFor(hexIndex, center, capacity)
  EVM->>Factory: getExplorerStationsListViewModel(cellIndex, center, cellCapacity)
  Factory-->>EVM: ExplorerStationsListViewModel
  EVM->>Router: explorerList(viewModel, cellCapacity)
  Router-->>User: Navigate to stations list
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

  • PavlosTze

Poem

A nibble of nodes, a hop through hexes bright,
I traced the borders, counted lights at night.
“Full?” the meadow hums — I pause, then try again;
Cached crumbs and borders guide my map‑made den.
If space remains, I leap — else I kindly wait. 🥕🗺️

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description only provides the Why section and omits the How, Testing, Screenshots, and Additional context sections required by the repository’s template, making the description incomplete. Without details on how the feature was implemented or steps taken to verify it, reviewers lack context to assess the changes. Please include all template sections to ensure clarity and a thorough review. Add the missing How, Testing, Screenshots, and Additional context sections per the template, detailing the implementation approach, test cases, and any visual changes introduced by this feature. Clearly describe how the cell capacity values are fetched, rendered, and used in the application, and list the steps you followed to verify these behaviors. If applicable, include screenshots or gifs showing the new capacity indicators and alerts in action to provide full context and support thorough review.
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.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Cell capacity feature" succinctly identifies the primary addition of cell capacity functionality and directly reflects the main purpose of this changeset, which introduces capacity data across multiple layers of the application. It is concise, clear, and avoids unnecessary detail while providing enough context for team members to understand the focus at a glance. It uses relevant domain terminology and accurately summarizes the feature scope.
✨ 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/cell_capacity_device_proximity

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
Copy Markdown

@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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
PresentationLayer/UIComponents/Screens/Explorer/ExplorerData.swift (1)

11-16: Equatable omits newly added fields (may prevent UI updates)

== doesn’t compare textPoints, cellCapacityPoints, cellBorderPoints, cellCapacityTextPoints. Changes to these won’t reflect in equality, risking stale UI. Include them.

 public static func == (lhs: ExplorerData, rhs: ExplorerData) -> Bool {
-    lhs.totalDevices == rhs.totalDevices &&
-    lhs.polygonPoints == rhs.polygonPoints &&
-    lhs.coloredPolygonPoints == rhs.coloredPolygonPoints &&
-    lhs.geoJsonSource.id == rhs.geoJsonSource.id
+    lhs.totalDevices == rhs.totalDevices &&
+    lhs.polygonPoints == rhs.polygonPoints &&
+    lhs.coloredPolygonPoints == rhs.coloredPolygonPoints &&
+    lhs.textPoints == rhs.textPoints &&
+    lhs.cellCapacityPoints == rhs.cellCapacityPoints &&
+    lhs.cellBorderPoints == rhs.cellBorderPoints &&
+    lhs.cellCapacityTextPoints == rhs.cellCapacityTextPoints &&
+    lhs.geoJsonSource.id == rhs.geoJsonSource.id
 }

Additionally, consider adding unit assertions for these fields in ExplorerViewModel tests.

Also applies to: 23-26, 33-36, 42-46, 52-55

♻️ Duplicate comments (1)
PresentationLayer/UIComponents/Screens/ClaimDevice/Location/ClaimDeviceLocationViewModel.swift (1)

33-44: LGTM!

The capacity check and alert flow is correctly implemented for the claiming flow. The ok action appropriately calls the completion callback.

Note: The code duplication with SelectStationLocationViewModel.swift has already been flagged in a previous comment.

🧹 Nitpick comments (15)
PresentationLayer/UIComponents/Screens/SelectStationLocation/SelectStationLocationViewModel.swift (1)

53-64: Consider extracting duplicated alert logic.

This alert handling pattern is duplicated in ClaimDeviceLocationViewModel.swift (lines 33-44). Consider extracting a shared helper method to avoid code duplication and ensure consistency.

For example, create a shared method in a utility or base class:

func showCellCapacityAlert(onProceed: @escaping () -> Void) {
    let okAction: AlertHelper.AlertObject.Action = (
        LocalizableString.ClaimDevice.proceedAnyway.localized,
        { _ in onProceed() }
    )
    let obj: AlertHelper.AlertObject = .init(
        title: LocalizableString.ClaimDevice.cellCapacityReachedAlertTitle.localized,
        message: LocalizableString.ClaimDevice.cellCapacityReachedAlertText.localized,
        cancelActionTitle: LocalizableString.ClaimDevice.relocate.localized,
        cancelAction: { },
        okAction: okAction
    )
    AlertHelper().showAlert(obj)
}
PresentationLayer/UIComponents/Screens/Explorer/ExplorerViewModel.swift (1)

75-78: Clamp capacity to non-negative before routing

Defensive guard avoids odd UI if a negative slips through.

Apply:

 func routeToDeviceListFor(_ hexIndex: String, _ coordinates: CLLocationCoordinate2D?, capacity: Int) {
     if let coordinates {
-        let route = Route.explorerList(ViewModelsFactory.getExplorerStationsListViewModel(cellIndex: hexIndex, cellCenter: coordinates, cellCapacity: capacity))
+        let safeCapacity = max(0, capacity)
+        let route = Route.explorerList(
+            ViewModelsFactory.getExplorerStationsListViewModel(
+                cellIndex: hexIndex,
+                cellCenter: coordinates,
+                cellCapacity: safeCapacity
+            )
+        )
         Router.shared.navigateTo(route)
     }
 }
PresentationLayer/UIComponents/Screens/ExplorerStationsList/ExplorerStationsListViewModel.swift (1)

59-69: Include capacity in hashing to avoid collisions

If two view models are created for the same cellIndex but with different capacities, the current hash collides. Include cellCapacity in hash for safer identity.

Outside this range, update the Hashable conformance:

extension ExplorerStationsListViewModel: HashableViewModel {
    nonisolated func hash(into hasher: inout Hasher) {
        hasher.combine(cellIndex)
        hasher.combine(cellCapacity)
    }
}
wxm-ios/DataLayer/ExplorerService.swift (2)

14-21: Align access control (public class vs internal API)

Either make getPublicHexes public or make ExplorerService internal if only consumed within DataLayer. Keeps API surface minimal and avoids confusion.


37-47: Use handleEvents instead of flatMap for side-effect caching

This keeps the publisher chain simpler and avoids unnecessary wrapping.

Apply:

-        return publisher.flatMap { [weak self] response in
-            guard let self else {
-                return Just(response)
-            }
-
-            if let value = response.value {
-                self.cache.insertValue(value, expire: self.cacheValidationInterval, for: self.cacheKey)
-            }
-
-            return Just(response)
-        }.eraseToAnyPublisher()
+        return publisher
+            .handleEvents(receiveOutput: { [weak self] response in
+                guard let self, let value = response.value else { return }
+                self.cache.insertValue(value, expire: self.cacheValidationInterval, for: self.cacheKey)
+            })
+            .eraseToAnyPublisher()
PresentationLayer/UIComponents/ViewModelsFactory.swift (2)

27-31: Injecting ExplorerUseCase into OverviewViewModel is correct; avoid force‑unwrap

Good wiring. Consider avoiding resolve(... )! to prevent crash if DI misconfigured; prefer guard/fatalError or a helper to unwrap with a clear message for consistency across the factory.


309-313: Typo: rename exlporerUseCaseexplorerUseCase

Minor naming fix for consistency/readability.

-        let exlporerUseCase = SwinjectHelper.shared.getContainerForSwinject().resolve(ExplorerUseCaseApi.self)!
-        return SelectLocationMapViewModel(useCase: useCase, explorerUseCase: exlporerUseCase, initialCoordinate: initialCoordinate)
+        let explorerUseCase = SwinjectHelper.shared.getContainerForSwinject().resolve(ExplorerUseCaseApi.self)!
+        return SelectLocationMapViewModel(useCase: useCase, explorerUseCase: explorerUseCase, initialCoordinate: initialCoordinate)
PresentationLayer/UIComponents/Screens/WeatherStations/StationDetails/Overview/OverviewViewModel.swift (1)

129-147: Handle capacity fetch failures gracefully (fallback or user feedback)

If getCell fails or returns nil capacity, we silently return. Consider:

  • Showing a non-blocking error or toast, or
  • Navigating to the list without capacity (e.g., capacity = 0/unknown), so the flow isn’t a dead end.

Loader dismissal is safely handled via defer — good.

-            guard let cellIndex = device?.cellIndex,
-                  let center = device?.cellCenter?.toCLLocationCoordinate2D(),
-                  let capacity = try? await explorerUseCase.getCell(cellIndex: cellIndex).get()?.capacity else {
-                return
-            }
+            guard let cellIndex = device?.cellIndex,
+                  let center = device?.cellCenter?.toCLLocationCoordinate2D() else {
+                return
+            }
+            let capacity = (try? await explorerUseCase.getCell(cellIndex: cellIndex).get()?.capacity) ?? 0
+            if capacity == 0 {
+                // Optional: surface a subtle message to the user
+                // ToastCenter.show(.info(LocalizableString.ClaimDevice.cellCapacityReachedMessage.localized))
+            }
PresentationLayer/UIComponents/Screens/Explorer/ExplorerFactory.swift (3)

74-80: Avoid storing nil capacity into userInfo

publicHex.capacity as Any may store an Optional into [String: Any]. Prefer adding the key only when capacity exists to prevent unexpected type checks downstream.

-            polygonAnnotation.userInfo = [ExplorerKeys.cellCenter.rawValue: CLLocationCoordinate2D(latitude: publicHex.center.lat,
-                                                                                                   longitude: publicHex.center.lon),
-                                          ExplorerKeys.cellIndex.rawValue: publicHex.index,
-                                          ExplorerKeys.deviceCount.rawValue: publicHex.deviceCount ?? 0,
-                                          ExplorerKeys.cellCapacity.rawValue: publicHex.capacity as Any]
+            var userInfo: [String: Any] = [
+                ExplorerKeys.cellCenter.rawValue: CLLocationCoordinate2D(latitude: publicHex.center.lat,
+                                                                          longitude: publicHex.center.lon),
+                ExplorerKeys.cellIndex.rawValue: publicHex.index,
+                ExplorerKeys.deviceCount.rawValue: publicHex.deviceCount ?? 0
+            ]
+            if let capacity = publicHex.capacity {
+                userInfo[ExplorerKeys.cellCapacity.rawValue] = capacity
+            }
+            polygonAnnotation.userInfo = userInfo

100-105: Capacity text points — OK

Shows “count/capacity” when both present. Consider using the same rule for zero counts to display “0/capacity” intentionally (current code does).


112-112: SwiftLint: extra blank line

Reduce consecutive blank lines to satisfy vertical_whitespace rule.

As flagged by SwiftLint. [Based on static analysis]

PresentationLayer/UIComponents/BaseComponents/SelectLocationMap/SelectLocationMapViewModel.swift (1)

157-169: Improve error handling for explorer data fetching.

Currently, errors during explorer data fetching are only printed to the console. Users receive no feedback about failures, and there's no retry mechanism.

Consider adding user-facing error handling:

 func getExplorerData() async {
 	do {
 		let result = try await explorerUseCase.getPublicHexes()
 		switch result {
 			case .success(let hexes):
 				self.explorerData = ExplorerFactory(publicHexes: hexes).generateExplorerData()
 			case .failure(let error):
-				print(error)
+				Toast.shared.show(text: "Failed to load map data. Please try again.".attributedMarkdown ?? "", type: .error)
+				print("Explorer data fetch failed: \(error)")
 		}
 	} catch {
-		print(error)
+		Toast.shared.show(text: "Failed to load map data. Please try again.".attributedMarkdown ?? "", type: .error)
+		print("Explorer data fetch error: \(error)")
 	}
 }
PresentationLayer/UIComponents/MapBox/MapBoxClaimDevice.swift (3)

168-168: Consider changing protocol visibility to private.

The MapViewLocationControllerDelegate protocol is only used within this file, so private would be more appropriate than fileprivate.

Based on static analysis.

Apply this diff:

-fileprivate protocol MapViewLocationControllerDelegate: AnyObject {
+private protocol MapViewLocationControllerDelegate: AnyObject {
 	func didPointAnnotations(_ annotations: [PolygonAnnotation])
 }

304-304: Remove extra blank line.

There are 2 consecutive blank lines here. Limit to a single blank line per the project's style guidelines.

Based on static analysis.


141-145: Consider optimizing annotation updates.

The current implementation updates all annotation managers on every updateUIViewController call, even if the annotations haven't changed. This could cause performance issues with frequent updates.

Consider caching the previous annotations and only updating when they change:

private var lastPolygonPoints: [PolygonAnnotation]?
private var lastPolylinePoints: [PolylineAnnotation]?
private var lastTextPoints: [PointAnnotation]?

func updateUIViewController(_ controller: MapViewLocationController, context _: Context) {
    if lastPolygonPoints != polygonPoints {
        controller.polygonManager?.annotations = polygonPoints ?? []
        lastPolygonPoints = polygonPoints
    }
    if lastPolylinePoints != polylinePoints {
        controller.polylineManager?.annotations = polylinePoints ?? []
        lastPolylinePoints = polylinePoints
    }
    if lastTextPoints != textPoints {
        controller.pointManager?.annotations = textPoints ?? []
        lastTextPoints = textPoints
    }
}

Note: This requires PolygonAnnotation, PolylineAnnotation, and PointAnnotation to conform to Equatable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85bc25b and f029606.

📒 Files selected for processing (27)
  • PresentationLayer/Extensions/DomainExtensions/PublicHex+.swift (1 hunks)
  • PresentationLayer/Navigation/DeepLinkHandler.swift (1 hunks)
  • PresentationLayer/UIComponents/BaseComponents/SelectLocationMap/SelectLocationMapView.swift (2 hunks)
  • PresentationLayer/UIComponents/BaseComponents/SelectLocationMap/SelectLocationMapViewModel.swift (5 hunks)
  • PresentationLayer/UIComponents/MapBox/MapBoxClaimDevice.swift (9 hunks)
  • PresentationLayer/UIComponents/MapBox/MapBoxMapView.swift (1 hunks)
  • PresentationLayer/UIComponents/Screens/ClaimDevice/Location/ClaimDeviceLocationViewModel.swift (1 hunks)
  • PresentationLayer/UIComponents/Screens/Explorer/ExplorerData.swift (1 hunks)
  • PresentationLayer/UIComponents/Screens/Explorer/ExplorerFactory.swift (4 hunks)
  • PresentationLayer/UIComponents/Screens/Explorer/ExplorerViewModel.swift (1 hunks)
  • PresentationLayer/UIComponents/Screens/ExplorerStationsList/ExplorerStationsListView.swift (1 hunks)
  • PresentationLayer/UIComponents/Screens/ExplorerStationsList/ExplorerStationsListViewModel.swift (2 hunks)
  • PresentationLayer/UIComponents/Screens/SelectStationLocation/SelectStationLocationViewModel.swift (1 hunks)
  • PresentationLayer/UIComponents/Screens/WeatherStations/StationDetails/Overview/OverviewView.swift (1 hunks)
  • PresentationLayer/UIComponents/Screens/WeatherStations/StationDetails/Overview/OverviewViewModel.swift (2 hunks)
  • PresentationLayer/UIComponents/ViewModelsFactory.swift (3 hunks)
  • PresentationLayer/Utils/MapBox/MapBoxConstants.swift (1 hunks)
  • wxm-ios.xcodeproj/project.pbxproj (4 hunks)
  • wxm-ios/DataLayer/DataLayer.xcodeproj/project.pbxproj (4 hunks)
  • wxm-ios/DataLayer/DataLayer/RepositoryImplementations/ExplorerRepositoryImpl.swift (2 hunks)
  • wxm-ios/DataLayer/ExplorerService.swift (1 hunks)
  • wxm-ios/DomainLayer/DomainLayer/Entities/Codables/Cells/PublicHex.swift (3 hunks)
  • wxm-ios/DomainLayer/DomainLayer/Extensions/UserDefaults+Constants.swift (1 hunks)
  • wxm-ios/Resources/Localizable/Localizable.xcstrings (7 hunks)
  • wxm-ios/Resources/Localizable/LocalizableConstants.swift (4 hunks)
  • wxm-ios/Resources/Localizable/LocalizableString+ClaimDevice.swift (2 hunks)
  • wxm-ios/Swinject/SwinjectHelper.swift (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (16)
PresentationLayer/UIComponents/Screens/SelectStationLocation/SelectStationLocationViewModel.swift (2)
PresentationLayer/UIComponents/BaseComponents/SelectLocationMap/SelectLocationMapViewModel.swift (1)
  • isPointedCellCapacityReached (137-144)
PresentationLayer/Utils/UIKitUtils/AlertHelper/AlertHelper.swift (1)
  • showAlert (39-73)
PresentationLayer/UIComponents/Screens/Explorer/ExplorerFactory.swift (1)
WeatherXMTests/PresentationLayer/ViewModels/ExplorerViewModelTests.swift (1)
  • explorerData (59-64)
PresentationLayer/UIComponents/MapBox/MapBoxMapView.swift (1)
PresentationLayer/UIComponents/Screens/Explorer/ExplorerViewModel.swift (1)
  • routeToDeviceListFor (75-80)
PresentationLayer/UIComponents/ViewModelsFactory.swift (1)
wxm-ios/Swinject/SwinjectHelper.swift (1)
  • getContainerForSwinject (272-274)
PresentationLayer/UIComponents/Screens/ExplorerStationsList/ExplorerStationsListViewModel.swift (1)
WeatherXMTests/PresentationLayer/ViewModels/ExplorerStationsListViewModelTests.swift (1)
  • pills (32-42)
PresentationLayer/UIComponents/Screens/ClaimDevice/Location/ClaimDeviceLocationViewModel.swift (2)
PresentationLayer/UIComponents/BaseComponents/SelectLocationMap/SelectLocationMapViewModel.swift (1)
  • isPointedCellCapacityReached (137-144)
PresentationLayer/Utils/UIKitUtils/AlertHelper/AlertHelper.swift (1)
  • showAlert (39-73)
PresentationLayer/UIComponents/Screens/ExplorerStationsList/ExplorerStationsListView.swift (1)
PresentationLayer/UIComponents/ViewModelsFactory.swift (1)
  • getExplorerStationsListViewModel (90-94)
PresentationLayer/UIComponents/BaseComponents/SelectLocationMap/SelectLocationMapViewModel.swift (4)
WeatherXMTests/PresentationLayer/ViewModels/ExplorerViewModelTests.swift (1)
  • explorerData (59-64)
WeatherXMTests/PresentationLayer/ViewModels/MyWalletViewModelTests.swift (1)
  • linkNavigation (42-49)
wxm-ios/DomainLayer/DomainLayer/UseCases/ExplorerUseCase.swift (1)
  • getPublicHexes (76-78)
PresentationLayer/UIComponents/Screens/Explorer/ExplorerFactory.swift (1)
  • generateExplorerData (32-123)
wxm-ios/DataLayer/ExplorerService.swift (3)
wxm-ios/DataLayer/DataLayer/RepositoryImplementations/ExplorerRepositoryImpl.swift (1)
  • getPublicHexes (37-39)
wxm-ios/Toolkit/Toolkit/Utils/TimeValidationCache.swift (2)
  • getCachedValue (76-80)
  • insertValue (51-57)
wxm-ios/DataLayer/DataLayer/Networking/ApiClient.swift (1)
  • requestCodable (63-65)
PresentationLayer/UIComponents/Screens/Explorer/ExplorerViewModel.swift (1)
PresentationLayer/UIComponents/ViewModelsFactory.swift (1)
  • getExplorerStationsListViewModel (90-94)
PresentationLayer/UIComponents/Screens/WeatherStations/StationDetails/Overview/OverviewView.swift (1)
PresentationLayer/UIComponents/ViewModelsFactory.swift (1)
  • getStationOverviewViewModel (27-32)
PresentationLayer/Navigation/DeepLinkHandler.swift (1)
PresentationLayer/UIComponents/ViewModelsFactory.swift (1)
  • getExplorerStationsListViewModel (90-94)
wxm-ios/DataLayer/DataLayer/RepositoryImplementations/ExplorerRepositoryImpl.swift (2)
wxm-ios/DataLayer/ExplorerService.swift (1)
  • getPublicHexes (23-48)
WeatherXMTests/DomainLayer/MockRepositories/MockExplorerRepositoryImpl.swift (1)
  • getPublicHexes (28-37)
PresentationLayer/UIComponents/BaseComponents/SelectLocationMap/SelectLocationMapView.swift (2)
WeatherXMTests/PresentationLayer/ViewModels/ExplorerViewModelTests.swift (1)
  • explorerData (59-64)
PresentationLayer/UIComponents/BaseComponents/SelectLocationMap/SelectLocationMapViewModel.swift (1)
  • handlePointedAnnotationsChange (124-135)
PresentationLayer/UIComponents/Screens/WeatherStations/StationDetails/Overview/OverviewViewModel.swift (3)
wxm-ios/DataLayer/DataLayer/RepositoryImplementations/LocationRepositoryImpl.swift (1)
  • toCLLocationCoordinate2D (220-222)
wxm-ios/DomainLayer/DomainLayer/UseCases/ExplorerUseCase.swift (1)
  • getCell (61-74)
PresentationLayer/UIComponents/ViewModelsFactory.swift (1)
  • getExplorerStationsListViewModel (90-94)
PresentationLayer/UIComponents/MapBox/MapBoxClaimDevice.swift (1)
PresentationLayer/UIComponents/MapBox/MapBoxMapView.swift (3)
  • makeUIViewController (52-59)
  • makeCoordinator (80-82)
  • updateUIViewController (70-76)
🪛 GitHub Check: swiftLint
PresentationLayer/UIComponents/Screens/Explorer/ExplorerFactory.swift

[warning] 112-112:
Vertical Whitespace Violation: Limit vertical whitespace to a single empty line; currently 2 (vertical_whitespace)

PresentationLayer/UIComponents/MapBox/MapBoxClaimDevice.swift

[warning] 168-168:
Private over Fileprivate Violation: Prefer private over fileprivate declarations (private_over_fileprivate)


[warning] 304-304:
Vertical Whitespace Violation: Limit vertical whitespace to a single empty line; currently 2 (vertical_whitespace)


[warning] 290-290:
Empty Enum Arguments Violation: Arguments can be omitted when matching enums with associated values if they are not used (empty_enum_arguments)

⏰ 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 (38)
wxm-ios/DomainLayer/DomainLayer/Extensions/UserDefaults+Constants.swift (2)

55-55: LGTM! Enum case syntax is correct.

The new explorerHexes case is properly declared with the correct raw value format consistent with other keys in the enum.


59-76: No change required: explorerHexes caches public PublicHex data via ExplorerService’s TimeValidationCache and isn’t user-specific.

wxm-ios/DataLayer/DataLayer.xcodeproj/project.pbxproj (1)

47-47: LGTM! Standard Xcode project file updates.

The additions properly register ExplorerService.swift as a source file in the DataLayer target with all necessary build phases and group membership.

Also applies to: 155-155, 323-323, 667-667

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

89-95: LGTM! Capacity-aware annotation handling implemented correctly.

The guard statement properly validates both hexIndex and cellCapacity are present before routing, and the extracted capacity is correctly passed through to the routing method. This aligns well with the capacity-aware flow documented in the related changes.

PresentationLayer/Utils/MapBox/MapBoxConstants.swift (1)

19-19: LGTM! New annotation manager identifiers added.

The constants follow the existing naming convention and are properly defined for the new capacity-aware annotation managers.

Also applies to: 24-24

PresentationLayer/UIComponents/Screens/WeatherStations/StationDetails/Overview/OverviewView.swift (1)

122-122: LGTM! Preview updated to use factory method.

The change aligns with the dependency injection pattern by using ViewModelsFactory.getStationOverviewViewModel instead of a direct mock instance, ensuring the preview uses the same construction path as production code (including the explorerUseCase dependency).

wxm-ios.xcodeproj/project.pbxproj (4)

372-372: Build file entry looks correct

PBXBuildFile references the right fileRef and is well‑formed.


1161-1161: File reference and location OK

PBXFileReference is consistent and scoped under the correct group hierarchy.


2558-2558: Grouped under DomainExtensions — good

Placement under PresentationLayer/Extensions/DomainExtensions is appropriate.


4235-4235: Confirm extension targets PublicHex+.swift is only added to the main wxm-ios target. If station-widget or station-intent reference this code, add the file to their Sources build phase as well.

PresentationLayer/Extensions/DomainExtensions/PublicHex+.swift (1)

11-17: LGTM!

The computed property safely handles optional values and implements the correct capacity check logic (deviceCount >= capacity).

PresentationLayer/UIComponents/Screens/SelectStationLocation/SelectStationLocationViewModel.swift (1)

53-64: Verify alert strings are localized.

The capacity check and alert flow is correctly implemented. Ensure the localization keys referenced here (e.g., LocalizableString.ClaimDevice.proceedAnyway, cellCapacityReachedAlertTitle, cellCapacityReachedAlertText, relocate) are properly defined in your localization files.

PresentationLayer/UIComponents/Screens/ExplorerStationsList/ExplorerStationsListView.swift (1)

211-211: LGTM!

The preview code correctly passes the new cellCapacity parameter with a reasonable default value of 0 for testing purposes.

PresentationLayer/Navigation/DeepLinkHandler.swift (1)

242-244: LGTM!

The capacity extraction with a default value of 0 is appropriate and aligns with the pattern used elsewhere in the codebase. The capacity value is correctly passed to the view model factory.

wxm-ios/DomainLayer/DomainLayer/Entities/Codables/Cells/PublicHex.swift (4)

10-10: LGTM!

The optional capacity property is appropriately added to the struct to support the cell capacity feature.


19-19: LGTM!

The capacity coding key is correctly added to support JSON encoding/decoding.


27-41: LGTM!

The initializer correctly includes the capacity parameter with an appropriate default value of nil for optional handling.


46-46: LGTM!

The decoder correctly uses decodeIfPresent to handle the optional capacity field, consistent with other optional properties in the struct.

wxm-ios/Swinject/SwinjectHelper.swift (2)

43-46: LGTM!

The ExplorerService registration is correctly configured with container scope for singleton behavior and properly wired with UserDefaultsService as its cache manager. This follows the established pattern used for other services in the codebase.


104-105: LGTM!

The ExplorerRepository registration correctly reflects the new dependency on ExplorerService, properly injecting it through the resolver.

wxm-ios/DataLayer/DataLayer/RepositoryImplementations/ExplorerRepositoryImpl.swift (3)

16-16: LGTM!

The ExplorerService dependency is appropriately added to enable the repository to delegate data fetching and caching to the service layer.


18-20: LGTM!

The initializer correctly accepts and stores the ExplorerService dependency, following standard dependency injection patterns.


38-38: LGTM!

Delegating getPublicHexes() to the service layer is a good architectural improvement that enables caching and separation of concerns.

wxm-ios/Resources/Localizable/LocalizableConstants.swift (2)

621-623: read_more key mapping looks good

Key wiring for readMore → "read_more" is consistent with usage.


227-229: Verify two integer placeholders in present_stations_format for all locales
Ensure each locale entry in Localizable.xcstrings includes exactly two “%d” placeholders to avoid runtime formatting crashes.

PresentationLayer/UIComponents/BaseComponents/SelectLocationMap/SelectLocationMapView.swift (2)

22-27: Good integration of explorer overlays and annotation callback

Passing capacity/border/text points and forwarding onAnnotationsChanged to the VM is clean.


134-136: Preview DI updated correctly

Resolving ExplorerUseCaseApi for the preview aligns with the new VM initializer.

PresentationLayer/UIComponents/Screens/ExplorerStationsList/ExplorerStationsListViewModel.swift (1)

46-47: No change required for localization placeholders

The present_stations_format entry already defines two integer placeholders (%d/%d stations present), matching count and capacity.

PresentationLayer/UIComponents/ViewModelsFactory.swift (1)

90-94: Capacity forwarded into ExplorerStationsListViewModel — looks good

API updated to carry cellCapacity; DI resolution consistent with the rest of the file.

PresentationLayer/UIComponents/Screens/WeatherStations/StationDetails/Overview/OverviewViewModel.swift (1)

40-46: Constructor now requires ExplorerUseCase — good dependency injection

Clean addition and storage; default linkNavigation preserved.

PresentationLayer/UIComponents/Screens/Explorer/ExplorerFactory.swift (6)

17-18: New userInfo key cell_capacity — OK

Key addition is clear and consistent with existing naming.


26-31: Capacity styling constants — OK

Consistent with design token usage and readable defaults.


50-54: Added capacity/border/text arrays — OK

Data model expansion aligns with capacity‑aware rendering.


87-93: Capacity overlay polygon — OK

Properly switches color/outline when capacity reached; opacity separate from base fill.


106-109: Cell border polyline — OK

Simple border with stateful color; width is fine unless design specifies otherwise.


117-121: ExplorerData construction with new fields — OK

All new arrays wired through; defaults preserved elsewhere.

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

169-174: New ClaimDevice localization keys added — OK; ensure strings exist

Enum cases and key mappings look consistent. Please verify these keys are present in Localizable.xcstrings for all supported locales.

Also applies to: 516-526

PresentationLayer/UIComponents/BaseComponents/SelectLocationMap/SelectLocationMapViewModel.swift (1)

137-144: LGTM! Capacity checking logic is correct.

The capacity check correctly extracts deviceCount and capacity from the annotation's userInfo dictionary and returns true when the cell is at or above capacity. The safe optional chaining prevents crashes if the data is missing.

@@ -0,0 +1,18 @@
//
// PublicHexes+.swift
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix filename in header comment.

The filename in the header comment says "PublicHexes+.swift" but the actual filename is "PublicHex+.swift" (singular, not plural).

Apply this diff:

-//  PublicHexes+.swift
+//  PublicHex+.swift
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// PublicHexes+.swift
// PublicHex+.swift
🤖 Prompt for AI Agents
In PresentationLayer/Extensions/DomainExtensions/PublicHex+.swift around line 2,
the file header comment has the wrong filename "PublicHexes+.swift"; update the
header comment to the correct filename "PublicHex+.swift" so the header matches
the actual file name.

Comment on lines +85 to +87
Task { @MainActor in
await getExplorerData()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Store and cancel the explorer data fetch task.

Spawning a Task in an initializer without storing a reference means it cannot be cancelled if the view model is deallocated. This can lead to:

  • Memory leaks if the task captures self strongly
  • Crashes if the task completes after deallocation
  • Wasted network requests

Apply this diff to store and cancel the task:

+	private var explorerDataTask: Task<Void, Never>?
+
 	init(useCase: DeviceLocationUseCaseApi,
 		 explorerUseCase: ExplorerUseCaseApi,
 		 initialCoordinate: CLLocationCoordinate2D? = nil,
 		 delegate: SelectLocationMapViewModelDelegate? = nil,
 		 linkNavigation: LinkNavigation = LinkNavigationHelper()) {
 		// ... existing initialization ...
-		Task { @MainActor in
+		explorerDataTask = Task { @MainActor in
 			await getExplorerData()
 		}
 	}
+
+	deinit {
+		explorerDataTask?.cancel()
+	}

Comment on lines +272 to +301
func updateLocationPointedAnnotations(_ point: CGPoint) {
guard let polygonManager else {
return
}
let layerIds = [polygonManager.layerId]
let annotations = polygonManager.annotations
let options = RenderedQueryOptions(layerIds: layerIds, filter: nil)
mapView.mapboxMap.queryRenderedFeatures(with: point, options: options) { [weak self] result in
switch result {
case .success(let features):
let tappedAnnotations = annotations.filter { annotation in
features.contains(where: { feature in
guard let featureid = feature.queriedFeature.feature.identifier else {
return false
}
switch featureid {
case .string(let str):
return annotation.id == str
case .number(_):
return false
}
})
}

self?.delegate?.didPointAnnotations(tappedAnnotations)
case .failure(let error):
print(error)
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix feature ID matching to handle numeric identifiers.

The current implementation only handles string feature IDs and returns false for numeric IDs without checking them. This could cause annotations with numeric IDs to be missed.

Apply this diff to properly handle both ID types:

 				let tappedAnnotations = annotations.filter { annotation in
 					features.contains(where: { feature in
-						guard let featureid = feature.queriedFeature.feature.identifier else {
+						guard let featureId = feature.queriedFeature.feature.identifier else {
 							return false
 						}
-						switch featureid {
+						switch featureId {
 							case .string(let str):
 								return annotation.id == str
-							case .number(_):
-								return false
+							case .number(let num):
+								return annotation.id == String(describing: num)
 						}
 					})
 				}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func updateLocationPointedAnnotations(_ point: CGPoint) {
guard let polygonManager else {
return
}
let layerIds = [polygonManager.layerId]
let annotations = polygonManager.annotations
let options = RenderedQueryOptions(layerIds: layerIds, filter: nil)
mapView.mapboxMap.queryRenderedFeatures(with: point, options: options) { [weak self] result in
switch result {
case .success(let features):
let tappedAnnotations = annotations.filter { annotation in
features.contains(where: { feature in
guard let featureid = feature.queriedFeature.feature.identifier else {
return false
}
switch featureid {
case .string(let str):
return annotation.id == str
case .number(_):
return false
}
})
}
self?.delegate?.didPointAnnotations(tappedAnnotations)
case .failure(let error):
print(error)
}
}
}
func updateLocationPointedAnnotations(_ point: CGPoint) {
guard let polygonManager else {
return
}
let layerIds = [polygonManager.layerId]
let annotations = polygonManager.annotations
let options = RenderedQueryOptions(layerIds: layerIds, filter: nil)
mapView.mapboxMap.queryRenderedFeatures(with: point, options: options) { [weak self] result in
switch result {
case .success(let features):
let tappedAnnotations = annotations.filter { annotation in
features.contains(where: { feature in
guard let featureId = feature.queriedFeature.feature.identifier else {
return false
}
switch featureId {
case .string(let str):
return annotation.id == str
case .number(let num):
return annotation.id == String(describing: num)
}
})
}
self?.delegate?.didPointAnnotations(tappedAnnotations)
case .failure(let error):
print(error)
}
}
}
🧰 Tools
🪛 GitHub Check: swiftLint

[warning] 290-290:
Empty Enum Arguments Violation: Arguments can be omitted when matching enums with associated values if they are not used (empty_enum_arguments)

🤖 Prompt for AI Agents
In PresentationLayer/UIComponents/MapBox/MapBoxClaimDevice.swift around lines
272 to 301, the feature ID matching only handles string IDs and ignores numeric
IDs; update the matching so numeric identifiers are also compared to
annotation.id by converting the numeric ID to a string (or otherwise serializing
it to the same format used by annotation.id) before comparing, i.e., handle both
.string and .number cases by deriving a string representation for the .number
case and using that for equality, then return the match result so
numeric-ID-backed annotations are detected correctly.

Comment on lines +311 to +334
func setPolygonManagers() {
self.polygonManager = mapView.annotations.makePolygonAnnotationManager(id: MapBoxConstants.cellCapacityPolygonManagerId)
self.polylineManager = mapView.annotations.makePolylineAnnotationManager(id: MapBoxConstants.bordersManagerId)

let pointAnnotationManager = mapView.annotations.makePointAnnotationManager(id: MapBoxConstants.pointManagerId)
pointManager = pointAnnotationManager

try? mapView.mapboxMap.updateLayer(withId: MapBoxConstants.pointManagerId, type: SymbolLayer.self) { layer in
layer.minZoom = 10

let stops: [Double: Double] = [
10: CGFloat(.mediumFontSize),
12: CGFloat(.XLTitleFontSize),
16: CGFloat(.maxFontSize)
]

layer.textSize = .expression(Exp(.interpolate) {
Exp(.exponential) { 1.75 }
Exp(.zoom)
stops
})
layer.textColor = .constant(StyleColor(UIColor(colorEnum: .textWhite)))
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace force-try with proper error handling.

The try? on line 318 silently ignores layer update failures, which could lead to incorrect map rendering without any indication of what went wrong. Layer updates can fail for various reasons (layer not found, invalid configuration, etc.).

Apply this diff to add proper error handling:

-	try? mapView.mapboxMap.updateLayer(withId: MapBoxConstants.pointManagerId, type: SymbolLayer.self) { layer in
+	do {
+		try mapView.mapboxMap.updateLayer(withId: MapBoxConstants.pointManagerId, type: SymbolLayer.self) { layer in
-		layer.minZoom = 10
-
-		let stops: [Double: Double] = [
-			10: CGFloat(.mediumFontSize),
-			12: CGFloat(.XLTitleFontSize),
-			16: CGFloat(.maxFontSize)
-		]
-
-		layer.textSize = .expression(Exp(.interpolate) {
-			Exp(.exponential) { 1.75 }
-			Exp(.zoom)
-			stops
-		})
-		layer.textColor = .constant(StyleColor(UIColor(colorEnum: .textWhite)))
-	}
+			layer.minZoom = 10
+
+			let stops: [Double: Double] = [
+				10: CGFloat(.mediumFontSize),
+				12: CGFloat(.XLTitleFontSize),
+				16: CGFloat(.maxFontSize)
+			]
+
+			layer.textSize = .expression(Exp(.interpolate) {
+				Exp(.exponential) { 1.75 }
+				Exp(.zoom)
+				stops
+			})
+			layer.textColor = .constant(StyleColor(UIColor(colorEnum: .textWhite)))
+		}
+	} catch {
+		print("Failed to configure point annotation layer: \(error)")
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func setPolygonManagers() {
self.polygonManager = mapView.annotations.makePolygonAnnotationManager(id: MapBoxConstants.cellCapacityPolygonManagerId)
self.polylineManager = mapView.annotations.makePolylineAnnotationManager(id: MapBoxConstants.bordersManagerId)
let pointAnnotationManager = mapView.annotations.makePointAnnotationManager(id: MapBoxConstants.pointManagerId)
pointManager = pointAnnotationManager
try? mapView.mapboxMap.updateLayer(withId: MapBoxConstants.pointManagerId, type: SymbolLayer.self) { layer in
layer.minZoom = 10
let stops: [Double: Double] = [
10: CGFloat(.mediumFontSize),
12: CGFloat(.XLTitleFontSize),
16: CGFloat(.maxFontSize)
]
layer.textSize = .expression(Exp(.interpolate) {
Exp(.exponential) { 1.75 }
Exp(.zoom)
stops
})
layer.textColor = .constant(StyleColor(UIColor(colorEnum: .textWhite)))
}
}
func setPolygonManagers() {
self.polygonManager = mapView.annotations.makePolygonAnnotationManager(id: MapBoxConstants.cellCapacityPolygonManagerId)
self.polylineManager = mapView.annotations.makePolylineAnnotationManager(id: MapBoxConstants.bordersManagerId)
let pointAnnotationManager = mapView.annotations.makePointAnnotationManager(id: MapBoxConstants.pointManagerId)
pointManager = pointAnnotationManager
do {
try mapView.mapboxMap.updateLayer(withId: MapBoxConstants.pointManagerId, type: SymbolLayer.self) { layer in
layer.minZoom = 10
let stops: [Double: Double] = [
10: CGFloat(.mediumFontSize),
12: CGFloat(.XLTitleFontSize),
16: CGFloat(.maxFontSize)
]
layer.textSize = .expression(Exp(.interpolate) {
Exp(.exponential) { 1.75 }
Exp(.zoom)
stops
})
layer.textColor = .constant(StyleColor(UIColor(colorEnum: .textWhite)))
}
} catch {
print("Failed to configure point annotation layer: \(error)")
}
}
🤖 Prompt for AI Agents
In PresentationLayer/UIComponents/MapBox/MapBoxClaimDevice.swift around lines
311 to 334, replace the silent `try? mapView.mapboxMap.updateLayer(...)` with
explicit error handling: wrap the updateLayer call in a do-catch, call `try`
inside the do block, and in the catch block log the error (using your app logger
or print/OSLog) with a clear contextual message including the layer id and the
thrown error; ensure the catch also contains any minimal recovery or fallback
(e.g., skip styling or mark a flag) so failures don’t go unnoticed.

Comment thread wxm-ios/DataLayer/ExplorerService.swift
Comment on lines 4539 to 4542
"stringUnit" : {
"state" : "translated",
"value" : "Cell capacity is a parameter that is used to define the maximum number of stations that are eligible for rewards in a specific cell. Every cell has a predefined capacity that depends on its geospatial characteristics*.\n\nEvery station is ranked in its cell daily, based on its reward score and its seniority. As long as the station's ranking is above the capacity threshold, it will be rewarded, whereas getting below it will lead to zero rewards. For example, in a cell with a max capacity of 5 stations, if a station is ranked 3rd it will get rewarded, but if it is ranked 7th, it won’t receive any rewards.\n\nRead more about how our Cell Capacity algorithm works.\n\n* The cell capacity is currently set to the fixed value of 10 rewardable stations, for every cell."
"value" : "Cell capacity is a parameter that is used to define the maximum number of stations that are eligible for rewards in a specific cell. Every cell has a predefined capacity that depends on its geospatial characteristics*.\n\nEvery station is ranked in its cell daily, based on its reward score and its seniority. As long as the station's ranking is above the capacity threshold, it will be rewarded, whereas getting below it will lead to zero rewards. For example, in a cell with a max capacity of 5 stations, if a station is ranked 3rd it will get rewarded, but if it is ranked 7th, it won’t receive any rewards.\n\nRead more about how our Cell Capacity algorithm works."
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove stray asterisk in the description.

geospatial characteristics*. still contains the asterisk but the explanatory footnote was dropped in this revision, so the text now points to nothing. Please either restore the note or drop the asterisk to avoid confusing readers.

🤖 Prompt for AI Agents
In wxm-ios/Resources/Localizable/Localizable.xcstrings around lines 4539 to
4542, the sentence "geospatial characteristics*." contains a stray asterisk that
points to a removed footnote; remove the asterisk from the string value (or if
the explanatory note must remain, restore the footnote text and reference) so
the localized string no longer references a missing footnote and reads cleanly.

Comment on lines 8708 to 8712
"stringUnit" : {
"state" : "translated",
"value" : "Cell ranking is a measure of a station's performance relative to others within the same cell - a hexagonal area, covering approximately 5 square kilometers (2 square miles) on the planet.\n\nCell capacity is a parameter that is used to define the maximum number of stations that are eligible for rewards in a specific cell. Every cell has a predefined capacity that depends on its geospatial characteristics*.\n\nEvery station is ranked in its cell daily, based on its reward score and its seniority. As long as the station's ranking is above the capacity threshold, it will be rewarded, whereas getting below it will lead to zero rewards. For example, in a cell with a max capacity of 5 stations, if a station is ranked 3rd it will get rewarded, but if it is ranked 7th, it won’t receive any rewards.\n\nRead more about how our Cell Capacity algorithm works.

* The cell capacity is currently set to the fixed value of 10 rewardable stations, for every cell."
"value" : "Cell ranking is a measure of a station's performance relative to others within the same cell - a hexagonal area, covering approximately 5 square kilometers (2 square miles) on the planet.\n\nCell capacity is a parameter that is used to define the maximum number of stations that are eligible for rewards in a specific cell. Every cell has a predefined capacity that depends on its geospatial characteristics*.\n\nEvery station is ranked in its cell daily, based on its reward score and its seniority. As long as the station's ranking is above the capacity threshold, it will be rewarded, whereas getting below it will lead to zero rewards. For example, in a cell with a max capacity of 5 stations, if a station is ranked 3rd it will get rewarded, but if it is ranked 7th, it won’t receive any rewards.\n\nRead more about how our Cell Capacity algorithm works."
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Drop the orphan footnote marker.

Same issue here: geospatial characteristics*. includes an asterisk whose footnote is no longer present. Clean it up or bring the note back so users aren’t left wondering what “*” refers to.

🤖 Prompt for AI Agents
In wxm-ios/Resources/Localizable/Localizable.xcstrings around lines 8708-8712,
the localized string contains a stray footnote marker "geospatial
characteristics*." with no corresponding note; remove the asterisk (or re-add
the missing footnote text if intended) and adjust punctuation so the sentence
reads “geospatial characteristics.”; update the localized value accordingly
ensuring no orphaned markers remain.

Copy link
Copy Markdown

@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: 1

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

33-43: Verify if pill count relates to cellCapacity.

The test expects 2 pills when cellCapacity: 2. If this relationship is intentional (e.g., pills derived from capacity), consider adding a test case with a different cellCapacity value to verify the correlation explicitly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f029606 and 1f885e9.

📒 Files selected for processing (3)
  • WeatherXMTests/PresentationLayer/ViewModels/ExplorerStationsListViewModelTests.swift (1 hunks)
  • WeatherXMTests/PresentationLayer/ViewModels/OverviewViewModelTests.swift (1 hunks)
  • wxm-ios/DataLayer/ExplorerService.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wxm-ios/DataLayer/ExplorerService.swift
⏰ 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 (2)
WeatherXMTests/PresentationLayer/ViewModels/ExplorerStationsListViewModelTests.swift (2)

19-25: LGTM! Initialization updated correctly.

The viewModel initialization now includes the required cellCapacity parameter, correctly aligning with the updated initializer signature.


27-31: LGTM! Cell capacity info test is well-structured.

The test properly verifies the info display toggle functionality for the cell capacity feature.

init() {
delegate = StationDetailsContainerDelegate()
viewModel = OverviewViewModel(device: DeviceDetails.mockDevice, linkNavigation: linkNavigator)
viewModel = OverviewViewModel(device: DeviceDetails.mockDevice, linkNavigation: linkNavigator, explorerUseCase: MockExplorerUseCase())
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add test coverage for the new explorerUseCase dependency.

The test now passes MockExplorerUseCase() to satisfy the updated constructor, but none of the tests verify capacity-related behavior. Given that this PR introduces cell capacity features end-to-end, consider adding tests that verify:

  • Cell capacity data retrieval via the explorer use case
  • Capacity-based navigation logic
  • Error handling when capacity fetching fails
  • Any capacity-related alerts or UI state changes

Without these tests, regressions in capacity functionality could go undetected.

Would you like me to suggest test cases or help draft test implementations for the capacity-related scenarios?

🤖 Prompt for AI Agents
In WeatherXMTests/PresentationLayer/ViewModels/OverviewViewModelTests.swift
around line 46, the test instantiates OverviewViewModel with MockExplorerUseCase
but does not assert any capacity-related behavior; add tests that (1) configure
MockExplorerUseCase to return valid capacity data and assert the viewModel
exposes that data/state, (2) configure it to return capacity that should trigger
capacity-based navigation and assert the linkNavigator is invoked with the
expected route/parameters, (3) configure it to fail and assert the viewModel
emits an error state or alert and no navigation occurs, and (4) verify any UI
state changes (loading, empty, populated) during fetch; implement these by
extending the mock to inject success/failure responses, calling the viewModel
fetch methods, awaiting async updates, and making assertions on published
properties and mock navigator call history.

@weatherxmdev
Copy link
Copy Markdown
Collaborator

Code Coverage Summary

Framework Source Files Coverage
DataLayer.framework 49 66.77%
DomainLayer.framework 66 83.76%
Toolkit.framework 30 53.18%
WeatherXM.app 404 14.17%
station-intent.appex 2 83.33%
station-widgetExtension.appex 45 33.94%

@pantelisss pantelisss merged commit cabeec4 into main Oct 15, 2025
4 checks passed
@pantelisss pantelisss deleted the feature/cell_capacity_device_proximity branch October 15, 2025 11:02
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