Fixed error on location picker screen
This commit is contained in:
parent
23224eb2bf
commit
81bdf64b61
@ -1657,7 +1657,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 160;
|
CURRENT_PROJECT_VERSION = 161;
|
||||||
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
||||||
INFOPLIST_FILE = AutoCat/Info.plist;
|
INFOPLIST_FILE = AutoCat/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = AutoCat;
|
INFOPLIST_KEY_CFBundleDisplayName = AutoCat;
|
||||||
@ -1686,7 +1686,7 @@
|
|||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 160;
|
CURRENT_PROJECT_VERSION = 161;
|
||||||
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
||||||
INFOPLIST_FILE = AutoCat/Info.plist;
|
INFOPLIST_FILE = AutoCat/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = AutoCat;
|
INFOPLIST_KEY_CFBundleDisplayName = AutoCat;
|
||||||
|
|||||||
@ -39,7 +39,7 @@ class EventsViewModel: ACHudContainer {
|
|||||||
var showPasteAlert: Bool = false
|
var showPasteAlert: Bool = false
|
||||||
var pastedEvent: VehicleEventDto?
|
var pastedEvent: VehicleEventDto?
|
||||||
|
|
||||||
var onUpdate: (VehicleDto) -> Void
|
var onUpdate: ((VehicleDto) -> Void)?
|
||||||
|
|
||||||
var isPasteAvailable: Bool {
|
var isPasteAvailable: Bool {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
@ -54,11 +54,13 @@ class EventsViewModel: ACHudContainer {
|
|||||||
settingsService.user.hasPermission(.locationAuthor)
|
settingsService.user.hasPermission(.locationAuthor)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(apiService: ApiServiceProtocol,
|
init(
|
||||||
storageService: StorageServiceProtocol,
|
apiService: ApiServiceProtocol,
|
||||||
settingsService: SettingsServiceProtocol,
|
storageService: StorageServiceProtocol,
|
||||||
vehicle: VehicleDto,
|
settingsService: SettingsServiceProtocol,
|
||||||
onUpdate: @escaping (VehicleDto) -> Void) {
|
vehicle: VehicleDto,
|
||||||
|
onUpdate: ((VehicleDto) -> Void)? = nil
|
||||||
|
) {
|
||||||
|
|
||||||
self.apiService = apiService
|
self.apiService = apiService
|
||||||
self.storageService = storageService
|
self.storageService = storageService
|
||||||
@ -93,7 +95,7 @@ class EventsViewModel: ACHudContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !initialLoad {
|
if !initialLoad {
|
||||||
onUpdate(vehicle)
|
onUpdate?(vehicle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,12 +33,12 @@ class FiltersViewModel {
|
|||||||
|
|
||||||
@ObservationIgnored var currentBrand: StringOption = .any
|
@ObservationIgnored var currentBrand: StringOption = .any
|
||||||
|
|
||||||
let onUpdate: (Filter) -> Void
|
let onUpdate: ((Filter) -> Void)?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
apiService: ApiServiceProtocol,
|
apiService: ApiServiceProtocol,
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
onUpdate: @escaping (Filter) -> Void
|
onUpdate: ((Filter) -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
self.apiService = apiService
|
self.apiService = apiService
|
||||||
self.filter = filter
|
self.filter = filter
|
||||||
@ -65,7 +65,7 @@ class FiltersViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applyFilters() {
|
func applyFilters() {
|
||||||
onUpdate(filter)
|
onUpdate?(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func nullifyTime(of date: Date?) -> Date? {
|
func nullifyTime(of date: Date?) -> Date? {
|
||||||
|
|||||||
@ -27,7 +27,7 @@ struct LocationPickerScreen: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Map(initialPosition: viewModel.position)
|
Map(position: $viewModel.position)
|
||||||
.mapControls {
|
.mapControls {
|
||||||
MapUserLocationButton()
|
MapUserLocationButton()
|
||||||
}
|
}
|
||||||
@ -43,6 +43,7 @@ struct LocationPickerScreen: View {
|
|||||||
.foregroundColor(.blue)
|
.foregroundColor(.blue)
|
||||||
}
|
}
|
||||||
//.ignoresSafeArea()
|
//.ignoresSafeArea()
|
||||||
|
.onAppear(perform: viewModel.onAppear)
|
||||||
.navigationTitle(viewModel.event.location)
|
.navigationTitle(viewModel.event.location)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .primaryAction) {
|
ToolbarItem(placement: .primaryAction) {
|
||||||
|
|||||||
@ -18,19 +18,26 @@ final class LocationPickerViewModel {
|
|||||||
let locationService: LocationServiceProtocol
|
let locationService: LocationServiceProtocol
|
||||||
|
|
||||||
var event: VehicleEventDto
|
var event: VehicleEventDto
|
||||||
var position: MapCameraPosition
|
var position: MapCameraPosition = .automatic
|
||||||
|
|
||||||
var onUpdate: (VehicleEventDto) -> Void
|
var onUpdate: ((VehicleEventDto) -> Void)?
|
||||||
|
|
||||||
|
@ObservationIgnored
|
||||||
|
@AutoCancellable
|
||||||
|
var geocoderTask: Task<Void, Never>?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
locationService: LocationServiceProtocol,
|
locationService: LocationServiceProtocol,
|
||||||
event: VehicleEventDto,
|
event: VehicleEventDto,
|
||||||
onUpdate: @escaping (VehicleEventDto) -> Void
|
onUpdate: ((VehicleEventDto) -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.locationService = locationService
|
self.locationService = locationService
|
||||||
self.event = event
|
self.event = event
|
||||||
self.onUpdate = onUpdate
|
self.onUpdate = onUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
func onAppear() {
|
||||||
|
|
||||||
if event.latitude == 0 && event.longitude == 0 {
|
if event.latitude == 0 && event.longitude == 0 {
|
||||||
self.position = .userLocation(fallback: .automatic)
|
self.position = .userLocation(fallback: .automatic)
|
||||||
@ -43,11 +50,24 @@ final class LocationPickerViewModel {
|
|||||||
func updateEvent(center: CLLocationCoordinate2D) async {
|
func updateEvent(center: CLLocationCoordinate2D) async {
|
||||||
event.latitude = center.latitude
|
event.latitude = center.latitude
|
||||||
event.longitude = center.longitude
|
event.longitude = center.longitude
|
||||||
event.address = try? await locationService.getAddressForLocation(latitude: center.latitude,
|
|
||||||
longitude: center.longitude)
|
geocoderTask = Task {
|
||||||
|
try? await Task.sleep(for: .milliseconds(200))
|
||||||
|
|
||||||
|
if Task.isCancelled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.address = try? await locationService.getAddressForLocation(
|
||||||
|
latitude: center.latitude,
|
||||||
|
longitude: center.longitude
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = await geocoderTask?.result
|
||||||
}
|
}
|
||||||
|
|
||||||
func done() {
|
func done() {
|
||||||
onUpdate(event)
|
onUpdate?(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,13 +28,13 @@ class NotesViewModel: ACHudContainer {
|
|||||||
var vehicle: VehicleDto
|
var vehicle: VehicleDto
|
||||||
var hud: ACHud?
|
var hud: ACHud?
|
||||||
|
|
||||||
var onUpdate: (VehicleDto) -> Void
|
var onUpdate: ((VehicleDto) -> Void)?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
storageService: StorageServiceProtocol,
|
storageService: StorageServiceProtocol,
|
||||||
apiService: ApiServiceProtocol,
|
apiService: ApiServiceProtocol,
|
||||||
vehicle: VehicleDto,
|
vehicle: VehicleDto,
|
||||||
onUpdate: @escaping (VehicleDto) -> Void
|
onUpdate: ((VehicleDto) -> Void)? = nil
|
||||||
) {
|
) {
|
||||||
|
|
||||||
self.storageService = storageService
|
self.storageService = storageService
|
||||||
@ -80,7 +80,7 @@ class NotesViewModel: ACHudContainer {
|
|||||||
await wrapWithToast(showProgress: false) { [weak self] in
|
await wrapWithToast(showProgress: false) { [weak self] in
|
||||||
guard let self else { throw GenericError.somethingWentWrong }
|
guard let self else { throw GenericError.somethingWentWrong }
|
||||||
vehicle = try await storageOp()
|
vehicle = try await storageOp()
|
||||||
onUpdate(vehicle)
|
onUpdate?(vehicle)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ class NotesViewModel: ACHudContainer {
|
|||||||
let vehicle = try await apiOp()
|
let vehicle = try await apiOp()
|
||||||
try await storageService.updateVehicle(dto: vehicle, policy: .ifExists)
|
try await storageService.updateVehicle(dto: vehicle, policy: .ifExists)
|
||||||
self.vehicle = vehicle
|
self.vehicle = vehicle
|
||||||
onUpdate(vehicle)
|
onUpdate?(vehicle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ class ReportViewModel: ACHudContainer {
|
|||||||
var vehicle: VehicleDto
|
var vehicle: VehicleDto
|
||||||
var hud: ACHud?
|
var hud: ACHud?
|
||||||
|
|
||||||
let onUpdate: (VehicleDto) -> Void
|
let onUpdate: ((VehicleDto) -> Void)?
|
||||||
|
|
||||||
var plateNumber: String {
|
var plateNumber: String {
|
||||||
if vehicle.outdated, let current = vehicle.currentNumber {
|
if vehicle.outdated, let current = vehicle.currentNumber {
|
||||||
@ -55,12 +55,14 @@ class ReportViewModel: ACHudContainer {
|
|||||||
return URL(string: Constants.reportLinkBaseURL + "?token=" + jwt)
|
return URL(string: Constants.reportLinkBaseURL + "?token=" + jwt)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(apiService: ApiServiceProtocol,
|
init(
|
||||||
storageService: StorageServiceProtocol,
|
apiService: ApiServiceProtocol,
|
||||||
settingsService: SettingsServiceProtocol,
|
storageService: StorageServiceProtocol,
|
||||||
vehicle: VehicleDto,
|
settingsService: SettingsServiceProtocol,
|
||||||
isPersistent: Bool,
|
vehicle: VehicleDto,
|
||||||
onUpdate: @escaping (VehicleDto) -> Void) {
|
isPersistent: Bool,
|
||||||
|
onUpdate: ((VehicleDto) -> Void)? = nil
|
||||||
|
) {
|
||||||
|
|
||||||
self.apiService = apiService
|
self.apiService = apiService
|
||||||
self.storageService = storageService
|
self.storageService = storageService
|
||||||
@ -97,12 +99,12 @@ class ReportViewModel: ACHudContainer {
|
|||||||
guard let self else { throw GenericError.somethingWentWrong }
|
guard let self else { throw GenericError.somethingWentWrong }
|
||||||
vehicle = try await apiService.checkVehicleGb(by: vehicle.getNumber())
|
vehicle = try await apiService.checkVehicleGb(by: vehicle.getNumber())
|
||||||
try await storageService.updateVehicle(dto: vehicle, policy: .ifExists)
|
try await storageService.updateVehicle(dto: vehicle, policy: .ifExists)
|
||||||
onUpdate(vehicle)
|
onUpdate?(vehicle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func onVehicleChanged(_ vehicle: VehicleDto) {
|
func onVehicleChanged(_ vehicle: VehicleDto) {
|
||||||
self.vehicle = vehicle
|
self.vehicle = vehicle
|
||||||
onUpdate(vehicle)
|
onUpdate?(vehicle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -140,8 +140,8 @@ struct HistoryTests {
|
|||||||
let updatedVehicle: VehicleDto = .normal.addNote(text: "123")
|
let updatedVehicle: VehicleDto = .normal.addNote(text: "123")
|
||||||
|
|
||||||
given(storageServiceMock)
|
given(storageServiceMock)
|
||||||
.loadVehicles()
|
.loadVehicles().willReturn([.normal])
|
||||||
.willReturn([.normal])
|
.loadVehicles().willReturn([updatedVehicle])
|
||||||
|
|
||||||
given(storageServiceMock)
|
given(storageServiceMock)
|
||||||
.dbFileURL
|
.dbFileURL
|
||||||
@ -151,14 +151,6 @@ struct HistoryTests {
|
|||||||
.updateHistory(number: .any)
|
.updateHistory(number: .any)
|
||||||
.willReturn((vehicle: updatedVehicle, errors: []))
|
.willReturn((vehicle: updatedVehicle, errors: []))
|
||||||
|
|
||||||
when(storageServiceMock)
|
|
||||||
.loadVehicles()
|
|
||||||
.perform {
|
|
||||||
given(storageServiceMock)
|
|
||||||
.loadVehicles()
|
|
||||||
.willReturn([updatedVehicle])
|
|
||||||
}
|
|
||||||
|
|
||||||
await viewModel.onAppear()
|
await viewModel.onAppear()
|
||||||
await viewModel.updateVehicle(.normal)
|
await viewModel.updateVehicle(.normal)
|
||||||
|
|
||||||
@ -168,7 +160,7 @@ struct HistoryTests {
|
|||||||
|
|
||||||
verify(storageServiceMock)
|
verify(storageServiceMock)
|
||||||
.loadVehicles()
|
.loadVehicles()
|
||||||
.called(.exactly(3))
|
.called(.exactly(2))
|
||||||
|
|
||||||
#expect(viewModel.vehicles.count == 1)
|
#expect(viewModel.vehicles.count == 1)
|
||||||
#expect(viewModel.vehiclesFiltered.count == 1)
|
#expect(viewModel.vehiclesFiltered.count == 1)
|
||||||
|
|||||||
@ -22,24 +22,21 @@ struct LocationPickerTests {
|
|||||||
let longitude: CLLocationDegrees = 10
|
let longitude: CLLocationDegrees = 10
|
||||||
let address = "Test Address"
|
let address = "Test Address"
|
||||||
|
|
||||||
let geocoderMock = MockGeocoderProtocol()
|
let locationServiceMock = MockLocationServiceProtocol()
|
||||||
|
|
||||||
func makeViewModel(event: VehicleEventDto) -> LocationPickerViewModel {
|
func makeViewModel(event: VehicleEventDto) -> LocationPickerViewModel {
|
||||||
|
|
||||||
let locationService = LocationService(
|
return LocationPickerViewModel(
|
||||||
geocoder: geocoderMock,
|
locationService: locationServiceMock,
|
||||||
locationManager: MockSwiftLocationProtocol(),
|
event: event
|
||||||
settingsService: MockSettingsServiceProtocol()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return LocationPickerViewModel(locationService: locationService,
|
|
||||||
event: event)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test("Set initial location (user)")
|
@Test("Set initial location (user)")
|
||||||
func setInitialLocationUser() async throws {
|
func setInitialLocationUser() async throws {
|
||||||
|
|
||||||
let viewModel = makeViewModel(event: .init(lat: 0, lon: 0, addedBy: nil))
|
let viewModel = makeViewModel(event: .init(lat: 0, lon: 0, addedBy: nil))
|
||||||
|
viewModel.onAppear()
|
||||||
|
|
||||||
#expect(viewModel.position == .userLocation(fallback: .automatic))
|
#expect(viewModel.position == .userLocation(fallback: .automatic))
|
||||||
}
|
}
|
||||||
@ -48,6 +45,7 @@ struct LocationPickerTests {
|
|||||||
func setInitialLocationCustom() async throws {
|
func setInitialLocationCustom() async throws {
|
||||||
|
|
||||||
let viewModel = makeViewModel(event: .init(lat: latitude, lon: longitude, addedBy: nil))
|
let viewModel = makeViewModel(event: .init(lat: latitude, lon: longitude, addedBy: nil))
|
||||||
|
viewModel.onAppear()
|
||||||
|
|
||||||
#expect(viewModel.position.region?.center.latitude == latitude)
|
#expect(viewModel.position.region?.center.latitude == latitude)
|
||||||
#expect(viewModel.position.region?.center.longitude == longitude)
|
#expect(viewModel.position.region?.center.longitude == longitude)
|
||||||
@ -58,12 +56,9 @@ struct LocationPickerTests {
|
|||||||
|
|
||||||
let viewModel = makeViewModel(event: .init(lat: 0, lon: 0, addedBy: nil))
|
let viewModel = makeViewModel(event: .init(lat: 0, lon: 0, addedBy: nil))
|
||||||
|
|
||||||
let location = CLLocation(latitude: latitude, longitude: longitude)
|
given(locationServiceMock)
|
||||||
let placemark = CLPlacemark(location: location, name: address, postalAddress: nil)
|
.getAddressForLocation(latitude: .value(latitude), longitude: .value(longitude))
|
||||||
|
.willReturn(address)
|
||||||
given(geocoderMock)
|
|
||||||
.reverseGeocodeLocation(.any)
|
|
||||||
.willReturn([placemark])
|
|
||||||
|
|
||||||
await viewModel.updateEvent(center: .init(latitude: latitude, longitude: longitude))
|
await viewModel.updateEvent(center: .init(latitude: latitude, longitude: longitude))
|
||||||
|
|
||||||
@ -71,4 +66,32 @@ struct LocationPickerTests {
|
|||||||
#expect(viewModel.event.longitude == longitude)
|
#expect(viewModel.event.longitude == longitude)
|
||||||
#expect(viewModel.event.address == address)
|
#expect(viewModel.event.address == address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test("Update event (throttling)")
|
||||||
|
func updateEventThrottling() async throws {
|
||||||
|
|
||||||
|
let viewModel = makeViewModel(event: .init(lat: 0, lon: 0, addedBy: nil))
|
||||||
|
|
||||||
|
let initialLocation = CLLocationCoordinate2D(latitude: 0, longitude: 0)
|
||||||
|
let finalLocation = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
|
||||||
|
|
||||||
|
given(locationServiceMock)
|
||||||
|
.getAddressForLocation(latitude: .value(0), longitude: .value(0)).willReturn("")
|
||||||
|
.getAddressForLocation(latitude: .value(latitude), longitude: .value(longitude)).willReturn(address)
|
||||||
|
|
||||||
|
viewModel.onAppear()
|
||||||
|
|
||||||
|
async let task1: () = viewModel.updateEvent(center: initialLocation)
|
||||||
|
async let task2: () = viewModel.updateEvent(center: initialLocation)
|
||||||
|
async let task3: () = viewModel.updateEvent(center: finalLocation)
|
||||||
|
_ = await (task1, task2, task3)
|
||||||
|
|
||||||
|
verify(locationServiceMock)
|
||||||
|
.getAddressForLocation(latitude: .value(0), longitude: .value(0)).called(.never)
|
||||||
|
.getAddressForLocation(latitude: .value(latitude), longitude: .value(longitude)).called(.once)
|
||||||
|
|
||||||
|
#expect(viewModel.event.latitude == latitude)
|
||||||
|
#expect(viewModel.event.longitude == longitude)
|
||||||
|
#expect(viewModel.event.address == address)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user