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

[SSDK-399] Add Discover.Options(country, proximity, origin) #167

Merged
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Guide: https://keepachangelog.com/en/1.0.0/

<!-- Add changes for active work here -->

- [Discover] Add support for country, proximity, and origin parameters in Discover.Options search parameters. This fixes an issue when using search-along-route to query category results.

- [SearchUI] Add `distanceFormatter` field to Configuration to support changing the search suggestions distance format. Nil values will use the default behavior.

- [Core] Add xcprivacy for MapboxSearch and MapboxSearchUI
Expand Down
12 changes: 12 additions & 0 deletions MapboxSearch.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
042477C62B72CCB000D870D5 /* geocoding-reverse-geocoding.json in Resources */ = {isa = PBXBuildFile; fileRef = 042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */; };
042477C72B72CCB000D870D5 /* geocoding-reverse-geocoding.json in Resources */ = {isa = PBXBuildFile; fileRef = 042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */; };
043A3D4D2B30F38300DB681B /* CoreAddress+AddressComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043A3D4C2B30F38300DB681B /* CoreAddress+AddressComponents.swift */; };
048823482B6B0A9D00C770AA /* category-hotel-search-along-route-jp.json in Resources */ = {isa = PBXBuildFile; fileRef = 04AB0B7C2B6B043C00FDE7D5 /* category-hotel-search-along-route-jp.json */; };
048823492B6B0A9D00C770AA /* category-hotel-search-along-route-jp.json in Resources */ = {isa = PBXBuildFile; fileRef = 04AB0B7C2B6B043C00FDE7D5 /* category-hotel-search-along-route-jp.json */; };
0488234A2B6B0A9E00C770AA /* category-hotel-search-along-route-jp.json in Resources */ = {isa = PBXBuildFile; fileRef = 04AB0B7C2B6B043C00FDE7D5 /* category-hotel-search-along-route-jp.json */; };
04AB0B7B2B6AF43E00FDE7D5 /* DiscoverIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04AB0B792B6AF37800FDE7D5 /* DiscoverIntegrationTests.swift */; };
04970F8D2B7A97C900213763 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 04970F8C2B7A97C900213763 /* PrivacyInfo.xcprivacy */; };
04AB0B4B2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json in Resources */ = {isa = PBXBuildFile; fileRef = 04AB0B4A2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json */; };
04AB0B4C2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json in Resources */ = {isa = PBXBuildFile; fileRef = 04AB0B4A2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json */; };
Expand Down Expand Up @@ -494,6 +498,8 @@
042477C12B7290E700D870D5 /* SearchEngineGeocodingIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchEngineGeocodingIntegrationTests.swift; sourceTree = "<group>"; };
042477C42B72CCB000D870D5 /* geocoding-reverse-geocoding.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "geocoding-reverse-geocoding.json"; sourceTree = "<group>"; };
043A3D4C2B30F38300DB681B /* CoreAddress+AddressComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoreAddress+AddressComponents.swift"; sourceTree = "<group>"; };
04AB0B792B6AF37800FDE7D5 /* DiscoverIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverIntegrationTests.swift; sourceTree = "<group>"; };
04AB0B7C2B6B043C00FDE7D5 /* category-hotel-search-along-route-jp.json */ = {isa = PBXFileReference; explicitFileType = text.json; path = "category-hotel-search-along-route-jp.json"; sourceTree = "<group>"; };
04970F8C2B7A97C900213763 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
04AB0B4A2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = mapbox.places.san.francisco.json; sourceTree = "<group>"; };
04BBC6332B61898F00E24E99 /* LocalhostMockServiceProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalhostMockServiceProvider.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1400,6 +1406,7 @@
2CE1B9FA2A13D412005B043F /* address-retrieve-san-francisco.json */,
2CE1B9F92A13D412005B043F /* address-suggestions-san-francisco.json */,
2CD8AC5129F28D6900C47BE4 /* retrieve-poi.json */,
04AB0B7C2B6B043C00FDE7D5 /* category-hotel-search-along-route-jp.json */,
2CD8AC4029F1D38800C47BE4 /* suggestions-category-with-coordinates.json */,
2CD8AC4129F1D38800C47BE4 /* suggestions-with-coordinates.json */,
2CD8AC4229F1D38800C47BE4 /* suggestions-with-mixed-coordinates.json */,
Expand Down Expand Up @@ -1521,6 +1528,7 @@
F9ACA6162642C18200F50CD4 /* SearchEngineIntegrationTests.swift */,
042477C12B7290E700D870D5 /* SearchEngineGeocodingIntegrationTests.swift */,
F99190422645ABE6009927A6 /* CategorySearchEngineIntegrationTests.swift */,
04AB0B792B6AF37800FDE7D5 /* DiscoverIntegrationTests.swift */,
F9C5572C2670C88E00BE8B94 /* Info.plist */,
);
path = MapboxSearchIntegrationTests;
Expand Down Expand Up @@ -2104,6 +2112,7 @@
042477C62B72CCB000D870D5 /* geocoding-reverse-geocoding.json in Resources */,
2CD8AC4429F1D38800C47BE4 /* suggestions-category-with-coordinates.json in Resources */,
F9B62CCB264BCC2600492999 /* suggestions-empty.json in Resources */,
048823492B6B0A9D00C770AA /* category-hotel-search-along-route-jp.json in Resources */,
04AB0B4C2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json in Resources */,
F9ACA6182642C48C00F50CD4 /* recursion.json in Resources */,
F9B62CC8264BC61600492999 /* category-cafe.json in Resources */,
Expand Down Expand Up @@ -2134,6 +2143,7 @@
F907440F261B00000091899C /* suggestions-san-francisco.json in Resources */,
F94FFA4826453D410019ED9B /* reverse-geocoding-sbs.json in Resources */,
2CD8AC4B29F1D38800C47BE4 /* suggestions-with-mixed-coordinates.json in Resources */,
0488234A2B6B0A9E00C770AA /* category-hotel-search-along-route-jp.json in Resources */,
2CD8AC5429F28D6900C47BE4 /* retrieve-poi.json in Resources */,
042477C72B72CCB000D870D5 /* geocoding-reverse-geocoding.json in Resources */,
F9074425261B0DF70091899C /* retrieve-san-francisco.json in Resources */,
Expand All @@ -2154,6 +2164,7 @@
042477C52B72CCB000D870D5 /* geocoding-reverse-geocoding.json in Resources */,
2CD8AC4329F1D38800C47BE4 /* suggestions-category-with-coordinates.json in Resources */,
F9C557A52670CB0400BE8B94 /* suggestions-empty.json in Resources */,
048823482B6B0A9D00C770AA /* category-hotel-search-along-route-jp.json in Resources */,
04AB0B4B2B6AADB700FDE7D5 /* mapbox.places.san.francisco.json in Resources */,
F9C557A62670CB0400BE8B94 /* recursion.json in Resources */,
F9C557A72670CB0400BE8B94 /* category-cafe.json in Resources */,
Expand Down Expand Up @@ -2670,6 +2681,7 @@
F9C557C12670CD8C00BE8B94 /* CoreSearchOptions+Samples.swift in Sources */,
1420F31C29A2800300D4A511 /* CoreSearchResultStub+Samples.swift in Sources */,
F9C557B72670CC5600BE8B94 /* CoreSearchResultStub.swift in Sources */,
04AB0B7B2B6AF43E00FDE7D5 /* DiscoverIntegrationTests.swift in Sources */,
F9C557BF2670CD4300BE8B94 /* CoreRequestOptions+Samples.swift in Sources */,
F9C557BB2670CCC000BE8B94 /* SearchResultType+Extensions.swift in Sources */,
F9C557BA2670CCAB00BE8B94 /* TestDataProviderRecord.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Foundation
public struct Country: Equatable {
public let countryCode: String

/// Country model initializier
/// Country model initializer
/// - Parameter countryCode: Permitted values are ISO 3166-1 alpha 2 country codes (e.g. US, DE, GB)
public init?(countryCode: String) {
guard ISO3166_1_alpha2(rawValue: countryCode.uppercased()) != nil else {
Expand All @@ -12,4 +12,23 @@ public struct Country: Equatable {

self.countryCode = countryCode.lowercased()
}

init(code: ISO3166_1_alpha2) {
self.countryCode = code.rawValue.lowercased()
}

/// Detect the system region ISO3166\_1\_Alpha2 identifier and return an instance for it
static var `default`: Self? {
if #available(iOS 16, *) {
return Country(countryCode: Locale.current.region?.identifier ?? "")
} else {
let regionComponents = Locale.current.identifier.components(separatedBy: "_")
if regionComponents.count >= 2 {
let countryIdentifier = regionComponents[1]
Copy link
Contributor

Choose a reason for hiding this comment

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

Hey @aokj4ck is countryIdenitifier only the last component in regionComponents or it is just the second item all the time?

just smal nitpick from my side: is it possible to use regionComponents?.last or regionComponents?.first to leave all these checks on compiler shoulders? Instead to access item via index in array

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great question, it is the second item all the time and there may be more items.

For example if you:

  1. Add an iOS 15 simulator (oldest simulator available in Xcode 14 and that uses this API)
  2. Change the Demo app > Edit Scheme > Run > Options > App Language and App Region
  3. Set a breakpoint on this line
  4. Build and run
  5. Go to Discover tab and tap "Search in this area"

You will find that there are scenarios where the locale has more components such as Language = Chinese (Hong Kong) and Region = Hong Kong will return the locale "en_HK_HK". There are various other examples and accessing the second items in the regionComponents is safest to cover all scenarios.

Screenshot 2024-02-14 at 10 16 17 Screenshot 2024-02-14 at 10 18 44

return Country(countryCode: countryIdentifier)
} else {
return nil
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,11 @@ extension Discover {
userActivityReporter.reportActivity(forComponent: "discover-search-nearby")

let searchOptions = SearchOptions(
countries: [options.country?.countryCode].compactMap { $0 },
languages: [options.language.languageCode],
limit: options.limit,
proximity: proximity
proximity: proximity,
origin: options.origin
)

search(for: query, with: searchOptions, completion: completion)
Expand All @@ -85,10 +87,12 @@ extension Discover {
userActivityReporter.reportActivity(forComponent: "discover-search-in-area")

let searchOptions = SearchOptions(
countries: [options.country?.countryCode].compactMap { $0 },
languages: [options.language.languageCode],
limit: options.limit,
proximity: proximity,
boundingBox: region
boundingBox: region,
origin: options.origin
)

search(for: query, with: searchOptions, completion: completion)
Expand All @@ -110,8 +114,11 @@ extension Discover {
userActivityReporter.reportActivity(forComponent: "discover-search-along-the-route")

let searchOptions = SearchOptions(
countries: [options.country?.countryCode].compactMap { $0 },
languages: [options.language.languageCode],
limit: options.limit,
proximity: options.proximity,
origin: options.origin,
routeOptions: route
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,27 @@ extension Discover {
/// English (en) language parameter, but Frankreich (“France”) with a German (de) language parameter.
public let language: Language

/// See ``MapboxSearch.Country.ISO3166_1_alpha2`` for the list of ISO 3166 alpha 2 country codes.
/// The default value will be selected from the Country.ISO3166\_1\_alpha2 identifiers based on the current
/// locale identifier or nil if no match is found.
public let country: Country?

public let proximity: CLLocationCoordinate2D?

public let origin: CLLocationCoordinate2D?

public init(
limit: Int = 10,
language: Language? = nil
language: Language? = nil,
country: Country? = nil,
proximity: CLLocationCoordinate2D? = nil,
origin: CLLocationCoordinate2D? = nil
) {
self.limit = limit
self.language = language ?? .default
self.country = country ?? .default
aokj4ck marked this conversation as resolved.
Show resolved Hide resolved
self.proximity = proximity
self.origin = origin
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import CoreLocation
@testable import MapboxSearch
import XCTest

class DiscoverIntegrationTests: MockServerIntegrationTestCase {
lazy var searchEngine = Discover(locationProvider: DefaultLocationProvider())

func testCategorySearchAlongRouteWithCountryProximityOrigin() throws {
try server.setResponse(.categoryHotelSearchAlongRoute_JP)
let expectation = XCTestExpectation(description: "Expecting results")

let coordinate1 = CLLocationCoordinate2D(latitude: 35.655614, longitude: 139.7081684)
let coordinate2 = CLLocationCoordinate2D(latitude: 35.6881616, longitude: 139.6994339)
let coordinates = [coordinate1, coordinate2]

let mapboxSearchRoute = MapboxSearch.Route(coordinates: coordinates)
let rOptions: MapboxSearch.RouteOptions = RouteOptions(route: mapboxSearchRoute, time: 1000)

let discoverOptions = Discover.Options(
limit: 10,
language: nil,
country: Country(countryCode: "jp"),
proximity: CLLocationCoordinate2D(
latitude: 35.6634363,
longitude: 139.7394536
),
origin: CLLocationCoordinate2D(latitude: 35.66580, longitude: 139.74609)
)

searchEngine.search(
for: Discover.Query.Category.canonicalName("hotel"),
route: rOptions,
options: discoverOptions
) { result in
switch result {
case .success(let searchResults):
XCTAssertFalse(searchResults.isEmpty)
expectation.fulfill()
case .failure:
XCTFail("Error not expected")
}
expectation.fulfill()
}
wait(for: [expectation], timeout: 10)
}
}
14 changes: 14 additions & 0 deletions Tests/MapboxSearchUITests/MockWebServer/MockResponse.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import Foundation

enum MockResponse {
enum Endpoint: String {
case suggest
case retrieve
case reverse
case multiRetrieve = "retrieve/multi"
case categoryCafe = "cafe"
case categoryHotel = "hotel"
case addressSuggest = "autofill/suggest"
case addressRetrieve = "autofill/retrieve"
}

case suggestEmpty
case suggestMinsk
case suggestSanFrancisco
Expand All @@ -22,6 +33,7 @@ enum MockResponse {
case reverseGeocoding
case reverseGeocodingSBS
case categoryCafe
case categoryHotelSearchAlongRoute_JP

var filepath: String {
let bundle = Bundle(for: MockWebServer.self)
Expand Down Expand Up @@ -60,6 +72,8 @@ enum MockResponse {
return bundle.path(forResource: "retrieve-multi", ofType: "json")!
case .categoryCafe:
return bundle.path(forResource: "category-cafe", ofType: "json")!
case .categoryHotelSearchAlongRoute_JP:
return bundle.path(forResource: "category-hotel-search-along-route-jp", ofType: "json")!
case .suggestAddressSanFrancisco:
return bundle.path(forResource: "address-suggestions-san-francisco", ofType: "json")!
case .retrieveAddressSanFrancisco:
Expand Down
5 changes: 3 additions & 2 deletions Tests/MapboxSearchUITests/MockWebServer/MockWebServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ extension MockWebServer {
.recursion,
.reverseGeocoding,
.reverseGeocodingSBS,
.categoryCafe:
.categoryCafe,
.categoryHotelSearchAlongRoute_JP:
return .get

case .multiRetrieve,
Expand Down Expand Up @@ -127,7 +128,7 @@ extension MockWebServer {
case .multiRetrieve:
path += "/retrieve/multi"

case .categoryCafe:
case .categoryCafe, .categoryHotelSearchAlongRoute_JP:
path += "/category/:category"
}

Expand Down
Loading