From 5f5ade455968e9c8cc0221e3f4a5ddf8a70e336b Mon Sep 17 00:00:00 2001 From: Jonathan Ha <78988736+jonha1@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:48:58 -0400 Subject: [PATCH] Creating more filter options (#101) * Add categories for places nearby Co-authored-by: Daniel W. Steinbrook --- apps/ios/GuideDogs.xcodeproj/project.pbxproj | 4 ++ .../en-GB.lproj/Localizable.strings | Bin 257390 -> 257482 bytes .../en-US.lproj/Localizable.strings | 63 ++++++++++++++++-- .../GDASpatialDataResultEntity+Typeable.swift | 55 +++++++++++++-- .../Models/Helpers/Types/PrimaryType.swift | 4 ++ .../Models/Helpers/Types/SecondaryType.swift | 4 ++ .../Nearby Table/NearbyTableFilter.swift | 18 ++++- .../NearbyTableFilterTest.swift | 63 ++++++++++++++++++ 8 files changed, 199 insertions(+), 12 deletions(-) create mode 100644 apps/ios/UnitTests/Data/Destination Manager/NearbyTableFilterTest.swift diff --git a/apps/ios/GuideDogs.xcodeproj/project.pbxproj b/apps/ios/GuideDogs.xcodeproj/project.pbxproj index a0f47a49..0308756c 100644 --- a/apps/ios/GuideDogs.xcodeproj/project.pbxproj +++ b/apps/ios/GuideDogs.xcodeproj/project.pbxproj @@ -669,6 +669,7 @@ 91C82AAD2A5DCF040086D126 /* GeolocationManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91C82AAC2A5DCF040086D126 /* GeolocationManagerTest.swift */; }; 91C82ABE2A6B08500086D126 /* RouteGuidanceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91C82ABD2A6B08500086D126 /* RouteGuidanceTest.swift */; }; 91DC0CF92A46134600244CC8 /* GeometryUtilsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914DEBDC2A3CE901007B161C /* GeometryUtilsTest.swift */; }; + AC3A3EE22C40C9080061EEB8 /* NearbyTableFilterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3A3EE12C40C9080061EEB8 /* NearbyTableFilterTest.swift */; }; B90C27D61EAF81D600007368 /* Sound.swift in Sources */ = {isa = PBXBuildFile; fileRef = B90C27D51EAF81D600007368 /* Sound.swift */; }; B918EE9825100FFF00A5354A /* CalloutRangeContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = B918EE9725100FFF00A5354A /* CalloutRangeContext.swift */; }; B91D3F6427AB5546004159A8 /* UserAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B91D3F6327AB5546004159A8 /* UserAction.swift */; }; @@ -1587,6 +1588,7 @@ 915FF9F42ADE3F91002B3690 /* AuthoredActivityContentTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthoredActivityContentTest.swift; sourceTree = ""; }; 91C82AAC2A5DCF040086D126 /* GeolocationManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = " GeolocationManagerTest.swift"; path = "UnitTests/Sensors/Geolocation/Geolocation Manager/ GeolocationManagerTest.swift"; sourceTree = SOURCE_ROOT; }; 91C82ABD2A6B08500086D126 /* RouteGuidanceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RouteGuidanceTest.swift; path = "UnitTests/Behaviors/Route Guidance/RouteGuidanceTest.swift"; sourceTree = SOURCE_ROOT; }; + AC3A3EE12C40C9080061EEB8 /* NearbyTableFilterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyTableFilterTest.swift; sourceTree = ""; }; B90C27D51EAF81D600007368 /* Sound.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Sound.swift; path = Code/Audio/Protocols/Sound.swift; sourceTree = ""; }; B918EE9725100FFF00A5354A /* CalloutRangeContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalloutRangeContext.swift; sourceTree = ""; }; B91D3F6327AB5546004159A8 /* UserAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAction.swift; sourceTree = ""; }; @@ -4283,6 +4285,7 @@ isa = PBXGroup; children = ( 914BAAF22AD745E400CB2171 /* DestinationManagerTest.swift */, + AC3A3EE12C40C9080061EEB8 /* NearbyTableFilterTest.swift */, ); path = "Destination Manager"; sourceTree = ""; @@ -5545,6 +5548,7 @@ 914BAAFD2AD7483300CB2171 /* AudioEngineTest.swift in Sources */, 914BAAF32AD745E400CB2171 /* DestinationManagerTest.swift in Sources */, 91C82ABE2A6B08500086D126 /* RouteGuidanceTest.swift in Sources */, + AC3A3EE22C40C9080061EEB8 /* NearbyTableFilterTest.swift in Sources */, 91C82AAD2A5DCF040086D126 /* GeolocationManagerTest.swift in Sources */, 91DC0CF92A46134600244CC8 /* GeometryUtilsTest.swift in Sources */, ); diff --git a/apps/ios/GuideDogs/Assets/Localization/en-GB.lproj/Localizable.strings b/apps/ios/GuideDogs/Assets/Localization/en-GB.lproj/Localizable.strings index eaaa9b4349b9cba604dcce8736e0f6c7839572d8..73bd2eb8964ee2e829dce12ce212b913674ac8a2 100644 GIT binary patch delta 106 zcmaENi~rPZ{)R1#9A@(R3|b5d3~mgW3^@!X45mCaB*JLJ5#)oPcUWVX>T)Q1Y)M`ZD!0a+W`cl4I2Oe diff --git a/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings b/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings index a95174bc..fb7e6a50 100644 --- a/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings +++ b/apps/ios/GuideDogs/Assets/Localization/en-US.lproj/Localizable.strings @@ -1533,6 +1533,22 @@ /* Filters, Transit */ "filter.transit" = "Public Transit"; +/* Filters, Food & Drink */ +"filter.food_drink" = "Food & Drink"; + +/* Filters, Parks */ +"filter.parks" = "Parks"; + +/* Filter, Things To Do */ +"filter.things_to_do" = "Things to do"; + +/* Filters, Groceries & Convenience Stores */ +"filter.groceries" = "Groceries & Convenience Stores"; + +/* Filters, Banks & ATMs */ +"filter.banks" = "Banks & ATMs"; + + /* Displayed next to the name of the filter to indicate that the filter is currently selected. %@ is the name of a filter */ "filter.selected" = "%@ (selected)"; @@ -3214,12 +3230,6 @@ /* Open Street Map term. Refers to any parking space designed for bicycles, where one can leave a pedal cycle unattended in reasonable security. */ "osm.tag.bike_parking" = "Bike Parking"; -/* Open Street Map term. An informal place with sit-down facilities selling beverages and light meals and/or snacks. */ -"osm.tag.cafe" = "Cafe"; - -/* Open Street Map term. Generally a formal eating places with sit-down facilities selling full meals served by waiters and often licensed (where allowed) to sell alcoholic drinks. */ -"osm.tag.restaurant" = "Restaurant"; - /* Open Street Map term. A public telephone, phone box, or telephone on a stand or wall. Usually you pay to use them, often only via a pre-pay card. */ "osm.tag.telephone" = "Telephone"; @@ -3240,10 +3250,42 @@ /* Open Street Map term. A bus stop is a place where passengers can board or alight from a bus. Its position may be marked by a shelter, pole, bus lay-by, or road markings. */ "osm.tag.bus_stop" = "Bus Stop"; +/* Open Street Map term. A bus stop is a place where passengers can go to a landmark */ +"osm.tag.landmark" = "Landmark"; /* Open Street Map term. Used as a display name for place or a location. %@ is a bus stop name, such as "London Bus Stop". */ "osm.tag.bus_stop.named" = "%@ Bus Stop"; +/* Open Street Map term. An informal place with sit-down facilities selling beverages and light meals and/or snacks. */ +"osm.tag.cafe" = "Cafe"; + +/* Open Street Map term. Generally a formal eating places with sit-down facilities selling full meals served by waiters and often licensed (where allowed) to sell alcoholic drinks. */ +"osm.tag.restaurant" = "restaurant"; + +/* Open Street Map term. Fast food is a place where food that is prepared and served very quickly. */ +"osm.tag.fast_food" = "fast_food"; + +/* Open Street Map term. A bar is an place where alcoholic beverages are served. */ +"osm.tag.bar" = "bar"; + +/* Open Street Map term. An ice cream shop is a place where ice cream is sold. */ +"osm.tag.ice_cream" = "ice_cream"; + +/* Open Street Map term. A pub is a place selling beer and other alcoholic drinks; may also provide food or accommodation (UK). */ +"osm.tag.pub" = "pub"; + +/* Open Street Map term. A coffee shop isa place that typically sells coffee and pastries. */ +"osm.tag.coffee_shop" = "coffee_shop"; + +/* Open Street Map term. A monument is a structure created to commemorate a person. */ +"osm.tag.monument" = "Monument"; +/* Other OSM tags... */ +"osm.tag.statue" = "Statue"; +"osm.tag.museum" = "Museum"; +"osm.tag.historic" = "Historic"; +"osm.tag.landmark" = "Landmark"; +"osm.tag.cathedral" = "Cathedral"; + /* Open Street Map term. Used as a display name for place or a location. %@ is a bus stop id, such as "Bus Stop 7" or "Bus Stop B". */ "osm.tag.bus_stop.refed" = "Bus Stop %@"; @@ -3280,6 +3322,15 @@ /* Open Street Map term. An area of open space for recreational use, usually designed and in semi-natural state with grassy areas, trees and bushes. Parks are usually urban, but not always. */ "osm.tag.park" = "Park"; +"osm.tag.playground" = "Playground"; +"osm.tag.nature_reserve" = "Nature Reserve"; +"osm.tag.botanical_garden" = "Botanical Garden"; +"osm.tag.public_garden" = "Public Garden"; +"osm.tag.field" = "Field"; +"osm.tag.reserve" = "Reserve"; +"osm.tag.green_space" = "Green Space"; +"osm.tag.recreation_area" = "Recreation Area"; + /* Open Street Map term. A table with benches, ideal for food and rest. */ "osm.tag.picnic_table" = "Picnic Table"; diff --git a/apps/ios/GuideDogs/Code/Data/Models/Extensions/OSM Entity/GDASpatialDataResultEntity+Typeable.swift b/apps/ios/GuideDogs/Code/Data/Models/Extensions/OSM Entity/GDASpatialDataResultEntity+Typeable.swift index a7b1c1fa..eb3fe6c4 100644 --- a/apps/ios/GuideDogs/Code/Data/Models/Extensions/OSM Entity/GDASpatialDataResultEntity+Typeable.swift +++ b/apps/ios/GuideDogs/Code/Data/Models/Extensions/OSM Entity/GDASpatialDataResultEntity+Typeable.swift @@ -12,13 +12,31 @@ extension GDASpatialDataResultEntity: Typeable { func isOfType(_ type: PrimaryType) -> Bool { switch type { - case .transit: return isOfType(.transitStop) + case .transit: + return isOfType(.transitStop) + case .food: + return isFood() + case .park: + return isPark() + case .bank: + return isBank() + case .grocery: + return isGrocery() } } func isOfType(_ type: SecondaryType) -> Bool { switch type { - case .transitStop: return isTransitStop() + case .transitStop: + return isTransitStop() + case .food: + return isFood() + case .park: + return isPark() + case .bank: + return isBank() + case .grocery: + return isGrocery() } } @@ -26,8 +44,35 @@ extension GDASpatialDataResultEntity: Typeable { guard let category = SuperCategory(rawValue: superCategory) else { return false } - - return category == .mobility && localizedName.lowercased().contains(GDLocalizedString("osm.tag.bus_stop").lowercased()) + let isTransitLocation = category == .mobility && localizedName.lowercased().contains(GDLocalizedString("osm.tag.bus_stop").lowercased()) + + return isTransitLocation + } + + /// All filters follow the same pattern: is amenity any one of a set of tags? + private func isAnyOf(tags: Array) -> Bool { + return tags.contains(amenity) + } + + private func isFood() -> Bool { + return isAnyOf(tags: [ + "restaurant", "fast_food", "cafe", "bar", "ice_cream", "pub", + "coffee_shop" + ]); + } + + private func isPark() -> Bool { + return isAnyOf(tags: [ + "park", "garden", "green_space", "recreation_area", "playground", + "nature_reserve", "botanical_garden", "public_garden", "field", "reserve" + ]); + } + + private func isBank() -> Bool { + return isAnyOf(tags: ["bank", "atm"]); + } + + private func isGrocery() -> Bool { + return isAnyOf(tags: ["convenience", "supermarket"]); } - } diff --git a/apps/ios/GuideDogs/Code/Data/Models/Helpers/Types/PrimaryType.swift b/apps/ios/GuideDogs/Code/Data/Models/Helpers/Types/PrimaryType.swift index 8da90fc4..64b98c3f 100644 --- a/apps/ios/GuideDogs/Code/Data/Models/Helpers/Types/PrimaryType.swift +++ b/apps/ios/GuideDogs/Code/Data/Models/Helpers/Types/PrimaryType.swift @@ -11,6 +11,10 @@ import Foundation enum PrimaryType: String, CaseIterable, Type { case transit + case food + case park + case bank + case grocery func matches(poi: POI) -> Bool { guard let typeable = poi as? Typeable else { diff --git a/apps/ios/GuideDogs/Code/Data/Models/Helpers/Types/SecondaryType.swift b/apps/ios/GuideDogs/Code/Data/Models/Helpers/Types/SecondaryType.swift index 5a0da034..839bba6b 100644 --- a/apps/ios/GuideDogs/Code/Data/Models/Helpers/Types/SecondaryType.swift +++ b/apps/ios/GuideDogs/Code/Data/Models/Helpers/Types/SecondaryType.swift @@ -11,6 +11,10 @@ import Foundation enum SecondaryType: Type { case transitStop + case food + case park + case bank + case grocery func matches(poi: POI) -> Bool { guard let typeable = poi as? Typeable else { diff --git a/apps/ios/GuideDogs/Code/Visual UI/Helpers/Nearby Table/NearbyTableFilter.swift b/apps/ios/GuideDogs/Code/Visual UI/Helpers/Nearby Table/NearbyTableFilter.swift index baed5815..ce759556 100644 --- a/apps/ios/GuideDogs/Code/Visual UI/Helpers/Nearby Table/NearbyTableFilter.swift +++ b/apps/ios/GuideDogs/Code/Visual UI/Helpers/Nearby Table/NearbyTableFilter.swift @@ -19,7 +19,11 @@ struct NearbyTableFilter: Equatable { static var defaultFilters: [NearbyTableFilter] { return [ .defaultFilter, - NearbyTableFilter(type: .transit) + NearbyTableFilter(type: .transit), + NearbyTableFilter(type: .food), + NearbyTableFilter(type: .park), + NearbyTableFilter(type: .grocery), + NearbyTableFilter(type: .bank), ] } @@ -54,6 +58,18 @@ struct NearbyTableFilter: Equatable { case .transit: self.localizedString = GDLocalizedString("filter.transit") self.image = UIImage(named: "Transit") + case .food: + self.localizedString = GDLocalizedString("filter.food_drink") + self.image = UIImage(named: "Food & Drink") + case .park: + self.localizedString = GDLocalizedString("filter.parks") + self.image = UIImage(named: "Parks") + case .bank: + self.localizedString = GDLocalizedString("filter.banks") + self.image = UIImage(named: "Banks & ATMs") + case .grocery: + self.localizedString = GDLocalizedString("filter.groceries") + self.image = UIImage(named: "Groceries & Convenience Stores") } } else { // There is no `PrimaryType` filter selected diff --git a/apps/ios/UnitTests/Data/Destination Manager/NearbyTableFilterTest.swift b/apps/ios/UnitTests/Data/Destination Manager/NearbyTableFilterTest.swift new file mode 100644 index 00000000..0065b990 --- /dev/null +++ b/apps/ios/UnitTests/Data/Destination Manager/NearbyTableFilterTest.swift @@ -0,0 +1,63 @@ +// +// NearbyTableFilterTest.swift +// UnitTests +// +// Created by Jonathan Ha on 7/11/24. +// Copyright © 2024 Soundscape community. All rights reserved. +// + +import XCTest +@testable import Soundscape + +final class NearbyTableFilterTest: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + override func tearDownWithError() throws { + } + + func testDefaultFilter() throws { + let filter = NearbyTableFilter.defaultFilter + XCTAssertNil(filter.type) + XCTAssertEqual(filter.localizedString, GDLocalizedString("filter.all")) + XCTAssertEqual(filter.image, UIImage(named: "AllPlaces")) + } + + func testPrimaryTypeFilters() throws { + let filters = NearbyTableFilter.primaryTypeFilters + XCTAssertEqual(filters.count, PrimaryType.allCases.count + 1) // +1 for the default filter + + for (index, type) in PrimaryType.allCases.enumerated() { + let filter = filters[index + 1] // First filter is the default filter + XCTAssertEqual(filter.type, type) + switch type { + case .transit: + XCTAssertEqual(filter.localizedString, GDLocalizedString("filter.transit")) + XCTAssertEqual(filter.image, UIImage(named: "Transit")) + case .food: + XCTAssertEqual(filter.localizedString, GDLocalizedString("filter.food_drink")) + XCTAssertEqual(filter.image, UIImage(named: "Food & Drink")) + case .park: + XCTAssertEqual(filter.localizedString, GDLocalizedString("filter.parks")) + XCTAssertEqual(filter.image, UIImage(named: "Parks")) + case .bank: + XCTAssertEqual(filter.localizedString, GDLocalizedString("filter.banks")) + XCTAssertEqual(filter.image, UIImage(named: "Banks & ATMs")) + case .grocery: + XCTAssertEqual(filter.localizedString, GDLocalizedString("filter.groceries")) + XCTAssertEqual(filter.image, UIImage(named: "Groceries & Convenience Stores ")) + } + } + } + + func testEquality() throws { + let filter1 = NearbyTableFilter(type: .transit) + let filter2 = NearbyTableFilter(type: .transit) + let filter3 = NearbyTableFilter(type: .food) + + XCTAssertEqual(filter1, filter2) + XCTAssertNotEqual(filter1, filter3) + } +}