Skip to content

Commit

Permalink
Make location refresh possible
Browse files Browse the repository at this point in the history
  • Loading branch information
yalishanda42 committed May 26, 2023
1 parent 8b4e8ec commit a349639
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import Dependencies
extension LocationService: TestDependencyKey {
public static var testValue = Self(
statusStream: unimplemented("LocationService.statusStream"),
requestAuthorization: unimplemented("LocationService.requestAuthorization")
requestAuthorization: unimplemented("LocationService.requestAuthorization"),
manuallyRefreshStatus: unimplemented("LocationService.manuallyRefreshStatus")
)

public static let previewValue: Self = {
Expand All @@ -27,6 +28,9 @@ extension LocationService: TestDependencyKey {
},
requestAuthorization: {
continuation?.yield(.authorized(nearestStation: .dobrich))
},
manuallyRefreshStatus: {
continuation?.yield(.authorized(nearestStation: .varna))
}
)
}()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import SharedModels
public struct LocationService {
public var statusStream: () async -> AsyncStream<LocationStatus>
public var requestAuthorization: () async -> Void
public var manuallyRefreshStatus: () async -> Void

public init(
statusStream: @escaping () async -> AsyncStream<LocationStatus>,
requestAuthorization: @escaping () async -> Void
requestAuthorization: @escaping () async -> Void,
manuallyRefreshStatus: @escaping () async -> Void
) {
self.statusStream = statusStream
self.requestAuthorization = requestAuthorization
self.manuallyRefreshStatus = manuallyRefreshStatus
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ extension LocationService: DependencyKey {
},
requestAuthorization: {
await LocationManagerActor.shared.requestAuth()
},
manuallyRefreshStatus: {
await LocationManagerActor.shared.refresh()
}
)
}()
Expand Down Expand Up @@ -127,6 +130,14 @@ private struct LocationManagerActor {
guard case .notDetermined = manager.authorizationStatus else { return }
manager.requestWhenInUseAuthorization()
}

func refresh() {
let location = authorizedStatuses.contains(manager.authorizationStatus)
? manager.location?.coordinate
: nil

delegate.onLocationUpdate?(location)
}
}

extension LocationManagerActor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ public struct SearchStationReducer: ReducerProtocol {
case moveFavorite(from: IndexSet, to: Int)

case locationStatusUpdate(LocationStatus)
case askForLocationPersmission
case locationSettings
case locationAction

/// To be send from the `.task` view modifier.
/// Used for executing a long-running effect for
Expand Down Expand Up @@ -134,11 +133,11 @@ public struct SearchStationReducer: ReducerProtocol {

state.stationState = .init(station: new)
return .send(.stationAction(.refresh))

case .loadSavedStations(let statons):
state.favoriteStations = statons
return .none

case .toggleSaveStation(let station):
if state.isStationFavorite(station) {
state.favoriteStations.removeAll { $0 == station }
Expand All @@ -151,7 +150,7 @@ public struct SearchStationReducer: ReducerProtocol {
} catch: { [favorites = state.favoriteStations] error, _ in
log.error(error, ".toggleSaveStation: Coud not save favorites=\(favorites)")
}

case let .moveFavorite(from: from, to: to):
state.favoriteStations.move(fromOffsets: from, toOffset: to)
return .run { [favorites = state.favoriteStations] _ in
Expand All @@ -160,15 +159,26 @@ public struct SearchStationReducer: ReducerProtocol {
log.error(error, ".moveFavorite: Could not save favorites=\(favorites)")
}

case .askForLocationPersmission:
state.locationStatus = .determining
return .fireAndForget {
await locationService.requestAuthorization()
}

case .locationSettings:
return .fireAndForget {
await settingsService.openSettings()
case .locationAction:
switch state.locationStatus {
case .notYetAskedForAuthorization:
state.locationStatus = .determining
return .fireAndForget {
await locationService.requestAuthorization()
}
case .denied:
return .fireAndForget {
await settingsService.openSettings()
}
case .authorized(nearestStation: .some(let station)):
return .send(.selectStation(station))
case .authorized(nearestStation: .none):
state.locationStatus = .determining
return .fireAndForget {
await locationService.manuallyRefreshStatus()
}
case .determining, .unableToUseLocation:
return .none
}

case .stationAction(let childAction):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,23 +130,23 @@ private struct NearestStationView: View {
Image(systemName: "location.fill")
.foregroundColor(.accentColor)
Button {
vs.send(.selectStation(station))
vs.send(.locationAction)
} label: {
Text(station.name)
}.favoritable(station: station, vs: vs)
case .notYetAskedForAuthorization:
Image(systemName: "location")
.foregroundColor(.accentColor)
Button {
vs.send(.askForLocationPersmission)
vs.send(.locationAction)
} label: {
Text("Позволи достъп до локацията")
}
case .denied:
Image(systemName: "location")
.foregroundColor(.red)
Button {
vs.send(.locationSettings)
vs.send(.locationAction)
} label: {
Text("Достъпът до локацията е отказан")
}
Expand All @@ -159,8 +159,12 @@ private struct NearestStationView: View {
case .authorized(nearestStation: .none):
Image(systemName: "wifi.exclamationmark")
.foregroundColor(.gray)
Text("Неуспех при опит за връзка")
.foregroundColor(.gray)
Button {
vs.send(.locationAction)
} label: {
Text("Неуспех при опит за връзка")
.foregroundColor(.gray)
}
case .unableToUseLocation:
EmptyView()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,25 +47,82 @@ final class SearchStationDomainTests: XCTestCase {
await store.send(.updateQuery(query)) // no modification expected
}

func test_askForLocation_callsService() async throws {
func test_locationAction_whenNotYetAsked_asksForPermisson() async throws {
let serviceSpy = Spy()
let store = TestStore(
initialState: SearchStationReducer.State(),
initialState: SearchStationReducer.State(
locationStatus: .notYetAskedForAuthorization
),
reducer: SearchStationReducer()
) {
$0.locationService.requestAuthorization = {
await serviceSpy.call()
}
}

await store.send(.askForLocationPersmission) {
await store.send(.locationAction) {
$0.locationStatus = .determining
}

let calls = await serviceSpy.calls
XCTAssertEqual(calls, 1)
}

func test_locationAction_whenAvailable_opensStationInfo() async throws {
let station = BGStation.dobrich
let store = TestStore(
initialState: SearchStationReducer.State(
locationStatus: .authorized(nearestStation: station)
),
reducer: SearchStationReducer()
)

store.exhaustivity = .off

await store.send(.locationAction)
await store.receive(.selectStation(station))
}

func test_locationAction_whenConnectionFailed_refreshes() async throws {
let serviceSpy = Spy()
let store = TestStore(
initialState: SearchStationReducer.State(
locationStatus: .authorized(nearestStation: nil)
),
reducer: SearchStationReducer()
) {
$0.locationService.manuallyRefreshStatus = {
await serviceSpy.call()
}
}

await store.send(.locationAction) {
$0.locationStatus = .determining
}

let calls = await serviceSpy.calls
XCTAssertEqual(calls, 1)
}

func test_locationAction_whenDenied_showsSettings() async throws {
let serviceSpy = Spy()
let store = TestStore(
initialState: SearchStationReducer.State(
locationStatus: .denied
),
reducer: SearchStationReducer()
) {
$0.settingsService.openSettings = {
await serviceSpy.call()
}
}

await store.send(.locationAction)

let calls = await serviceSpy.calls
XCTAssertEqual(calls, 1)
}

func test_task_observesLocation() async throws {
let statuses: [LocationStatus] = [
.notYetAskedForAuthorization,
Expand Down

0 comments on commit a349639

Please sign in to comment.