Adding tests for location picker
This commit is contained in:
parent
299ee23992
commit
9a1c05cb93
@ -39,6 +39,8 @@
|
||||
7A1CF80529A41C66007962DA /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7A1CF80429A41C66007962DA /* RealmSwift */; };
|
||||
7A1CF81629A42117007962DA /* Realm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1CF81529A42117007962DA /* Realm.swift */; };
|
||||
7A1DC38E2517ED98002E9C99 /* BlockBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A1DC38D2517ED98002E9C99 /* BlockBarButtonItem.swift */; };
|
||||
7A22B6ED2C67FDEA00E60173 /* SwiftLocationMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A22B6EB2C67FDEA00E60173 /* SwiftLocationMock.swift */; };
|
||||
7A22B6EE2C67FDEA00E60173 /* GeocoderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A22B6EA2C67FDEA00E60173 /* GeocoderMock.swift */; };
|
||||
7A27ADC7249D43210035F39E /* RegionsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADC6249D43210035F39E /* RegionsController.swift */; };
|
||||
7A27ADF3249F8B650035F39E /* RecordsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF2249F8B650035F39E /* RecordsController.swift */; };
|
||||
7A27ADF5249FD2F90035F39E /* FileManagerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */; };
|
||||
@ -130,6 +132,8 @@
|
||||
7AAAFADE2C4D23620050410D /* ACImageSliderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AAAFADD2C4D23620050410D /* ACImageSliderModel.swift */; };
|
||||
7AABB1F2267E9CC800D7AB32 /* SwiftDate in Frameworks */ = {isa = PBXBuildFile; productRef = 7AABB1F1267E9CC800D7AB32 /* SwiftDate */; };
|
||||
7AABDE26253350C30041AFC6 /* RxSectionedDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */; };
|
||||
7AB0EF812C5CC0FE00291EE6 /* SwiftLocationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB0EF802C5CC0FE00291EE6 /* SwiftLocationProtocol.swift */; };
|
||||
7AB0EF892C5D307600291EE6 /* LocationServiceStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB0EF882C5D307600291EE6 /* LocationServiceStub.swift */; };
|
||||
7AB5871D2C42C1CF00FA7B66 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 7AB5871C2C42C1CF00FA7B66 /* RealmSwift */; };
|
||||
7AB587322C42D38E00FA7B66 /* StorageServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB587312C42D38E00FA7B66 /* StorageServiceProtocol.swift */; };
|
||||
7AB587342C42D3FA00FA7B66 /* StorageService+Notes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB587332C42D3FA00FA7B66 /* StorageService+Notes.swift */; };
|
||||
@ -274,6 +278,8 @@
|
||||
7A17CE4B2A2E850200626A6E /* UISegmentedControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISegmentedControl.swift; sourceTree = "<group>"; };
|
||||
7A1CF81529A42117007962DA /* Realm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Realm.swift; sourceTree = "<group>"; };
|
||||
7A1DC38D2517ED98002E9C99 /* BlockBarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockBarButtonItem.swift; sourceTree = "<group>"; };
|
||||
7A22B6EA2C67FDEA00E60173 /* GeocoderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeocoderMock.swift; sourceTree = "<group>"; };
|
||||
7A22B6EB2C67FDEA00E60173 /* SwiftLocationMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLocationMock.swift; sourceTree = "<group>"; };
|
||||
7A27ADC6249D43210035F39E /* RegionsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionsController.swift; sourceTree = "<group>"; };
|
||||
7A27ADF2249F8B650035F39E /* RecordsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordsController.swift; sourceTree = "<group>"; };
|
||||
7A27ADF4249FD2F90035F39E /* FileManagerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManagerExt.swift; sourceTree = "<group>"; };
|
||||
@ -374,6 +380,8 @@
|
||||
7AAAFADD2C4D23620050410D /* ACImageSliderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACImageSliderModel.swift; sourceTree = "<group>"; };
|
||||
7AABDE25253350C30041AFC6 /* RxSectionedDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RxSectionedDataSource.swift; sourceTree = "<group>"; };
|
||||
7AAE6AD224CDDF950023860B /* VehicleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleEvent.swift; sourceTree = "<group>"; };
|
||||
7AB0EF802C5CC0FE00291EE6 /* SwiftLocationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLocationProtocol.swift; sourceTree = "<group>"; };
|
||||
7AB0EF882C5D307600291EE6 /* LocationServiceStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationServiceStub.swift; sourceTree = "<group>"; };
|
||||
7AB562B9249C9E9B00473D53 /* VehicleRegion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VehicleRegion.swift; sourceTree = "<group>"; };
|
||||
7AB587222C42D27F00FA7B66 /* AutoCatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AutoCatTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7AB587312C42D38E00FA7B66 /* StorageServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageServiceProtocol.swift; sourceTree = "<group>"; };
|
||||
@ -641,6 +649,15 @@
|
||||
path = NotesScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7A22B6EC2C67FDEA00E60173 /* Mocks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7A22B6EA2C67FDEA00E60173 /* GeocoderMock.swift */,
|
||||
7A22B6EB2C67FDEA00E60173 /* SwiftLocationMock.swift */,
|
||||
);
|
||||
path = Mocks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7A3F07A924360D9100E59687 /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -718,6 +735,7 @@
|
||||
7A60D24C2C5A9D4900D13F7B /* LocationService.swift */,
|
||||
7A60D24E2C5A9DA800D13F7B /* LocationServiceProtocol.swift */,
|
||||
7A60D2502C5A9E4200D13F7B /* GeocoderProtocol.swift */,
|
||||
7AB0EF802C5CC0FE00291EE6 /* SwiftLocationProtocol.swift */,
|
||||
);
|
||||
path = LocationService;
|
||||
sourceTree = "<group>";
|
||||
@ -857,6 +875,7 @@
|
||||
children = (
|
||||
7AB587362C42E3EC00FA7B66 /* StorageServiceStub.swift */,
|
||||
7A176DB12C43071A00999D6B /* ApiServiceStub.swift */,
|
||||
7AB0EF882C5D307600291EE6 /* LocationServiceStub.swift */,
|
||||
);
|
||||
path = Preview;
|
||||
sourceTree = "<group>";
|
||||
@ -910,6 +929,7 @@
|
||||
7AF6D2292677C3950086EA64 /* Extensions */,
|
||||
7A11474523FF2A9000B424AF /* Models */,
|
||||
7AF6D20D2677C0C30086EA64 /* Utils */,
|
||||
7A22B6EC2C67FDEA00E60173 /* Mocks */,
|
||||
7AF6D1F12677C03B0086EA64 /* AutoCatCore.h */,
|
||||
7AF6D1F22677C03B0086EA64 /* Info.plist */,
|
||||
);
|
||||
@ -1269,6 +1289,7 @@
|
||||
7A64AE732469DFB600ABE48E /* DismissAnimationController.swift in Sources */,
|
||||
7ADF6C97250F41B000F237B2 /* PNKeyboard.swift in Sources */,
|
||||
7A1022702C551EFD00B84627 /* LocationEditCoordinator.swift in Sources */,
|
||||
7AB0EF892C5D307600291EE6 /* LocationServiceStub.swift in Sources */,
|
||||
7A7158042C43EAA200852088 /* OwnersCoordinator.swift in Sources */,
|
||||
7A7547E024032CB6004E8406 /* VehiclePhotoCell.swift in Sources */,
|
||||
7A17CE4C2A2E850200626A6E /* UISegmentedControl.swift in Sources */,
|
||||
@ -1294,11 +1315,14 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
7A5D84C22C1AE5C900C2209B /* VehicleModel.swift in Sources */,
|
||||
7A22B6ED2C67FDEA00E60173 /* SwiftLocationMock.swift in Sources */,
|
||||
7A22B6EE2C67FDEA00E60173 /* GeocoderMock.swift in Sources */,
|
||||
7A5D84B92C1AD3C200C2209B /* DtoConvertible.swift in Sources */,
|
||||
7AF6D2182677C1680086EA64 /* VehicleAd.swift in Sources */,
|
||||
7A761C08267E8EA20005F28F /* JWT.swift in Sources */,
|
||||
7A64A2242C1A07EA00284124 /* Formatters.swift in Sources */,
|
||||
7A5D84C02C1AE4DC00C2209B /* VehicleEngine.swift in Sources */,
|
||||
7AB0EF812C5CC0FE00291EE6 /* SwiftLocationProtocol.swift in Sources */,
|
||||
7AF6D2282677C2DC0086EA64 /* Constants.swift in Sources */,
|
||||
7A64A2182C19E64800284124 /* VehicleOwnershipPeriodDto.swift in Sources */,
|
||||
7A599C3B2C18B36A00D47C18 /* FbVerifyTokenModel.swift in Sources */,
|
||||
@ -1543,16 +1567,17 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 132;
|
||||
CURRENT_PROJECT_VERSION = 133;
|
||||
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
||||
INFOPLIST_FILE = AutoCat/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = AutoCat;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.AutoCat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@ -1569,16 +1594,17 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_ENTITLEMENTS = AutoCat/AutoCat.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 132;
|
||||
CURRENT_PROJECT_VERSION = 133;
|
||||
DEVELOPMENT_TEAM = 46DTTB8X4S;
|
||||
INFOPLIST_FILE = AutoCat/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = AutoCat;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.AutoCat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@ -1601,8 +1627,9 @@
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.AutoCatCoreTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@ -1625,8 +1652,9 @@
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.AutoCatCoreTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@ -1649,8 +1677,9 @@
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.AutoCatTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@ -1675,8 +1704,9 @@
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.AutoCatTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@ -1700,12 +1730,13 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = AutoCatCore/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.AutoCatCore;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
@ -1730,12 +1761,13 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = AutoCatCore/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@loader_path/Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = pro.aliencat.AutoCatCore;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
|
||||
@ -33,8 +33,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/Kolos65/Mockable",
|
||||
"state" : {
|
||||
"revision" : "81ccaead99a3c038c09345caa2888ae74b644ee9",
|
||||
"version" : "0.0.9"
|
||||
"revision" : "da977ecb20974c4b1cf185f5fd38771b2d4674fb",
|
||||
"version" : "0.0.10"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -51,8 +51,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/realm/realm-core.git",
|
||||
"state" : {
|
||||
"revision" : "f3d7ae5f9f31d90b327a64536bb7801cc69fd85b",
|
||||
"version" : "14.9.0"
|
||||
"revision" : "c2552e1d36867cb42b28130e894a81fc17081062",
|
||||
"version" : "14.12.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -60,17 +60,17 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/realm/realm-swift.git",
|
||||
"state" : {
|
||||
"revision" : "4c4413abd0cd2221f59318f800960fe5bddc1494",
|
||||
"version" : "10.51.0"
|
||||
"revision" : "5221a83dc720823e3017493394d00d49ccf13446",
|
||||
"version" : "10.52.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-syntax",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-syntax.git",
|
||||
"location" : "https://github.com/swiftlang/swift-syntax.git",
|
||||
"state" : {
|
||||
"revision" : "303e5c5c36d6a558407d364878df131c3546fad8",
|
||||
"version" : "510.0.2"
|
||||
"revision" : "2bc86522d115234d1f588efe2bcb4ce4be8f8b82",
|
||||
"version" : "510.0.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -25,8 +25,10 @@ class AudioRecordCell: UITableViewCell, ConfigurableCell {
|
||||
self.componentsFormatter.allowedUnits = [.minute, .second]
|
||||
self.componentsFormatter.zeroFormattingBehavior = .pad
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.progressView.progress = 0
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
super.prepareForReuse()
|
||||
|
||||
@ -10,9 +10,12 @@ class VehicleNoteCell: UITableViewCell {
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.dateFormatter.dateStyle = .medium
|
||||
self.dateFormatter.timeStyle = .medium
|
||||
}
|
||||
}
|
||||
|
||||
func configure(with note: VehicleNoteDto) {
|
||||
self.noteText.text = note.text
|
||||
|
||||
@ -2,6 +2,7 @@ import UIKit
|
||||
import Kingfisher
|
||||
import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
class VehiclePhotoCell: UICollectionViewCell {
|
||||
@IBOutlet weak var photo: UIImageView!
|
||||
@IBOutlet weak var model: UILabel!
|
||||
@ -11,9 +12,12 @@ class VehiclePhotoCell: UICollectionViewCell {
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.layer.cornerRadius = 8
|
||||
formatter.timeStyle = .none
|
||||
formatter.dateStyle = .medium
|
||||
self.formatter.timeStyle = .none
|
||||
self.formatter.dateStyle = .medium
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
|
||||
27
AutoCat/Preview/LocationServiceStub.swift
Normal file
27
AutoCat/Preview/LocationServiceStub.swift
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// LocationServiceStub.swift
|
||||
// AutoCat
|
||||
//
|
||||
// Created by Selim Mustafaev on 02.08.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AutoCatCore
|
||||
|
||||
final class LocationServiceStub: LocationServiceProtocol {
|
||||
|
||||
let event: VehicleEventDto
|
||||
|
||||
init(event: VehicleEventDto) {
|
||||
self.event = event
|
||||
}
|
||||
|
||||
func getAddressForLocation(latitude: Double, longitude: Double) async throws -> String {
|
||||
event.address ?? ""
|
||||
}
|
||||
|
||||
func requestCurrentLocation() async throws -> AutoCatCore.VehicleEventDto {
|
||||
event
|
||||
}
|
||||
}
|
||||
@ -23,7 +23,7 @@ final class LocationPickerCoordinator: Coordinator {
|
||||
|
||||
func start() async throws -> VehicleEventDto? {
|
||||
|
||||
let viewModel = LocationPickerViewModel(event: event)
|
||||
let viewModel = LocationPickerViewModel(event: event, locationService: LocationService.shared)
|
||||
let screen = LocationPickerScreen(viewModel: viewModel)
|
||||
let controller = CustomHostingController(rootView: screen)
|
||||
viewController?.pushViewController(controller, animated: true)
|
||||
|
||||
@ -18,15 +18,22 @@ struct LocationPickerScreen: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Map(coordinateRegion: $viewModel.region)
|
||||
Map(initialPosition: viewModel.position)
|
||||
.mapControls {
|
||||
MapUserLocationButton()
|
||||
}
|
||||
.onMapCameraChange(frequency: .onEnd) { context in
|
||||
Task { await viewModel.updateEvent(center: context.region.center) }
|
||||
}
|
||||
|
||||
Image(systemName: "mappin.and.ellipse")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 48)
|
||||
.offset(.init(width: 0, height: -10))
|
||||
.offset(.init(width: 0, height: -16))
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
//.ignoresSafeArea()
|
||||
.navigationTitle(viewModel.event.location)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
@ -44,5 +51,9 @@ struct LocationPickerScreen: View {
|
||||
var event = VehicleEventDto(lat: 47.250049, lon: 39.711821)
|
||||
event.address = "Ул. Ленина, 123"
|
||||
|
||||
return LocationPickerScreen(viewModel: .init(event: event))
|
||||
let locationService = LocationServiceStub(event: event)
|
||||
let viewModel = LocationPickerViewModel(event: event,
|
||||
locationService: locationService)
|
||||
|
||||
return LocationPickerScreen(viewModel: viewModel)
|
||||
}
|
||||
|
||||
@ -14,58 +14,30 @@ import SwiftUI
|
||||
@MainActor
|
||||
final class LocationPickerViewModel: ObservableObject {
|
||||
|
||||
let locationService: LocationServiceProtocol
|
||||
|
||||
@Published var event: VehicleEventDto
|
||||
@Published var region: MKCoordinateRegion {
|
||||
didSet {
|
||||
Task { await updateEvent(region: region) }
|
||||
}
|
||||
}
|
||||
@Published var position: MapCameraPosition
|
||||
|
||||
var result: VehicleEventDto?
|
||||
|
||||
var geocodingTask: Task<Void, Error>?
|
||||
|
||||
init(event: VehicleEventDto) {
|
||||
init(event: VehicleEventDto, locationService: LocationServiceProtocol) {
|
||||
self.event = event
|
||||
|
||||
let center = CLLocationCoordinate2D(latitude: event.latitude, longitude: event.longitude)
|
||||
self.region = MKCoordinateRegion(center: center,
|
||||
latitudinalMeters: 1000,
|
||||
longitudinalMeters: 1000)
|
||||
self.locationService = locationService
|
||||
|
||||
if event.latitude == 0 && event.longitude == 0 {
|
||||
Task { await moveToCurrentLocation() }
|
||||
self.position = .userLocation(fallback: .automatic)
|
||||
} else {
|
||||
let center = CLLocationCoordinate2D(latitude: event.latitude, longitude: event.longitude)
|
||||
self.position = .region(.init(center: center, latitudinalMeters: 1000, longitudinalMeters: 1000))
|
||||
}
|
||||
}
|
||||
|
||||
func moveToCurrentLocation() async {
|
||||
do {
|
||||
let currentEvent = try await RxLocationManager.requestCurrentLocation()
|
||||
let center = CLLocationCoordinate2D(latitude: currentEvent.latitude,
|
||||
longitude: currentEvent.longitude)
|
||||
self.region = MKCoordinateRegion(center: center,
|
||||
latitudinalMeters: 1000,
|
||||
longitudinalMeters: 1000)
|
||||
|
||||
event.latitude = currentEvent.latitude
|
||||
event.longitude = currentEvent.longitude
|
||||
event.address = try? await RxLocationManager.getAddressForLocation(latitude: region.center.latitude,
|
||||
longitude: region.center.longitude)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
func updateEvent(region: MKCoordinateRegion) async {
|
||||
geocodingTask?.cancel()
|
||||
geocodingTask = Task {
|
||||
event.latitude = region.center.latitude
|
||||
event.longitude = region.center.longitude
|
||||
try await Task.sleep(nanoseconds: 500_000_000)
|
||||
event.address = try? await RxLocationManager.getAddressForLocation(latitude: region.center.latitude,
|
||||
longitude: region.center.longitude)
|
||||
geocodingTask = nil
|
||||
}
|
||||
func updateEvent(center: CLLocationCoordinate2D) async {
|
||||
event.latitude = center.latitude
|
||||
event.longitude = center.longitude
|
||||
event.address = try? await locationService.getAddressForLocation(latitude: center.latitude,
|
||||
longitude: center.longitude)
|
||||
}
|
||||
|
||||
func done() {
|
||||
|
||||
45
AutoCatCore/Mocks/SwiftLocationMock.swift
Normal file
45
AutoCatCore/Mocks/SwiftLocationMock.swift
Normal file
@ -0,0 +1,45 @@
|
||||
//
|
||||
// SwiftLocationMock.swift
|
||||
// AutoCatCoreTests
|
||||
//
|
||||
// Created by Selim Mustafaev on 02.08.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import AutoCatCore
|
||||
import CoreLocation
|
||||
import SwiftLocation
|
||||
|
||||
final class SwiftLocationMock {
|
||||
|
||||
var authorizationStatus: CLAuthorizationStatus = .notDetermined
|
||||
var requestedStatus: CLAuthorizationStatus = .notDetermined
|
||||
var location: CLLocation?
|
||||
|
||||
var requestLocationTime: TimeInterval = 0
|
||||
var requestLocationCount = 0
|
||||
}
|
||||
|
||||
extension SwiftLocationMock: SwiftLocationProtocol {
|
||||
|
||||
func requestPermission(_ permission: LocationPermission) async throws -> CLAuthorizationStatus {
|
||||
authorizationStatus = requestedStatus
|
||||
return requestedStatus
|
||||
}
|
||||
|
||||
func requestLocation(accuracy filters: AccuracyFilters?,
|
||||
timeout: TimeInterval?) async throws -> Tasks.ContinuousUpdateLocation.StreamEvent {
|
||||
|
||||
requestLocationCount += 1
|
||||
|
||||
if requestLocationTime > 0 {
|
||||
try await Task.sleep(nanoseconds: UInt64(requestLocationTime*1_000_000_000))
|
||||
}
|
||||
|
||||
if let location {
|
||||
return .didUpdateLocations([location])
|
||||
} else {
|
||||
return .didUpdateLocations([])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,16 +7,53 @@
|
||||
//
|
||||
|
||||
import CoreLocation
|
||||
import SwiftLocation
|
||||
|
||||
@MainActor
|
||||
public final class LocationService: LocationServiceProtocol {
|
||||
public final class LocationService {
|
||||
|
||||
private var geocoder: GeocoderProtocol
|
||||
public static let shared = LocationService(geocoder: CLGeocoder(),
|
||||
locationManager: Location())
|
||||
|
||||
public init(geocoder: GeocoderProtocol) {
|
||||
private let geocoder: GeocoderProtocol
|
||||
private var locationManager: SwiftLocationProtocol
|
||||
|
||||
private var eventTask: Task<VehicleEventDto,Error>?
|
||||
|
||||
public init(geocoder: GeocoderProtocol, locationManager: SwiftLocationProtocol) {
|
||||
self.geocoder = geocoder
|
||||
self.locationManager = locationManager
|
||||
}
|
||||
|
||||
private func checkPermissions() async throws {
|
||||
|
||||
switch locationManager.authorizationStatus {
|
||||
case .authorizedWhenInUse, .authorizedAlways:
|
||||
break
|
||||
case .notDetermined:
|
||||
_ = try await locationManager.requestPermission(.always)
|
||||
try await checkPermissions()
|
||||
case .denied:
|
||||
throw CLError(.denied)
|
||||
default:
|
||||
throw LocationError.permission
|
||||
}
|
||||
}
|
||||
|
||||
private func requestLocation() async throws -> VehicleEventDto {
|
||||
try await checkPermissions()
|
||||
let locationEvent = try await locationManager.requestLocation(accuracy: nil, timeout: 20)
|
||||
|
||||
guard let coordinate = locationEvent.location?.coordinate else {
|
||||
throw LocationError.generic
|
||||
}
|
||||
|
||||
return VehicleEventDto(lat: coordinate.latitude, lon: coordinate.longitude)
|
||||
}
|
||||
}
|
||||
|
||||
extension LocationService: LocationServiceProtocol {
|
||||
|
||||
public func getAddressForLocation(latitude: Double, longitude: Double) async throws -> String {
|
||||
|
||||
let location = CLLocation(latitude: latitude, longitude: longitude)
|
||||
@ -28,4 +65,20 @@ public final class LocationService: LocationServiceProtocol {
|
||||
throw LocationError.reverseGeocode
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func requestCurrentLocation() async throws -> VehicleEventDto {
|
||||
|
||||
if let eventTask {
|
||||
return try await eventTask.value
|
||||
} else {
|
||||
let task = Task {
|
||||
let location = try await requestLocation()
|
||||
eventTask = nil
|
||||
return location
|
||||
}
|
||||
eventTask = task
|
||||
return try await task.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,9 +6,9 @@
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
public protocol LocationServiceProtocol {
|
||||
|
||||
func getAddressForLocation(latitude: Double, longitude: Double) async throws -> String
|
||||
func requestCurrentLocation() async throws -> VehicleEventDto
|
||||
}
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
//
|
||||
// SwiftLocationProtocol.swift
|
||||
// AutoCatCore
|
||||
//
|
||||
// Created by Selim Mustafaev on 02.08.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftLocation
|
||||
import CoreLocation
|
||||
|
||||
public protocol SwiftLocationProtocol {
|
||||
|
||||
var authorizationStatus: CLAuthorizationStatus { get }
|
||||
|
||||
func requestPermission(_ permission: LocationPermission) async throws -> CLAuthorizationStatus
|
||||
func requestLocation(accuracy filters: AccuracyFilters?,
|
||||
timeout: TimeInterval?) async throws -> Tasks.ContinuousUpdateLocation.StreamEvent
|
||||
}
|
||||
|
||||
extension Location: SwiftLocationProtocol { }
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
import Testing
|
||||
import CoreLocation
|
||||
import AutoCatCore
|
||||
@testable import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
struct LocationServiceTests {
|
||||
@ -18,10 +18,12 @@ struct LocationServiceTests {
|
||||
let address = "Test Address"
|
||||
|
||||
let geocoder = GeocoderMock()
|
||||
let locationManager = SwiftLocationMock()
|
||||
let locationService: LocationService
|
||||
|
||||
init() {
|
||||
self.locationService = LocationService(geocoder: geocoder)
|
||||
self.locationService = LocationService(geocoder: geocoder,
|
||||
locationManager: locationManager)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -58,4 +60,85 @@ struct LocationServiceTests {
|
||||
longitude: longitude)
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Get location: denied")
|
||||
func getLocationDenied() async throws {
|
||||
|
||||
locationManager.authorizationStatus = .denied
|
||||
|
||||
await #expect(throws: CLError(.denied)) {
|
||||
_ = try await locationService.requestCurrentLocation()
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Get location: not determined -> denied")
|
||||
func getLocationNotDeterminedDenied() async throws {
|
||||
|
||||
locationManager.authorizationStatus = .notDetermined
|
||||
locationManager.requestedStatus = .denied
|
||||
|
||||
await #expect(throws: CLError(.denied)) {
|
||||
_ = try await locationService.requestCurrentLocation()
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Get location: not determined -> allow")
|
||||
func getLocationNotDeterminedAllow() async throws {
|
||||
|
||||
locationManager.authorizationStatus = .notDetermined
|
||||
locationManager.requestedStatus = .authorizedWhenInUse
|
||||
locationManager.location = CLLocation(latitude: latitude, longitude: longitude)
|
||||
|
||||
let event = try await locationService.requestCurrentLocation()
|
||||
|
||||
#expect(event.latitude == latitude)
|
||||
#expect(event.longitude == longitude)
|
||||
}
|
||||
|
||||
@Test("Get location: normal")
|
||||
func getLocationNormal() async throws {
|
||||
|
||||
locationManager.authorizationStatus = .authorizedWhenInUse
|
||||
locationManager.location = CLLocation(latitude: latitude, longitude: longitude)
|
||||
|
||||
let event = try await locationService.requestCurrentLocation()
|
||||
|
||||
#expect(event.latitude == latitude)
|
||||
#expect(event.longitude == longitude)
|
||||
}
|
||||
|
||||
@Test("Get location: no location")
|
||||
func getLocationNone() async throws {
|
||||
|
||||
locationManager.authorizationStatus = .authorizedWhenInUse
|
||||
|
||||
await #expect(throws: LocationError.generic) {
|
||||
_ = try await locationService.requestCurrentLocation()
|
||||
}
|
||||
}
|
||||
|
||||
@Test("Get location: parallel requests")
|
||||
func getLocationParallel() async throws {
|
||||
|
||||
locationManager.authorizationStatus = .authorizedWhenInUse
|
||||
locationManager.location = CLLocation(latitude: latitude, longitude: longitude)
|
||||
locationManager.requestLocationTime = 1
|
||||
|
||||
async let task1 = locationService.requestCurrentLocation()
|
||||
async let task2 = locationService.requestCurrentLocation()
|
||||
|
||||
try await Task.sleep(nanoseconds: 1_500_000_000)
|
||||
async let task3 = locationService.requestCurrentLocation()
|
||||
|
||||
let (event1, event2, event3) = try await (task1, task2, task3)
|
||||
|
||||
#expect(locationManager.requestLocationCount == 2)
|
||||
|
||||
#expect(event1.latitude == latitude)
|
||||
#expect(event1.longitude == longitude)
|
||||
#expect(event2.latitude == latitude)
|
||||
#expect(event2.longitude == longitude)
|
||||
#expect(event3.latitude == latitude)
|
||||
#expect(event3.longitude == longitude)
|
||||
}
|
||||
}
|
||||
|
||||
66
AutoCatTests/LocationPickerTests.swift
Normal file
66
AutoCatTests/LocationPickerTests.swift
Normal file
@ -0,0 +1,66 @@
|
||||
//
|
||||
// LocationPickerTests.swift
|
||||
// AutoCatTests
|
||||
//
|
||||
// Created by Selim Mustafaev on 10.08.2024.
|
||||
// Copyright © 2024 Selim Mustafaev. All rights reserved.
|
||||
//
|
||||
|
||||
import Testing
|
||||
import CoreLocation
|
||||
|
||||
@testable import AutoCat
|
||||
@testable import AutoCatCore
|
||||
|
||||
@MainActor
|
||||
struct LocationPickerTests {
|
||||
|
||||
let latitude: CLLocationDegrees = 10
|
||||
let longitude: CLLocationDegrees = 10
|
||||
let address = "Test Address"
|
||||
|
||||
let geocoder = GeocoderMock()
|
||||
let locationManager = SwiftLocationMock()
|
||||
let locationService: LocationService
|
||||
|
||||
init() {
|
||||
self.locationService = LocationService(geocoder: geocoder,
|
||||
locationManager: locationManager)
|
||||
}
|
||||
|
||||
@Test("Set initial location (user)")
|
||||
func setInitialLocationUser() async throws {
|
||||
|
||||
let viewModel = LocationPickerViewModel(event: .init(lat: 0, lon: 0),
|
||||
locationService: locationService)
|
||||
|
||||
#expect(viewModel.position == .userLocation(fallback: .automatic))
|
||||
}
|
||||
|
||||
@Test("Set initial location (custom)")
|
||||
func setInitialLocationCustom() async throws {
|
||||
|
||||
let viewModel = LocationPickerViewModel(event: .init(lat: latitude, lon: longitude),
|
||||
locationService: locationService)
|
||||
|
||||
#expect(viewModel.position.region?.center.latitude == latitude)
|
||||
#expect(viewModel.position.region?.center.longitude == longitude)
|
||||
}
|
||||
|
||||
@Test("Update event")
|
||||
func updateEvent() async throws {
|
||||
|
||||
let viewModel = LocationPickerViewModel(event: .init(lat: 0, lon: 0),
|
||||
locationService: locationService)
|
||||
|
||||
geocoder.addLocation(latitude: latitude,
|
||||
longitude: longitude,
|
||||
address: address)
|
||||
|
||||
await viewModel.updateEvent(center: .init(latitude: latitude, longitude: longitude))
|
||||
|
||||
#expect(viewModel.event.latitude == latitude)
|
||||
#expect(viewModel.event.longitude == longitude)
|
||||
#expect(viewModel.event.address == address)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user