Skip to content

Instantly share code, notes, and snippets.

@saroar
Created December 11, 2023 19:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save saroar/6de5688775745db23d33a75fe8c26680 to your computer and use it in GitHub Desktop.
Save saroar/6de5688775745db23d33a75fe8c26680 to your computer and use it in GitHub Desktop.
import ComposableArchitecture
import CoreLocation
import ComposableCoreLocation
import MapKit
import AddaSharedModels
extension String: LocalizedError {
public var errorDescription: String? { return self }
}
extension LocationManager: TestDependencyKey {
public static let previewValue: Self = .live
public static let testValue: Self = .failing
}
extension LocationManager: DependencyKey {
public static let liveValue: Self = .live
}
extension DependencyValues {
public var locationManager: LocationManager {
get { self[LocationManager.self] }
set { self[LocationManager.self] = newValue }
}
}
//// Extend AnyPublisher to add an async method
//extension AnyPublisher where Output == Never, Failure == Never {
//
// func async() async {
// await withCheckedContinuation { continuation in
// let cancellable = self.sink(receiveCompletion: { _ in
// continuation.resume()
// }, receiveValue: { _ in
// // This will never be called because Output == Never
// })
//
// // Store the cancellable if you need to cancel the subscription later
// _ = cancellable
// }
// }
//}
extension Publisher {
func values() async throws -> AsyncThrowingStream<Output, Error> {
AsyncThrowingStream { continuation in
let cancellable = self.sink { completion in
switch completion {
case .finished:
continuation.finish()
case .failure(let error):
continuation.finish(throwing: error)
}
} receiveValue: { value in
continuation.yield(value)
}
continuation.onTermination = { @Sendable _ in
cancellable.cancel()
}
}
}
}
extension LocationManager {
public func locationManagerActions() -> AsyncStream<LocationManager.Action> {
let publisher = delegate()
return AsyncStream<LocationManager.Action> { continuation in
let cancellable = publisher.sink { action in
continuation.yield(action)
}
continuation.onTermination = { @Sendable _ in
cancellable.cancel()
}
}
}
public func requestWhenInUseAuthorizationAsync() async {
await withCheckedContinuation { continuation in
// Subscribe to the publisher and handle the completion
let cancellable = self.requestWhenInUseAuthorization().sink(
receiveCompletion: { _ in
// Resume the continuation when the publisher completes
continuation.resume()
},
receiveValue: { _ in
// This will never be called because Output == Never
}
)
// Store the cancellable if you need to cancel the subscription later
_ = cancellable
}
}
public func requestAlwaysAuthorizationAsync() async {
await withCheckedContinuation { continuation in
// Subscribe to the publisher and handle the completion
let cancellable = self.requestAlwaysAuthorization().sink(
receiveCompletion: { _ in
// Resume the continuation when the publisher completes
continuation.resume()
},
receiveValue: { _ in
// This will never be called because Output == Never
}
)
// Store the cancellable if you need to cancel the subscription later
_ = cancellable
}
}
}
extension LocationReducer.State {
public static let diff = Self(
coordinate: CLLocation(latitude: 60.020532228306031, longitude: 30.388014239849944),
isLocationAuthorized: true
)
}
public struct LocationReducer: Reducer {
public struct ID: Hashable, @unchecked Sendable {
let rawValue: AnyHashable
init<RawValue: Hashable & Sendable>(_ rawValue: RawValue) {
self.rawValue = rawValue
}
public init() {
struct RawValue: Hashable, Sendable {}
self.rawValue = RawValue()
}
}
public struct State: Equatable {
public var alert: AlertState<Action>?
public var coordinate: CLLocation?
public var isLocationAuthorized = false
public var waitingForUpdateLocation = false
public var isRequestingCurrentLocation = false
public var isConnected = false
public var location: Location? = nil
public var placeMark: Placemark? = nil
public init(
alert: AlertState<LocationReducer.Action>? = nil,
coordinate: CLLocation? = nil,
isLocationAuthorized: Bool = false,
waitingForUpdateLocation: Bool = false,
isRequestingCurrentLocation: Bool = false,
isConnected: Bool = false,
location: Location? = nil,
placeMark: Placemark? = nil
) {
self.alert = alert
self.coordinate = coordinate
self.isLocationAuthorized = isLocationAuthorized
self.waitingForUpdateLocation = waitingForUpdateLocation
self.isRequestingCurrentLocation = isRequestingCurrentLocation
self.isConnected = isConnected
self.location = location
self.placeMark = placeMark
}
}
public enum Action: Equatable {
case callDelegateThenGetLocation
case locationManager(LocationManager.Action)
case delegate
case getLocation
case askLocationPermission
case tearDown
case placeMarkResponse(TaskResult<Placemark>)
}
@Dependency(\.locationManager) var locationManager
@Dependency(\.mainQueue) var mainQueue
public init() {}
public func reduce(into state: inout State, action: Action) -> Effect<Action> {
@Sendable func getPlacemark(_ location: Location) async throws -> Placemark {
do {
let address = CLGeocoder()
let placemarks = try await address.reverseGeocodeLocation(location.rawValue)
let new_placemark: MKPlacemark = MKPlacemark(placemark: placemarks[0])
return Placemark(rawValue: new_placemark)
} catch {
throw("Placemark is empty")
}
}
switch action {
case .callDelegateThenGetLocation:
return .run { send in
await send(.delegate)
await send(.getLocation)
}
case let .locationManager(.didUpdateLocations(locations)):
state.isRequestingCurrentLocation = false
guard let location = locations.first else { return .none }
state.location = location
return .run { send in
await send(.placeMarkResponse(
await TaskResult {
try await getPlacemark(location)
}
))
}
case let .locationManager(.didChangeAuthorization(status)):
switch status {
case .notDetermined:
state.alert = .init(
title: TextState(
"""
Please note without location access we will not able to show any events.
To give us access to your location in settings.
"""
)
)
return .none
case .restricted, .denied:
state.isLocationAuthorized = false
state.alert = .init(
title: TextState(
"""
Please note without location access we will not able to show any events.
To give us access to your location in settings.
"""
)
)
return .none
case .authorizedAlways, .authorizedWhenInUse:
state.isLocationAuthorized = true
return .run { send in
await send(.getLocation)
}
@unknown default:
state.alert = .init(
title: TextState(
"""
Please note without location access we will not able to show any events.
To give us access to your location in settings.
"""
)
)
return .none
}
case .locationManager:
return .none
case .delegate:
return .run { send in
for try await action in try await locationManager.delegate().values() {
await send(.locationManager(action))
}
}
.cancellable(id: LocationReducer.ID())
case .tearDown:
return .cancel(id: LocationReducer.ID())
case .askLocationPermission:
return .run { _ in
#if os(macOS)
await locationManager.requestAlwaysAuthorizationAsync()
#else
await locationManager.requestWhenInUseAuthorizationAsync() //requestWhenInUseAuthorizationAsync()
#endif
}
case .getLocation:
switch locationManager.authorizationStatus() {
case .notDetermined:
state.isRequestingCurrentLocation = true
state.waitingForUpdateLocation = true
return .run { _ in
#if os(macOS)
await locationManager.requestAlwaysAuthorization().async()
#else
await locationManager.requestWhenInUseAuthorizationAsync()
#endif
}
case .restricted, .denied:
state.isLocationAuthorized = false
state.alert = .init(title: TextState("Please give us access to your location in settings."))
return .none
case .authorizedAlways, .authorizedWhenInUse:
state.isLocationAuthorized = true
state.isConnected = true
state.waitingForUpdateLocation = false
return .run { _ in
// await locationManager.startUpdatingLocationAsync()
}
@unknown default:
return .none
}
case let .placeMarkResponse(.success(placemarkNewValue)):
state.placeMark = placemarkNewValue
return .cancel(id: LocationReducer.ID())
case .placeMarkResponse(.failure):
// handle error
return .none
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment