diff --git a/MyLibrary/Package.swift b/MyLibrary/Package.swift index dbae88a..a97a05e 100644 --- a/MyLibrary/Package.swift +++ b/MyLibrary/Package.swift @@ -13,7 +13,7 @@ let package = Package( .library( name: "GuidanceFeature", targets: ["GuidanceFeature"] - ) + ), ], dependencies: [ .package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "1.9.1"), @@ -44,14 +44,14 @@ let package = Package( dependencies: [ "MapKitClient", "Safari", - .product(name: "ComposableArchitecture", package: "swift-composable-architecture") + .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), ] ), .target( name: "MapKitClient", dependencies: [ "SharedModels", - .product(name: "ComposableArchitecture", package: "swift-composable-architecture") + .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), ] ), .target( diff --git a/MyLibrary/Sources/GuidanceFeature/Guidance.swift b/MyLibrary/Sources/GuidanceFeature/Guidance.swift index 7e806b2..53ad13e 100644 --- a/MyLibrary/Sources/GuidanceFeature/Guidance.swift +++ b/MyLibrary/Sources/GuidanceFeature/Guidance.swift @@ -1,14 +1,14 @@ import ComposableArchitecture import CoreLocation import Foundation -import MapKitClient import MapKit +import MapKitClient import Safari import SwiftUI @Reducer public struct Guidance { - + @ObservableState public struct State: Equatable { @Presents var destination: Destination.State? @@ -60,71 +60,75 @@ public struct Guidance { BindingReducer() Reduce { state, action in switch action { - case .view(.onAppear): - return .run { [state] send in - await send( - .initialResponse( - Result { - try await onAppear(lines: state.lines) - } - ) + case .view(.onAppear): + return .run { [state] send in + await send( + .initialResponse( + Result { + try await onAppear(lines: state.lines) + } ) - } - case let .initialResponse(.success(response)): - guard let response = response else { return .none } - let route = response.2 + ) + } + case let .initialResponse(.success(response)): + guard let response = response else { return .none } + let route = response.2 - state.origin = response.0 - state.destinationItem = response.1 - state.route = route - state.lookAround = response.3 - //TODO: Calculate distance from 2 CLLocation - state.cameraPosition = .camera(.init(centerCoordinate: route.polyline.coordinate, distance: route.distance * 2)) - return .none + state.origin = response.0 + state.destinationItem = response.1 + state.route = route + state.lookAround = response.3 + //TODO: Calculate distance from 2 CLLocation + state.cameraPosition = .camera( + .init(centerCoordinate: route.polyline.coordinate, distance: route.distance * 2)) + return .none - case let .initialResponse(.failure(error)): - print(error) - return .none + case let .initialResponse(.failure(error)): + print(error) + return .none - case let .updateResponse(.success(response)): - guard let response = response else { return .none } - let route = response.1 - state.origin = response.0 - state.route = route - state.lookAround = response.2 - //TODO: Calculate distance from 2 CLLocation - state.cameraPosition = .camera(.init(centerCoordinate: route.polyline.coordinate, distance: route.distance * 2)) - return .none + case let .updateResponse(.success(response)): + guard let response = response else { return .none } + let route = response.1 + state.origin = response.0 + state.route = route + state.lookAround = response.2 + //TODO: Calculate distance from 2 CLLocation + state.cameraPosition = .camera( + .init(centerCoordinate: route.polyline.coordinate, distance: route.distance * 2)) + return .none - case let .updateResponse(.failure(error)): - print(error) - return .none + case let .updateResponse(.failure(error)): + print(error) + return .none - case .binding(\.lines): - guard let destination = state.destinationItem else { return .none } - return .run { [state] send in - await send( - .updateResponse( - Result { - try await update(with: state.lines, destination: destination) - } - ) + case .binding(\.lines): + guard let destination = state.destinationItem else { return .none } + return .run { [state] send in + await send( + .updateResponse( + Result { + try await update(with: state.lines, destination: destination) + } ) - } + ) + } - case .view(.openMapTapped): - return .run { [state] _ in - state.destinationItem?.openInMaps() - } - case .destination, .binding: - return .none + case .view(.openMapTapped): + return .run { [state] _ in + state.destinationItem?.openInMaps() + } + case .destination, .binding: + return .none } } .ifLet(\.$destination, action: \.destination) } func onAppear(lines: Lines) async throws -> (MKMapItem, MKMapItem, MKRoute, MKLookAroundScene?)? { - let items = try await withThrowingTaskGroup(of: (Int, MKMapItem?).self, returning: (MKMapItem?, MKMapItem?).self) { group in + let items = try await withThrowingTaskGroup( + of: (Int, MKMapItem?).self, returning: (MKMapItem?, MKMapItem?).self + ) { group in group.addTask { (0, try await mapKitClient.localSearch(lines.searchQuery, lines.region).first) } @@ -140,16 +144,23 @@ public struct Guidance { guard let origin = items.0, let destination = items.1 else { return nil } guard let route = try await mapKitClient.mapRoute(origin, destination) else { return nil } let polylineOrigin = route.polyline.coords.first! - guard let geoLocation = try await mapKitClient.reverseGeocodeLocation(.init(latitude: polylineOrigin.latitude, longitude: polylineOrigin.longitude)).first else { + guard + let geoLocation = try await mapKitClient.reverseGeocodeLocation( + .init(latitude: polylineOrigin.latitude, longitude: polylineOrigin.longitude) + ).first + else { return nil } - guard let lookAroundScene = try await mapKitClient.lookAround(.init(placemark: geoLocation)) else { + guard let lookAroundScene = try await mapKitClient.lookAround(.init(placemark: geoLocation)) + else { return (origin, destination, route, nil) } return (origin, destination, route, lookAroundScene) } - func update(with lines: Lines, destination: MKMapItem) async throws -> (MKMapItem, MKRoute, MKLookAroundScene?)? { + func update(with lines: Lines, destination: MKMapItem) async throws -> ( + MKMapItem, MKRoute, MKLookAroundScene? + )? { let origin = try await mapKitClient.localSearch(lines.searchQuery, lines.region).first guard let origin = origin else { return nil } guard let route = try await mapKitClient.mapRoute(origin, destination) else { @@ -157,11 +168,16 @@ public struct Guidance { return nil } let polylineOrigin = route.polyline.coords.first! - guard let geoLocation = try await mapKitClient.reverseGeocodeLocation(.init(latitude: polylineOrigin.latitude, longitude: polylineOrigin.longitude)).first else { + guard + let geoLocation = try await mapKitClient.reverseGeocodeLocation( + .init(latitude: polylineOrigin.latitude, longitude: polylineOrigin.longitude) + ).first + else { print("[Error] Reverse Geocode failed", polylineOrigin) return nil } - guard let lookAroundScene = try await mapKitClient.lookAround(.init(placemark: geoLocation)) else { + guard let lookAroundScene = try await mapKitClient.lookAround(.init(placemark: geoLocation)) + else { print("[Error] Look around scene not found", geoLocation) return (origin, route, nil) } @@ -246,7 +262,11 @@ public struct GuidanceView: View { .tint(.blue) } } - .mapStyle(.standard(elevation: .realistic, emphasis: .automatic, pointsOfInterest: .including([.publicTransport]), showsTraffic: false)) + .mapStyle( + .standard( + elevation: .realistic, emphasis: .automatic, + pointsOfInterest: .including([.publicTransport]), showsTraffic: false) + ) .frame(minHeight: 240) .mapControlVisibility(.hidden) @@ -291,8 +311,11 @@ public struct GuidanceView: View { Text("Warning", bundle: .module) .font(.subheadline.bold()) .foregroundStyle(Color.accentColor) - Text("Our venue is Belle Salle Shibuya FIRST, not garden. Make sure there are two belle salle hall in Shibuya.", bundle: .module) - .font(.callout) + Text( + "Our venue is Belle Salle Shibuya FIRST, not garden. Make sure there are two belle salle hall in Shibuya.", + bundle: .module + ) + .font(.callout) } } icon: { Image(systemName: "exclamationmark.triangle.fill") @@ -308,11 +331,14 @@ public struct GuidanceView: View { } var hallLocation: MKCoordinateRegion { - .init(center: .init(latitude: 35.657920, longitude: 139.708854), span: .init(latitudeDelta: 0.01, longitudeDelta: 0.01)) + .init( + center: .init(latitude: 35.657920, longitude: 139.708854), + span: .init(latitudeDelta: 0.01, longitudeDelta: 0.01)) } #Preview { - GuidanceView(store: .init(initialState: .init()) { - Guidance() - }) + GuidanceView( + store: .init(initialState: .init()) { + Guidance() + }) } diff --git a/MyLibrary/Sources/GuidanceFeature/Lines.swift b/MyLibrary/Sources/GuidanceFeature/Lines.swift index 71a5045..cf6d679 100644 --- a/MyLibrary/Sources/GuidanceFeature/Lines.swift +++ b/MyLibrary/Sources/GuidanceFeature/Lines.swift @@ -12,108 +12,114 @@ enum Lines: Equatable, Identifiable, CaseIterable { var localizedKey: LocalizedStringKey { switch self { - case .metroShibuya: - return "Metro Shibuya" - case .jrShibuya: - return "JR Shibuya" - case .metroOmotesando: - return "Omote-sando" + case .metroShibuya: + return "Metro Shibuya" + case .jrShibuya: + return "JR Shibuya" + case .metroOmotesando: + return "Omote-sando" } } var region: MKCoordinateRegion { switch self { - case .metroShibuya: - return .init(center: .init(latitude: 35.657892, longitude: 139.703748), span: .init(latitudeDelta: 0.01, longitudeDelta: 0.01)) - case .jrShibuya: - return .init(center: .init(latitude: 35.658575, longitude: 139.701499), span: .init(latitudeDelta: 0.01, longitudeDelta: 0.01)) - case .metroOmotesando: - return .init(center: .init(latitude: 35.665222, longitude: 139.712543), span: .init(latitudeDelta: 0.01, longitudeDelta: 0.01)) + case .metroShibuya: + return .init( + center: .init(latitude: 35.657892, longitude: 139.703748), + span: .init(latitudeDelta: 0.01, longitudeDelta: 0.01)) + case .jrShibuya: + return .init( + center: .init(latitude: 35.658575, longitude: 139.701499), + span: .init(latitudeDelta: 0.01, longitudeDelta: 0.01)) + case .metroOmotesando: + return .init( + center: .init(latitude: 35.665222, longitude: 139.712543), + span: .init(latitudeDelta: 0.01, longitudeDelta: 0.01)) } } var searchQuery: String { switch self { - case .jrShibuya: - return "JR Shibuya Station" - case .metroOmotesando: - return "表参道駅 B1" - case .metroShibuya: - return "渋谷駅 C1" + case .jrShibuya: + return "JR Shibuya Station" + case .metroOmotesando: + return "表参道駅 B1" + case .metroShibuya: + return "渋谷駅 C1" } } var exitName: LocalizedStringKey { switch self { - case .metroShibuya: - return "Exit C1" - case .jrShibuya: - return "JR Shibuya Station East Exit" - case .metroOmotesando: - return "Exit B1" + case .metroShibuya: + return "Exit C1" + case .jrShibuya: + return "JR Shibuya Station East Exit" + case .metroOmotesando: + return "Exit B1" } } var originTitle: LocalizedStringKey { switch self { - case .metroShibuya: - return "Shibuya Station C1 Exit" - case .jrShibuya: - return "Shibuya Station East Exit" - case .metroOmotesando: - return "Omote-sando Station B1 Exit" + case .metroShibuya: + return "Shibuya Station C1 Exit" + case .jrShibuya: + return "Shibuya Station East Exit" + case .metroOmotesando: + return "Omote-sando Station B1 Exit" } } var itemColor: Color { switch self { - case .metroShibuya: - return Color.red - case .jrShibuya: - return Color.green - case .metroOmotesando: - return Color.purple + case .metroShibuya: + return Color.red + case .jrShibuya: + return Color.green + case .metroOmotesando: + return Color.purple } } var directions: IdentifiedArrayOf { switch self { - case .metroShibuya: - return [ - .init(order: 1, description: "metro-1", imageName: "metro-1"), - .init(order: 2, description: "jr-9", imageName: "jr-9"), - .init(order: 3, description: "jr-10", imageName: "jr-10"), - .init(order: 4, description: "jr-11", imageName: "jr-11"), - ] - case .jrShibuya: - return [ - .init(order: 1, description: "jr-1", imageName: "jr-1"), - .init(order: 2, description: "jr-2", imageName: "jr-2"), - .init(order: 3, description: "jr-3", imageName: "jr-3"), - .init(order: 4, description: "jr-4", imageName: "jr-4"), - .init(order: 5, description: "jr-5", imageName: "jr-5"), - .init(order: 6, description: "jr-6", imageName: "jr-6"), - .init(order: 7, description: "jr-7", imageName: "jr-7"), - .init(order: 8, description: "jr-8", imageName: "jr-8"), - .init(order: 9, description: "jr-9", imageName: "jr-9"), - .init(order: 10, description: "jr-10", imageName: "jr-10"), - .init(order: 11, description: "jr-11", imageName: "jr-11"), - ] - case .metroOmotesando: - return [ - .init(order: 1, description: "omotesando-1", imageName: "jr-11"), - ] + case .metroShibuya: + return [ + .init(order: 1, description: "metro-1", imageName: "metro-1"), + .init(order: 2, description: "jr-9", imageName: "jr-9"), + .init(order: 3, description: "jr-10", imageName: "jr-10"), + .init(order: 4, description: "jr-11", imageName: "jr-11"), + ] + case .jrShibuya: + return [ + .init(order: 1, description: "jr-1", imageName: "jr-1"), + .init(order: 2, description: "jr-2", imageName: "jr-2"), + .init(order: 3, description: "jr-3", imageName: "jr-3"), + .init(order: 4, description: "jr-4", imageName: "jr-4"), + .init(order: 5, description: "jr-5", imageName: "jr-5"), + .init(order: 6, description: "jr-6", imageName: "jr-6"), + .init(order: 7, description: "jr-7", imageName: "jr-7"), + .init(order: 8, description: "jr-8", imageName: "jr-8"), + .init(order: 9, description: "jr-9", imageName: "jr-9"), + .init(order: 10, description: "jr-10", imageName: "jr-10"), + .init(order: 11, description: "jr-11", imageName: "jr-11"), + ] + case .metroOmotesando: + return [ + .init(order: 1, description: "omotesando-1", imageName: "jr-11") + ] } } var duration: Duration { switch self { - case .metroShibuya: - return .seconds(6 * 60) - case .jrShibuya: - return .seconds(8 * 60) - case .metroOmotesando: - return .seconds(10 * 60) + case .metroShibuya: + return .seconds(6 * 60) + case .jrShibuya: + return .seconds(8 * 60) + case .metroOmotesando: + return .seconds(10 * 60) } } diff --git a/MyLibrary/Sources/MapKitClient/Client.swift b/MyLibrary/Sources/MapKitClient/Client.swift index b598f28..9cd1ad6 100644 --- a/MyLibrary/Sources/MapKitClient/Client.swift +++ b/MyLibrary/Sources/MapKitClient/Client.swift @@ -9,7 +9,8 @@ import SharedModels public struct MapKitClient { public var mapRoute: @Sendable (MKMapItem, MKMapItem) async throws -> MKRoute? public var lookAround: @Sendable (MKMapItem) async throws -> MKLookAroundScene? - public var reverseGeocodeLocation: @Sendable (CLLocationCoordinate2D) async throws -> [MKPlacemark] + public var reverseGeocodeLocation: + @Sendable (CLLocationCoordinate2D) async throws -> [MKPlacemark] public var localSearch: @Sendable (String, MKCoordinateRegion) async throws -> [MKMapItem] } @@ -32,8 +33,10 @@ extension MapKitClient: DependencyKey { }, reverseGeocodeLocation: { location in let geoCoder = CLGeocoder() - return try await geoCoder.reverseGeocodeLocation(.init(latitude: location.latitude, longitude: location.longitude)) - .map(MKPlacemark.init(placemark:)) + return try await geoCoder.reverseGeocodeLocation( + .init(latitude: location.latitude, longitude: location.longitude) + ) + .map(MKPlacemark.init(placemark:)) }, localSearch: { naturalLanguageQuery, region in let request = MKLocalSearch.Request() diff --git a/MyLibrary/Sources/ScheduleFeature/Schedule.swift b/MyLibrary/Sources/ScheduleFeature/Schedule.swift index c45438a..6127aea 100644 --- a/MyLibrary/Sources/ScheduleFeature/Schedule.swift +++ b/MyLibrary/Sources/ScheduleFeature/Schedule.swift @@ -76,34 +76,34 @@ public struct Schedule { let workshop = try dataClient.fetchWorkshop() return .init(day1: day1, day2: day2, workshop: workshop) })) - case let .view(.disclosureTapped(session)): - guard let description = session.description, let speakers = session.speakers else { - return .none - } - state.path.append( - .detail( - .init( - title: session.title, - description: description, - requirements: session.requirements, - speakers: speakers - ) + case let .view(.disclosureTapped(session)): + guard let description = session.description, let speakers = session.speakers else { + return .none + } + state.path.append( + .detail( + .init( + title: session.title, + description: description, + requirements: session.requirements, + speakers: speakers ) ) - return .none - case let .fetchResponse(.success(response)): - state.day1 = response.day1 - state.day2 = response.day2 - state.workshop = response.workshop - return .none - case let .fetchResponse(.failure(error as DecodingError)): - assertionFailure(error.localizedDescription) - return .none - case let .fetchResponse(.failure(error)): - print(error) // TODO: replace to Logger API - return .none - case .binding, .path, .destination: - return .none + ) + return .none + case let .fetchResponse(.success(response)): + state.day1 = response.day1 + state.day2 = response.day2 + state.workshop = response.workshop + return .none + case let .fetchResponse(.failure(error as DecodingError)): + assertionFailure(error.localizedDescription) + return .none + case let .fetchResponse(.failure(error)): + print(error) // TODO: replace to Logger API + return .none + case .binding, .path, .destination: + return .none } } .forEach(\.path, action: \.path) @@ -125,10 +125,10 @@ public struct ScheduleView: View { root } destination: { store in switch store.state { - case .detail: - if let store = store.scope(state: \.detail, action: \.detail) { - ScheduleDetailView(store: store) - } + case .detail: + if let store = store.scope(state: \.detail, action: \.detail) { + ScheduleDetailView(store: store) + } } } } @@ -144,24 +144,24 @@ public struct ScheduleView: View { .pickerStyle(.segmented) .padding(.horizontal) switch store.selectedDay { - case .day1: - if let day1 = store.day1 { - conferenceList(conference: day1) - } else { - Text("") - } - case .day2: - if let day2 = store.day2 { - conferenceList(conference: day2) - } else { - Text("") - } - case .day3: - if let workshop = store.workshop { - conferenceList(conference: workshop) - } else { - Text("") - } + case .day1: + if let day1 = store.day1 { + conferenceList(conference: day1) + } else { + Text("") + } + case .day2: + if let day2 = store.day2 { + conferenceList(conference: day2) + } else { + Text("") + } + case .day3: + if let workshop = store.workshop { + conferenceList(conference: workshop) + } else { + Text("") + } } } .onAppear(perform: {