From 2790388a69c62e7ed74c92e66b0868e4b372a43d Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Mon, 8 Aug 2022 20:45:42 +0200 Subject: [PATCH 01/18] feat(develop): use @BindableState and unimplemented to clean tests --- swiftUV/app/UVClient/Mock.swift | 7 +++ swiftUV/app/Views/AppReducer.swift | 15 ++++- swiftUV/app/Views/ContentVIew.swift | 2 +- swiftUVTests/AppReducerTests.swift | 92 +++++++++-------------------- 4 files changed, 49 insertions(+), 67 deletions(-) diff --git a/swiftUV/app/UVClient/Mock.swift b/swiftUV/app/UVClient/Mock.swift index 783c951..597fc67 100644 --- a/swiftUV/app/UVClient/Mock.swift +++ b/swiftUV/app/UVClient/Mock.swift @@ -20,4 +20,11 @@ extension UVClient { } ) } + +extension UVClient { + static let unimplemented = Self( + fetchUVIndex: { _ in .unimplemented("\(Self.self).fetchUVIndex)")}, + fetchCityName: { _ in .unimplemented("\(Self.self).fetchCityName")} + ) +} #endif diff --git a/swiftUV/app/Views/AppReducer.swift b/swiftUV/app/Views/AppReducer.swift index c781081..2b8a85a 100644 --- a/swiftUV/app/Views/AppReducer.swift +++ b/swiftUV/app/Views/AppReducer.swift @@ -14,15 +14,16 @@ struct AppState: Equatable { var cityName = "loading" var weatherRequestInFlight = false var getCityNameRequestInFlight = false - var shouldShowErrorPopup = false var errorText = "" var userLocation: Location? var isRequestingCurrentLocation = false var isLocationRefused = false + + @BindableState var shouldShowErrorPopup = false } -enum AppAction: Equatable { +enum AppAction: Equatable, BindableAction { case getUVRequest case getUVResponse(Result) case getCityNameResponse(Result) @@ -30,6 +31,7 @@ enum AppAction: Equatable { case onAppear case locationManager(LocationManager.Action) + case binding(BindingAction) } struct AppEnvironment { @@ -133,6 +135,13 @@ let appReducer = Reducer { state, action, e state.shouldShowErrorPopup = false return .none + case .binding(\.$shouldShowErrorPopup): + state.shouldShowErrorPopup = false + return .none + + case .binding: + return .none + case .locationManager: return .none } @@ -142,3 +151,5 @@ let appReducer = Reducer { state, action, e locationManagerReducer .pullback(state: \.self, action: /AppAction.self, environment: { $0 }) ) +.binding() +.debug() diff --git a/swiftUV/app/Views/ContentVIew.swift b/swiftUV/app/Views/ContentVIew.swift index 15b4b5f..e7f0ace 100644 --- a/swiftUV/app/Views/ContentVIew.swift +++ b/swiftUV/app/Views/ContentVIew.swift @@ -78,7 +78,7 @@ struct ContentView: View { .redacted(reason: viewStore.weatherRequestInFlight ? .placeholder : []) } } - .alert(isPresented: viewStore.binding(get: \.shouldShowErrorPopup, send: .dismissErrorPopup)) { + .alert(isPresented: viewStore.binding(\.$shouldShowErrorPopup)) { Alert(title: Text("app.label.error"), message: Text(viewStore.errorText)) } .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in diff --git a/swiftUVTests/AppReducerTests.swift b/swiftUVTests/AppReducerTests.swift index cd3c607..4b7e061 100644 --- a/swiftUVTests/AppReducerTests.swift +++ b/swiftUVTests/AppReducerTests.swift @@ -15,7 +15,6 @@ import XCTest class AppReducerTests: XCTestCase { func testGetUVRequestSuccess() { - let mainQueue = DispatchQueue.test let expectedForecast = Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5) let store = TestStore( initialState: @@ -24,27 +23,23 @@ class AppReducerTests: XCTestCase { userLocation: Location(latitude: 12.0, longitude: 13.0) ), reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: mainQueue.eraseToAnyScheduler(), - locationManager: .failing - ) + environment: .unimplemented ) + store.environment.uvClient.fetchUVIndex = { _ in Effect(value: Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5)) } + store.environment.uvClient.fetchCityName = { _ in Effect(value: "Gueugnon") } + store.environment.dispatchQueue = .immediate + store.send(.getUVRequest) { $0.weatherRequestInFlight = true $0.getCityNameRequestInFlight = true } - mainQueue.advance() - store.receive(.getUVResponse(.success(expectedForecast))) { $0.weatherRequestInFlight = false $0.uvIndex = 5 } - mainQueue.advance() - store.receive(.getCityNameResponse(.success("Gueugnon"))) { $0.getCityNameRequestInFlight = false $0.cityName = "Gueugnon" @@ -52,7 +47,6 @@ class AppReducerTests: XCTestCase { } func testGetUVRequestFailure() { - let mainQueue = DispatchQueue.test let store = TestStore( initialState: AppState( @@ -60,20 +54,17 @@ class AppReducerTests: XCTestCase { userLocation: Location(latitude: 12.0, longitude: 13.0) ), reducer: appReducer, - environment: AppEnvironment( - uvClient: UVClient(fetchUVIndex: { _ in return Effect(error: UVClient.Failure(errorDescription: "test"))}, fetchCityName: { _ in fatalError()}), - dispatchQueue: mainQueue.eraseToAnyScheduler(), - locationManager: .failing - ) + environment: .unimplemented ) + store.environment.uvClient.fetchUVIndex = { _ in Effect(error: UVClient.Failure(errorDescription: "test")) } + store.environment.dispatchQueue = .immediate + store.send(.getUVRequest) { $0.weatherRequestInFlight = true $0.getCityNameRequestInFlight = true } - mainQueue.advance() - store.receive(.getUVResponse(.failure(UVClient.Failure(errorDescription: "test")))) { $0.weatherRequestInFlight = false $0.shouldShowErrorPopup = true @@ -89,11 +80,7 @@ class AppReducerTests: XCTestCase { getCityNameRequestInFlight: true ), reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: DispatchQueue.main.eraseToAnyScheduler(), - locationManager: .failing - ) + environment: .unimplemented ) store.send(.getUVRequest) { @@ -104,7 +91,6 @@ class AppReducerTests: XCTestCase { } func testGetUVResponseSuccess() { - let mainQueue = DispatchQueue.test let expectedForecast = Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5) let store = TestStore( initialState: @@ -113,20 +99,17 @@ class AppReducerTests: XCTestCase { userLocation: Location(latitude: 12.0, longitude: 13.0) ), reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: mainQueue.eraseToAnyScheduler(), - locationManager: .failing - ) + environment: .unimplemented ) + store.environment.uvClient.fetchCityName = { _ in Effect(value: "Gueugnon") } + store.environment.dispatchQueue = .immediate + store.send(.getUVResponse(.success(expectedForecast))) { $0.weatherRequestInFlight = false $0.uvIndex = 5 } - mainQueue.advance() - store.receive(.getCityNameResponse(.success("Gueugnon"))) { $0.cityName = "Gueugnon" $0.getCityNameRequestInFlight = false @@ -141,11 +124,7 @@ class AppReducerTests: XCTestCase { getCityNameRequestInFlight: true ), reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: DispatchQueue.test.eraseToAnyScheduler(), - locationManager: .failing - ) + environment: .unimplemented ) store.send(.getUVResponse(.success(expectedForecast))) { @@ -162,11 +141,7 @@ class AppReducerTests: XCTestCase { getCityNameRequestInFlight: true ), reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: DispatchQueue.test.eraseToAnyScheduler(), - locationManager: .failing - ) + environment: .unimplemented ) store.send(.getUVResponse(.failure(UVClient.Failure(errorDescription: "test")))) { @@ -179,16 +154,9 @@ class AppReducerTests: XCTestCase { func testGetCityNameResponseSuccess() { let store = TestStore( - initialState: - AppState( - getCityNameRequestInFlight: true - ), + initialState: .init(), reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: DispatchQueue.test.eraseToAnyScheduler(), - locationManager: .failing - ) + environment: .unimplemented ) store.send(.getCityNameResponse(.success("Gueugnon"))) { @@ -199,16 +167,9 @@ class AppReducerTests: XCTestCase { func testGetCityNameResponseFailure() { let store = TestStore( - initialState: - AppState( - getCityNameRequestInFlight: true - ), + initialState: .init(), reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: DispatchQueue.test.eraseToAnyScheduler(), - locationManager: .failing - ) + environment: .unimplemented ) store.send(.getCityNameResponse(.failure(UVClient.Failure(errorDescription: "test")))) { @@ -224,11 +185,7 @@ class AppReducerTests: XCTestCase { shouldShowErrorPopup: true ), reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: DispatchQueue.test.eraseToAnyScheduler(), - locationManager: .failing - ) + environment: .unimplemented ) store.send(.dismissErrorPopup) { @@ -236,3 +193,10 @@ class AppReducerTests: XCTestCase { } } } +extension AppEnvironment { + static let unimplemented = Self( + uvClient: .unimplemented, + dispatchQueue: .unimplemented, + locationManager: .failing + ) +} From ecd40f8747fe69d06a08fb830498999a8a65c8df Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Wed, 10 Aug 2022 22:48:45 +0200 Subject: [PATCH 02/18] feat(develop): update TCA to 0.39 to use async/await + min iOS version to 15 --- Podfile | 2 +- Podfile.lock | 2 +- swiftUV.xcodeproj/project.pbxproj | 14 +- .../xcshareddata/xcschemes/swiftUV.xcscheme | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 +- swiftUV/App.swift | 1 - swiftUV/app/UVClient/Live.swift | 24 ++-- swiftUV/app/UVClient/Mock.swift | 9 +- swiftUV/app/UVClient/UVClient.swift | 4 +- swiftUV/app/Views/AppReducer.swift | 48 +++---- swiftUV/app/Views/ContentVIew.swift | 6 +- swiftUV/app/Views/LocationReducer.swift | 27 +--- swiftUVTests/AppReducerTests.swift | 129 +++++------------- 13 files changed, 96 insertions(+), 180 deletions(-) diff --git a/Podfile b/Podfile index 8c25960..a8e3015 100644 --- a/Podfile +++ b/Podfile @@ -21,7 +21,7 @@ plugin 'cocoapods-keys', { post_install do |pi| pi.pods_project.targets.each do |t| t.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' end end end diff --git a/Podfile.lock b/Podfile.lock index 47a67a8..ff005d7 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -18,6 +18,6 @@ SPEC CHECKSUMS: Keys: a576f4c9c1c641ca913a959a9c62ed3f215a8de9 SwiftLint: 284cea64b6187c5d6bd83e9a548a64104d546447 -PODFILE CHECKSUM: 05532806187603e2d3d823648e92d6ec2366b8e7 +PODFILE CHECKSUM: a4a78f1ad95aec9fc5bc5c53a54a54098520791d COCOAPODS: 1.11.3 diff --git a/swiftUV.xcodeproj/project.pbxproj b/swiftUV.xcodeproj/project.pbxproj index 5726781..a0e4326 100644 --- a/swiftUV.xcodeproj/project.pbxproj +++ b/swiftUV.xcodeproj/project.pbxproj @@ -465,7 +465,7 @@ DEVELOPMENT_TEAM = XDX42S5M9N; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = swiftUVTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -501,7 +501,7 @@ DEVELOPMENT_TEAM = XDX42S5M9N; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = swiftUVTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -567,7 +567,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -620,7 +620,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; @@ -641,7 +641,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = XDX42S5M9N; INFOPLIST_FILE = swiftUV/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -666,7 +666,7 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = XDX42S5M9N; INFOPLIST_FILE = swiftUV/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -721,7 +721,7 @@ repositoryURL = "https://github.com/pointfreeco/swift-composable-architecture"; requirement = { kind = exactVersion; - version = 0.38.3; + version = 0.39.0; }; }; E9AF349D289C58DD00C0F763 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */ = { diff --git a/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme b/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme index fab2342..38cc31c 100644 --- a/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme +++ b/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme @@ -71,7 +71,7 @@ diff --git a/swiftUV.xcworkspace/xcshareddata/swiftpm/Package.resolved b/swiftUV.xcworkspace/xcshareddata/swiftpm/Package.resolved index ad118c9..998e0b1 100644 --- a/swiftUV.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/swiftUV.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/combine-schedulers", "state" : { - "revision" : "0ba3a562716efabb585ccb169450c5389107286b", - "version" : "0.6.0" + "revision" : "f7c8277f05f27a5bfb2f6ecccb0bad126ffcf472", + "version" : "0.7.0" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-composable-architecture", "state" : { - "revision" : "0a38f2c860a64a005afeb8a6fe186eb2818c9a3f", - "version" : "0.38.3" + "revision" : "108e3a536fcebb16c4f247ef92c2d7326baf9fe3", + "version" : "0.39.0" } }, { diff --git a/swiftUV/App.swift b/swiftUV/App.swift index bd8db3a..0740477 100644 --- a/swiftUV/App.swift +++ b/swiftUV/App.swift @@ -23,7 +23,6 @@ struct SwiftUVApp: App { reducer: appReducer, environment: AppEnvironment( uvClient: .live, - dispatchQueue: .main, locationManager: .live ) ) diff --git a/swiftUV/app/UVClient/Live.swift b/swiftUV/app/UVClient/Live.swift index 39d1b9e..a569749 100644 --- a/swiftUV/app/UVClient/Live.swift +++ b/swiftUV/app/UVClient/Live.swift @@ -14,28 +14,28 @@ import Keys extension UVClient { static let live = UVClient( fetchUVIndex: { request in - var request = URLRequest(url: URL(string: K.Api.baseURL + String(format: K.Api.Endpoints.getUV, arguments: [request.lat, request.long, SwiftUVKeys().openWeatherMapApiKey]))!) - request.httpMethod = "GET" - request.httpBody = try? JSONSerialization.data(withJSONObject: [:], options: []) - - return URLSession.shared.dataTaskPublisher(for: request.url!) - .map { data, _ in data } - .decode(type: Forecast.self, decoder: JSONDecoder()) - .mapError { error in Failure(errorDescription: error.localizedDescription) } - .eraseToEffect() + let url = URL( + string: K.Api.baseURL + String(format: K.Api.Endpoints.getUV, arguments: [request.lat, request.long, SwiftUVKeys().openWeatherMapApiKey]))! + do { + let (response, _) = try await URLSession.shared.data(from: url) + let forectast = try JSONDecoder().decode(Forecast.self, from: response) + return forectast + } catch { + throw error + } }, fetchCityName: { location in - Effect.future { callback in + try await withUnsafeThrowingContinuation { continuation in let geocoder = CLGeocoder() let clLocation = CLLocation(latitude: location.latitude, longitude: location.longitude) geocoder.reverseGeocodeLocation(clLocation) { placemarks, error in guard error == nil else { - callback(.failure(Failure(errorDescription: error!.localizedDescription))) + continuation.resume(throwing: error!) return } let cityName = placemarks?.first?.locality ?? "Unknown" - callback(.success(cityName)) + continuation.resume(returning: cityName) } } } diff --git a/swiftUV/app/UVClient/Mock.swift b/swiftUV/app/UVClient/Mock.swift index 597fc67..2895f0e 100644 --- a/swiftUV/app/UVClient/Mock.swift +++ b/swiftUV/app/UVClient/Mock.swift @@ -8,23 +8,24 @@ import Foundation import ComposableArchitecture +import XCTestDynamicOverlay #if DEBUG extension UVClient { static let mock = Self( fetchUVIndex: { _ in - Effect(value: Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5)) + Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5) }, fetchCityName: { _ in - Effect(value: "Gueugnon") + "Gueugnon" } ) } extension UVClient { static let unimplemented = Self( - fetchUVIndex: { _ in .unimplemented("\(Self.self).fetchUVIndex)")}, - fetchCityName: { _ in .unimplemented("\(Self.self).fetchCityName")} + fetchUVIndex: XCTUnimplemented("\(Self.self).fetchUVIndex)"), + fetchCityName: XCTUnimplemented("\(Self.self).fetchCityName") ) } #endif diff --git a/swiftUV/app/UVClient/UVClient.swift b/swiftUV/app/UVClient/UVClient.swift index 2e7d4e2..94d65be 100644 --- a/swiftUV/app/UVClient/UVClient.swift +++ b/swiftUV/app/UVClient/UVClient.swift @@ -14,8 +14,8 @@ struct UVClientRequest { } struct UVClient { - var fetchUVIndex: (UVClientRequest) -> Effect - var fetchCityName: (Location) -> Effect + var fetchUVIndex: @Sendable (UVClientRequest) async throws -> Forecast + var fetchCityName: @Sendable (Location) async throws -> String struct Failure: Error, Equatable { let errorDescription: String diff --git a/swiftUV/app/Views/AppReducer.swift b/swiftUV/app/Views/AppReducer.swift index 2b8a85a..3381cb6 100644 --- a/swiftUV/app/Views/AppReducer.swift +++ b/swiftUV/app/Views/AppReducer.swift @@ -18,6 +18,7 @@ struct AppState: Equatable { var userLocation: Location? var isRequestingCurrentLocation = false + var hasAlreadyRequestLocation = false var isLocationRefused = false @BindableState var shouldShowErrorPopup = false @@ -25,18 +26,17 @@ struct AppState: Equatable { enum AppAction: Equatable, BindableAction { case getUVRequest - case getUVResponse(Result) - case getCityNameResponse(Result) - case dismissErrorPopup + case getUVResponse(TaskResult) + case getCityNameResponse(TaskResult) case onAppear + case onDisappear case locationManager(LocationManager.Action) case binding(BindingAction) } struct AppEnvironment { var uvClient: UVClient - var dispatchQueue: AnySchedulerOf var locationManager: LocationManager } @@ -50,8 +50,6 @@ let appReducer = Reducer { state, action, e switch environment.locationManager.authorizationStatus() { case .notDetermined: - state.isRequestingCurrentLocation = true - return .merge( environment.locationManager .delegate() @@ -83,6 +81,10 @@ let appReducer = Reducer { state, action, e return .none } + case .onDisappear: + state.hasAlreadyRequestLocation = false + return .none + case .getUVRequest: state.weatherRequestInFlight = true state.getCityNameRequestInFlight = true @@ -93,31 +95,27 @@ let appReducer = Reducer { state, action, e return .none } - return environment.uvClient - .fetchUVIndex(UVClientRequest(lat: location.latitude, long: location.longitude)) - .receive(on: environment.dispatchQueue) - .catchToEffect() - .map { .getUVResponse($0) } + return .run { send in + async let fetchUV: Void = send( + .getUVResponse(TaskResult { try await environment.uvClient.fetchUVIndex(UVClientRequest(lat: location.latitude, long: location.longitude)) }) + ) + + async let fetchCityName: Void = send( + .getCityNameResponse(TaskResult { try await environment.uvClient.fetchCityName(location) }) + ) + + _ = await [fetchUV, fetchCityName] + } case .getUVResponse(.success(let forecast)): state.weatherRequestInFlight = false state.uvIndex = Int(forecast.value) - - guard let location = state.userLocation else { - state.cityName = "app.label.unknown".localized - return .none - } - - return environment.uvClient - .fetchCityName(location) - .receive(on: environment.dispatchQueue) - .catchToEffect() - .map { .getCityNameResponse($0) } + return .none case .getUVResponse(.failure(let error)): state.weatherRequestInFlight = false state.shouldShowErrorPopup = true - state.errorText = error.errorDescription + state.errorText = error.localizedDescription state.uvIndex = 0 return .none @@ -131,10 +129,6 @@ let appReducer = Reducer { state, action, e state.cityName = "app.label.unknown".localized return .none - case .dismissErrorPopup: - state.shouldShowErrorPopup = false - return .none - case .binding(\.$shouldShowErrorPopup): state.shouldShowErrorPopup = false return .none diff --git a/swiftUV/app/Views/ContentVIew.swift b/swiftUV/app/Views/ContentVIew.swift index e7f0ace..14d0d26 100644 --- a/swiftUV/app/Views/ContentVIew.swift +++ b/swiftUV/app/Views/ContentVIew.swift @@ -17,7 +17,7 @@ struct ContentView: View { WithViewStore(self.store) { viewStore in ZStack { Rectangle() - .animation(Animation.easeIn(duration: 1.0)) + .animation(.easeIn(duration: 0.5), value: viewStore.uvIndex.associatedColor) .foregroundColor(Color(viewStore.uvIndex.associatedColor)) .edgesIgnoringSafeArea(.all) @@ -84,6 +84,9 @@ struct ContentView: View { .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in viewStore.send(.onAppear) } + .onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in + viewStore.send(.onDisappear) + } } } } @@ -102,7 +105,6 @@ struct ContentView_Previews: PreviewProvider { reducer: appReducer, environment: AppEnvironment( uvClient: .mock, - dispatchQueue: DispatchQueue.test.eraseToAnyScheduler(), locationManager: .live ) ) diff --git a/swiftUV/app/Views/LocationReducer.swift b/swiftUV/app/Views/LocationReducer.swift index 31f6ba4..03267cb 100644 --- a/swiftUV/app/Views/LocationReducer.swift +++ b/swiftUV/app/Views/LocationReducer.swift @@ -10,31 +10,18 @@ import ComposableArchitecture import ComposableCoreLocation import Foundation -let locationManagerReducer = Reducer { state, action, environment in - +let locationManagerReducer = Reducer { state, action, _ in switch action { - case .locationManager(.didChangeAuthorization(.authorizedAlways)), - .locationManager(.didChangeAuthorization(.authorizedWhenInUse)): - if state.isRequestingCurrentLocation { - return environment.locationManager - .requestLocation() - .fireAndForget() - } - return .none - - case .locationManager(.didChangeAuthorization(.denied)): - if state.isRequestingCurrentLocation { - state.errorText = "app.error.localisationDisabled".localized - state.shouldShowErrorPopup = true - state.isRequestingCurrentLocation = false - state.isLocationRefused = true - } - return .none case let .locationManager(.didUpdateLocations(locations)): + guard state.hasAlreadyRequestLocation == false else { + return .none + } + state.isRequestingCurrentLocation = false guard let location = locations.first else { return .none } state.userLocation = Location(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude) - return Effect(value: .getUVRequest) + state.hasAlreadyRequestLocation = true + return .task { .getUVRequest } default: return .none diff --git a/swiftUVTests/AppReducerTests.swift b/swiftUVTests/AppReducerTests.swift index 4b7e061..4a6ad08 100644 --- a/swiftUVTests/AppReducerTests.swift +++ b/swiftUVTests/AppReducerTests.swift @@ -12,9 +12,9 @@ import XCTest @testable import swiftUV +@MainActor class AppReducerTests: XCTestCase { - - func testGetUVRequestSuccess() { + func testGetUVRequestSuccess() async { let expectedForecast = Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5) let store = TestStore( initialState: @@ -26,27 +26,26 @@ class AppReducerTests: XCTestCase { environment: .unimplemented ) - store.environment.uvClient.fetchUVIndex = { _ in Effect(value: Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5)) } - store.environment.uvClient.fetchCityName = { _ in Effect(value: "Gueugnon") } - store.environment.dispatchQueue = .immediate + store.environment.uvClient.fetchUVIndex = { _ in Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5) } + store.environment.uvClient.fetchCityName = { _ in "Gueugnon" } - store.send(.getUVRequest) { + await store.send(.getUVRequest) { $0.weatherRequestInFlight = true $0.getCityNameRequestInFlight = true } - store.receive(.getUVResponse(.success(expectedForecast))) { + await store.receive(.getUVResponse(.success(expectedForecast))) { $0.weatherRequestInFlight = false $0.uvIndex = 5 } - store.receive(.getCityNameResponse(.success("Gueugnon"))) { + await store.receive(.getCityNameResponse(.success("Gueugnon"))) { $0.getCityNameRequestInFlight = false $0.cityName = "Gueugnon" } } - func testGetUVRequestFailure() { + func testGetUVRequestFailure() async { let store = TestStore( initialState: AppState( @@ -57,20 +56,25 @@ class AppReducerTests: XCTestCase { environment: .unimplemented ) - store.environment.uvClient.fetchUVIndex = { _ in Effect(error: UVClient.Failure(errorDescription: "test")) } - store.environment.dispatchQueue = .immediate + store.environment.uvClient.fetchUVIndex = { _ in throw "test" } + store.environment.uvClient.fetchCityName = { _ in throw "no city" } - store.send(.getUVRequest) { + await store.send(.getUVRequest) { $0.weatherRequestInFlight = true $0.getCityNameRequestInFlight = true } - store.receive(.getUVResponse(.failure(UVClient.Failure(errorDescription: "test")))) { + await store.receive(.getUVResponse(.failure("test"))) { $0.weatherRequestInFlight = false $0.shouldShowErrorPopup = true $0.errorText = "test" $0.uvIndex = 0 } + + await store.receive(.getCityNameResponse(.failure("no city"))) { + $0.getCityNameRequestInFlight = false + $0.cityName = "app.label.unknown".localized + } } func testGetUVRequestFailureNoLocation() { @@ -89,114 +93,43 @@ class AppReducerTests: XCTestCase { $0.errorText = "app.error.couldNotLocalise".localized } } - - func testGetUVResponseSuccess() { - let expectedForecast = Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5) - let store = TestStore( - initialState: - AppState( - getCityNameRequestInFlight: true, - userLocation: Location(latitude: 12.0, longitude: 13.0) - ), - reducer: appReducer, - environment: .unimplemented - ) - - store.environment.uvClient.fetchCityName = { _ in Effect(value: "Gueugnon") } - store.environment.dispatchQueue = .immediate - store.send(.getUVResponse(.success(expectedForecast))) { - $0.weatherRequestInFlight = false - $0.uvIndex = 5 - } - - store.receive(.getCityNameResponse(.success("Gueugnon"))) { - $0.cityName = "Gueugnon" - $0.getCityNameRequestInFlight = false - } - } - - func testGetUVResponseSuccessNoLocation() { - let expectedForecast = Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5) - let store = TestStore( - initialState: - AppState( - getCityNameRequestInFlight: true - ), - reducer: appReducer, - environment: .unimplemented - ) - - store.send(.getUVResponse(.success(expectedForecast))) { - $0.weatherRequestInFlight = false - $0.uvIndex = 5 - $0.cityName = "app.label.unknown".localized - } - } - - func testGetUVResponseFailure() { + func testDismissErrorPopup() { let store = TestStore( initialState: AppState( - getCityNameRequestInFlight: true + shouldShowErrorPopup: true ), reducer: appReducer, environment: .unimplemented ) - store.send(.getUVResponse(.failure(UVClient.Failure(errorDescription: "test")))) { - $0.weatherRequestInFlight = false - $0.shouldShowErrorPopup = true - $0.errorText = "test" - $0.uvIndex = 0 - } - } - - func testGetCityNameResponseSuccess() { - let store = TestStore( - initialState: .init(), - reducer: appReducer, - environment: .unimplemented - ) - - store.send(.getCityNameResponse(.success("Gueugnon"))) { - $0.getCityNameRequestInFlight = false - $0.cityName = "Gueugnon" + store.send(.set(\.$shouldShowErrorPopup, true)) { + $0.shouldShowErrorPopup = false } } - func testGetCityNameResponseFailure() { - let store = TestStore( - initialState: .init(), - reducer: appReducer, - environment: .unimplemented - ) - - store.send(.getCityNameResponse(.failure(UVClient.Failure(errorDescription: "test")))) { - $0.getCityNameRequestInFlight = false - $0.cityName = "app.label.unknown".localized - } - } - - func testDismissErrorPopup() { + func testOnDisappear() { let store = TestStore( - initialState: - AppState( - shouldShowErrorPopup: true - ), + initialState: AppState(hasAlreadyRequestLocation: true), reducer: appReducer, environment: .unimplemented ) - store.send(.dismissErrorPopup) { - $0.shouldShowErrorPopup = false + store.send(.onDisappear) { + $0.hasAlreadyRequestLocation = false } } } + extension AppEnvironment { static let unimplemented = Self( uvClient: .unimplemented, - dispatchQueue: .unimplemented, locationManager: .failing ) } + +extension String: Error {} +extension String: LocalizedError { + public var errorDescription: String? { self } +} From 51b91391150758d4a294f57c04a37d8218e89962 Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Thu, 1 Sep 2022 21:48:26 +0200 Subject: [PATCH 03/18] feat(modularization): add SPM as root + models module --- Package.swift | 17 ++++++++++ README.md | 5 +-- Sources/Models/Constants.swift | 17 ++++++++++ Sources/Models/Forecast.swift | 31 ++++++++++++++++++ {swiftUV/app => Sources}/Models/Index.swift | 6 ++-- Sources/Models/Location.swift | 17 ++++++++++ {swiftUV/app => Sources}/Models/String.swift | 2 +- Sources/uv-today-ios/uv_today_ios.swift | 6 ++++ .../uv-today-iosTests/uv_today_iosTests.swift | 11 +++++++ .../xcschemes/xcschememanagement.plist | 22 ------------- .swiftlint.yml => swiftUV/.swiftlint.yml | 0 swiftUV/Package.swift | 8 +++++ Podfile => swiftUV/Podfile | 0 Podfile.lock => swiftUV/Podfile.lock | 0 swiftUV/README.md | 2 ++ swiftUV/app/Models/Constants.swift | 17 ---------- swiftUV/app/Models/Forecast.swift | 23 ------------- swiftUV/app/Models/Location.swift | 12 ------- {fastlane => swiftUV/fastlane}/Appfile | 0 {fastlane => swiftUV/fastlane}/Fastfile | 0 .../swiftUV.xcodeproj}/project.pbxproj | 29 +++++----------- .../contents.xcworkspacedata | 0 .../xcshareddata/xcschemes/swiftUV.xcscheme | 0 .../xcschemes/swiftUVTests.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/swiftpm/Package.resolved | 0 swiftUV/{ => swiftUV}/App.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../Icon-App-20x20@2x-1.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../Icon-App-29x29@2x-1.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../Icon-App-40x40@2x-1.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../Icon-App-83.5x83.5@2x.png | Bin .../AppIcon.appiconset/iTunesArtwork@2x.png | Bin .../Assets.xcassets/Contents.json | 0 .../LaunchImage.launchimage/Contents.json | 0 .../splashScreen-4.7-1.png | Bin .../splashScreen-4.7-2.png | Bin .../splashScreen-4.7.png | Bin .../LaunchImage.launchimage/splashScreen.png | Bin .../SplashScreen.imageset/Contents.json | 0 .../splashScreen-4.7-2.png | Bin .../splashScreen-4.7-3.png | Bin .../SplashSreen.imageset/Contents.json | 0 .../refresh.imageset/Contents.json | 0 .../refresh.imageset/refresh-1.png | Bin .../refresh.imageset/refresh-2.png | Bin .../refresh.imageset/refresh.png | Bin .../Base.lproj/LaunchScreen.storyboard | 0 .../Base.lproj/Localizable.strings | 0 .../Colors.xcassets/Contents.json | 0 .../LightRed.colorset/Contents.json | 0 swiftUV/{ => swiftUV}/Info.plist | 0 swiftUV/{ => swiftUV}/app/UVClient/Live.swift | 1 + swiftUV/{ => swiftUV}/app/UVClient/Mock.swift | 1 + .../{ => swiftUV}/app/UVClient/UVClient.swift | 1 + .../{ => swiftUV}/app/Views/AppReducer.swift | 3 +- .../{ => swiftUV}/app/Views/ContentVIew.swift | 0 .../app/Views/LocationReducer.swift | 3 +- .../en.lproj/Localizable.strings | 0 .../fr.lproj/LaunchScreen.strings | 0 .../fr.lproj/Localizable.strings | 0 .../swiftUVTests}/AppReducerTests.swift | 0 .../swiftUVTests}/IndexTests.swift | 0 .../swiftUVTests}/Info.plist | 0 78 files changed, 132 insertions(+), 102 deletions(-) create mode 100644 Package.swift create mode 100644 Sources/Models/Constants.swift create mode 100644 Sources/Models/Forecast.swift rename {swiftUV/app => Sources}/Models/Index.swift (88%) create mode 100644 Sources/Models/Location.swift rename {swiftUV/app => Sources}/Models/String.swift (89%) create mode 100644 Sources/uv-today-ios/uv_today_ios.swift create mode 100644 Tests/uv-today-iosTests/uv_today_iosTests.swift delete mode 100644 swiftUV.xcodeproj/xcuserdata/Zlatan.xcuserdatad/xcschemes/xcschememanagement.plist rename .swiftlint.yml => swiftUV/.swiftlint.yml (100%) create mode 100644 swiftUV/Package.swift rename Podfile => swiftUV/Podfile (100%) rename Podfile.lock => swiftUV/Podfile.lock (100%) create mode 100644 swiftUV/README.md delete mode 100644 swiftUV/app/Models/Constants.swift delete mode 100644 swiftUV/app/Models/Forecast.swift delete mode 100644 swiftUV/app/Models/Location.swift rename {fastlane => swiftUV/fastlane}/Appfile (100%) rename {fastlane => swiftUV/fastlane}/Fastfile (100%) rename {swiftUV.xcodeproj => swiftUV/swiftUV.xcodeproj}/project.pbxproj (93%) rename {swiftUV.xcodeproj => swiftUV/swiftUV.xcodeproj}/project.xcworkspace/contents.xcworkspacedata (100%) rename {swiftUV.xcodeproj => swiftUV/swiftUV.xcodeproj}/xcshareddata/xcschemes/swiftUV.xcscheme (100%) rename {swiftUV.xcodeproj => swiftUV/swiftUV.xcodeproj}/xcshareddata/xcschemes/swiftUVTests.xcscheme (100%) rename {swiftUV.xcworkspace => swiftUV/swiftUV.xcworkspace}/contents.xcworkspacedata (100%) rename {swiftUV.xcworkspace => swiftUV/swiftUV.xcworkspace}/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {swiftUV.xcworkspace => swiftUV/swiftUV.xcworkspace}/xcshareddata/swiftpm/Package.resolved (100%) rename swiftUV/{ => swiftUV}/App.swift (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/Contents.json (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/LaunchImage.launchimage/Contents.json (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7-1.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7-2.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/LaunchImage.launchimage/splashScreen.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/SplashScreen.imageset/Contents.json (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/SplashScreen.imageset/splashScreen-4.7-2.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/SplashScreen.imageset/splashScreen-4.7-3.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/SplashSreen.imageset/Contents.json (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/refresh.imageset/Contents.json (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/refresh.imageset/refresh-1.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/refresh.imageset/refresh-2.png (100%) rename swiftUV/{ => swiftUV}/Assets.xcassets/refresh.imageset/refresh.png (100%) rename swiftUV/{ => swiftUV}/Base.lproj/LaunchScreen.storyboard (100%) rename swiftUV/{ => swiftUV}/Base.lproj/Localizable.strings (100%) rename swiftUV/{ => swiftUV}/Colors.xcassets/Contents.json (100%) rename swiftUV/{ => swiftUV}/Colors.xcassets/LightRed.colorset/Contents.json (100%) rename swiftUV/{ => swiftUV}/Info.plist (100%) rename swiftUV/{ => swiftUV}/app/UVClient/Live.swift (98%) rename swiftUV/{ => swiftUV}/app/UVClient/Mock.swift (97%) rename swiftUV/{ => swiftUV}/app/UVClient/UVClient.swift (97%) rename swiftUV/{ => swiftUV}/app/Views/AppReducer.swift (98%) rename swiftUV/{ => swiftUV}/app/Views/ContentVIew.swift (100%) rename swiftUV/{ => swiftUV}/app/Views/LocationReducer.swift (84%) rename swiftUV/{ => swiftUV}/en.lproj/Localizable.strings (100%) rename swiftUV/{ => swiftUV}/fr.lproj/LaunchScreen.strings (100%) rename swiftUV/{ => swiftUV}/fr.lproj/Localizable.strings (100%) rename {swiftUVTests => swiftUV/swiftUVTests}/AppReducerTests.swift (100%) rename {swiftUVTests => swiftUV/swiftUVTests}/IndexTests.swift (100%) rename {swiftUVTests => swiftUV/swiftUVTests}/Info.plist (100%) diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..b796de2 --- /dev/null +++ b/Package.swift @@ -0,0 +1,17 @@ +// swift-tools-version: 5.6 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "uv-today-ios", + platforms: [.iOS(.v15)], + products: [ + .library(name: "Models", targets: ["Models"]) + ], + dependencies: [ + ], + targets: [ + .target(name: "Models") + ] +) diff --git a/README.md b/README.md index de73c04..bdc5c80 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ -# SwiftUV -Swift app to get current UV index depending on location +# uv-today-ios + +A description of this package. diff --git a/Sources/Models/Constants.swift b/Sources/Models/Constants.swift new file mode 100644 index 0000000..48310d6 --- /dev/null +++ b/Sources/Models/Constants.swift @@ -0,0 +1,17 @@ +// +// Constants.swift +// swiftUV +// +// Created by Zlatan on 04/11/2017. +// Copyright © 2017 Thomas Guilleminot. All rights reserved. +// + +public struct K { + public struct Api { + public static let baseURL = "https://api.openweathermap.org/data/2.5/" + + public struct Endpoints { + public static let getUV = "uvi?lat=%.4f&lon=%.4f&appid=%@" + } + } +} diff --git a/Sources/Models/Forecast.swift b/Sources/Models/Forecast.swift new file mode 100644 index 0000000..1817efc --- /dev/null +++ b/Sources/Models/Forecast.swift @@ -0,0 +1,31 @@ +// +// Forecast.swift +// swiftUV +// +// Created by Zlatan on 16/11/2018. +// Copyright © 2018 Thomas Guilleminot. All rights reserved. +// + +public struct Forecast: Codable, Equatable { + public let lat: Double + public let lon: Double + public let dateIso: String + public let date: Int + public let value: Double + + public init(lat: Double, lon: Double, dateIso: String, date: Int, value: Double) { + self.lat = lat + self.lon = lon + self.dateIso = dateIso + self.date = date + self.value = value + } + + enum CodingKeys: String, CodingKey { + case lat + case lon + case dateIso = "date_iso" + case date + case value + } +} diff --git a/swiftUV/app/Models/Index.swift b/Sources/Models/Index.swift similarity index 88% rename from swiftUV/app/Models/Index.swift rename to Sources/Models/Index.swift index f74b3ee..2540015 100644 --- a/swiftUV/app/Models/Index.swift +++ b/Sources/Models/Index.swift @@ -8,11 +8,11 @@ import UIKit -typealias Index = Int +public typealias Index = Int extension Index { - var associatedColor: UIColor { + public var associatedColor: UIColor { switch self { case 0: return UIColor.systemBlue case 1, 2: return UIColor.systemGreen @@ -24,7 +24,7 @@ extension Index { } } - var associatedDescription: String { + public var associatedDescription: String { switch self { case 0, 1, 2: return "lowUV.desc".localized diff --git a/Sources/Models/Location.swift b/Sources/Models/Location.swift new file mode 100644 index 0000000..4f2749b --- /dev/null +++ b/Sources/Models/Location.swift @@ -0,0 +1,17 @@ +// +// Location.swift +// swiftUV +// +// Created by Zlatan on 11/11/2018. +// Copyright © 2018 Thomas Guilleminot. All rights reserved. +// + +public struct Location: Equatable { + public let latitude: Double + public let longitude: Double + + public init(latitude: Double, longitude: Double) { + self.latitude = latitude + self.longitude = longitude + } +} diff --git a/swiftUV/app/Models/String.swift b/Sources/Models/String.swift similarity index 89% rename from swiftUV/app/Models/String.swift rename to Sources/Models/String.swift index 9fc4c23..a05f507 100644 --- a/swiftUV/app/Models/String.swift +++ b/Sources/Models/String.swift @@ -9,7 +9,7 @@ import Foundation extension String { - var localized: String { + public var localized: String { return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "") } } diff --git a/Sources/uv-today-ios/uv_today_ios.swift b/Sources/uv-today-ios/uv_today_ios.swift new file mode 100644 index 0000000..caddfba --- /dev/null +++ b/Sources/uv-today-ios/uv_today_ios.swift @@ -0,0 +1,6 @@ +public struct uv_today_ios { + public private(set) var text = "Hello, World!" + + public init() { + } +} diff --git a/Tests/uv-today-iosTests/uv_today_iosTests.swift b/Tests/uv-today-iosTests/uv_today_iosTests.swift new file mode 100644 index 0000000..07a794b --- /dev/null +++ b/Tests/uv-today-iosTests/uv_today_iosTests.swift @@ -0,0 +1,11 @@ +import XCTest +@testable import uv_today_ios + +final class uv_today_iosTests: XCTestCase { + func testExample() throws { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct + // results. + XCTAssertEqual(uv_today_ios().text, "Hello, World!") + } +} diff --git a/swiftUV.xcodeproj/xcuserdata/Zlatan.xcuserdatad/xcschemes/xcschememanagement.plist b/swiftUV.xcodeproj/xcuserdata/Zlatan.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index f410413..0000000 --- a/swiftUV.xcodeproj/xcuserdata/Zlatan.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - SchemeUserState - - swiftUV.xcscheme_^#shared#^_ - - orderHint - 0 - - - SuppressBuildableAutocreation - - EBF4B74B1E7951D300B7A616 - - primary - - - - - diff --git a/.swiftlint.yml b/swiftUV/.swiftlint.yml similarity index 100% rename from .swiftlint.yml rename to swiftUV/.swiftlint.yml diff --git a/swiftUV/Package.swift b/swiftUV/Package.swift new file mode 100644 index 0000000..c7564ee --- /dev/null +++ b/swiftUV/Package.swift @@ -0,0 +1,8 @@ +import PackageDescription + +let package = Package( + name: "", + products: [], + dependencies: [], + targets: [] +) diff --git a/Podfile b/swiftUV/Podfile similarity index 100% rename from Podfile rename to swiftUV/Podfile diff --git a/Podfile.lock b/swiftUV/Podfile.lock similarity index 100% rename from Podfile.lock rename to swiftUV/Podfile.lock diff --git a/swiftUV/README.md b/swiftUV/README.md new file mode 100644 index 0000000..de73c04 --- /dev/null +++ b/swiftUV/README.md @@ -0,0 +1,2 @@ +# SwiftUV +Swift app to get current UV index depending on location diff --git a/swiftUV/app/Models/Constants.swift b/swiftUV/app/Models/Constants.swift deleted file mode 100644 index aa991e9..0000000 --- a/swiftUV/app/Models/Constants.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// Constants.swift -// swiftUV -// -// Created by Zlatan on 04/11/2017. -// Copyright © 2017 Thomas Guilleminot. All rights reserved. -// - -struct K { - struct Api { - static let baseURL = "https://api.openweathermap.org/data/2.5/" - - struct Endpoints { - static let getUV = "uvi?lat=%.4f&lon=%.4f&appid=%@" - } - } -} diff --git a/swiftUV/app/Models/Forecast.swift b/swiftUV/app/Models/Forecast.swift deleted file mode 100644 index fc7e797..0000000 --- a/swiftUV/app/Models/Forecast.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Forecast.swift -// swiftUV -// -// Created by Zlatan on 16/11/2018. -// Copyright © 2018 Thomas Guilleminot. All rights reserved. -// - -struct Forecast: Codable, Equatable { - let lat: Double - let lon: Double - let dateIso: String - let date: Int - let value: Double - - enum CodingKeys: String, CodingKey { - case lat - case lon - case dateIso = "date_iso" - case date - case value - } -} diff --git a/swiftUV/app/Models/Location.swift b/swiftUV/app/Models/Location.swift deleted file mode 100644 index 06312a9..0000000 --- a/swiftUV/app/Models/Location.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Location.swift -// swiftUV -// -// Created by Zlatan on 11/11/2018. -// Copyright © 2018 Thomas Guilleminot. All rights reserved. -// - -struct Location: Equatable { - let latitude: Double - let longitude: Double -} diff --git a/fastlane/Appfile b/swiftUV/fastlane/Appfile similarity index 100% rename from fastlane/Appfile rename to swiftUV/fastlane/Appfile diff --git a/fastlane/Fastfile b/swiftUV/fastlane/Fastfile similarity index 100% rename from fastlane/Fastfile rename to swiftUV/fastlane/Fastfile diff --git a/swiftUV.xcodeproj/project.pbxproj b/swiftUV/swiftUV.xcodeproj/project.pbxproj similarity index 93% rename from swiftUV.xcodeproj/project.pbxproj rename to swiftUV/swiftUV.xcodeproj/project.pbxproj index a0e4326..4eb6e10 100644 --- a/swiftUV.xcodeproj/project.pbxproj +++ b/swiftUV/swiftUV.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ E938D4E1289B0C7000B8C968 /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E938D4E0289B0C7000B8C968 /* AppReducer.swift */; }; E938D4E7289B0DCD00B8C968 /* Live.swift in Sources */ = {isa = PBXBuildFile; fileRef = E938D4E6289B0DCD00B8C968 /* Live.swift */; }; E938D4E9289B0E0000B8C968 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E938D4E8289B0E0000B8C968 /* Mock.swift */; }; + E967004028C142010071FDB3 /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = E967003F28C142010071FDB3 /* Models */; }; E9A116352322E96C004DC9FB /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E9A116342322E96C004DC9FB /* Colors.xcassets */; }; E9A22D3D2896C7A3006DC054 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = E9A22D3C2896C7A3006DC054 /* ComposableArchitecture */; }; E9AF349C289C4FE600C0F763 /* AppReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AF349B289C4FE600C0F763 /* AppReducerTests.swift */; }; @@ -21,11 +22,6 @@ E9E29EC42896E0A7000DE660 /* ComposableCoreLocation in Frameworks */ = {isa = PBXBuildFile; productRef = E9E29EC32896E0A7000DE660 /* ComposableCoreLocation */; }; E9E29EC62896E346000DE660 /* LocationReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E29EC52896E346000DE660 /* LocationReducer.swift */; }; EBB0615B21A1CF9A0084E975 /* IndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB0615A21A1CF9A0084E975 /* IndexTests.swift */; }; - EBB061D821A1DC980084E975 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB061D121A1DC980084E975 /* Constants.swift */; }; - EBB061D921A1DC980084E975 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB061D221A1DC980084E975 /* String.swift */; }; - EBB061DB21A1DC980084E975 /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB061D421A1DC980084E975 /* Location.swift */; }; - EBB061DC21A1DC980084E975 /* Forecast.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB061D521A1DC980084E975 /* Forecast.swift */; }; - EBB061DD21A1DC980084E975 /* Index.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB061D621A1DC980084E975 /* Index.swift */; }; EBCAA0451E7C30F000DC2E9D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = EBCAA0471E7C30F000DC2E9D /* Localizable.strings */; }; EBF4B7571E7951D400B7A616 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EBF4B7561E7951D400B7A616 /* Assets.xcassets */; }; EBF4B75A1E7951D400B7A616 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EBF4B7581E7951D400B7A616 /* LaunchScreen.storyboard */; }; @@ -49,17 +45,13 @@ E938D4E0289B0C7000B8C968 /* AppReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppReducer.swift; path = app/Views/AppReducer.swift; sourceTree = ""; }; E938D4E6289B0DCD00B8C968 /* Live.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Live.swift; sourceTree = ""; }; E938D4E8289B0E0000B8C968 /* Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mock.swift; sourceTree = ""; }; + E9720BB828C140E200771EC4 /* uv-today-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "uv-today-ios"; path = ..; sourceTree = ""; }; E9A116342322E96C004DC9FB /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; E9AF349B289C4FE600C0F763 /* AppReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducerTests.swift; sourceTree = ""; }; E9AF34A2289C606D00C0F763 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; E9E29EBF2896CD7C000DE660 /* UVClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UVClient.swift; sourceTree = ""; }; E9E29EC52896E346000DE660 /* LocationReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LocationReducer.swift; path = app/Views/LocationReducer.swift; sourceTree = ""; }; EBB0615A21A1CF9A0084E975 /* IndexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexTests.swift; sourceTree = ""; }; - EBB061D121A1DC980084E975 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = app/Models/Constants.swift; sourceTree = ""; }; - EBB061D221A1DC980084E975 /* String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = String.swift; path = app/Models/String.swift; sourceTree = ""; }; - EBB061D421A1DC980084E975 /* Location.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Location.swift; path = app/Models/Location.swift; sourceTree = ""; }; - EBB061D521A1DC980084E975 /* Forecast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Forecast.swift; path = app/Models/Forecast.swift; sourceTree = ""; }; - EBB061D621A1DC980084E975 /* Index.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Index.swift; path = app/Models/Index.swift; sourceTree = ""; }; EBC1904E1E8EEAB600B9F2EC /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/LaunchScreen.strings; sourceTree = ""; }; EBC1904F1E8EEAB600B9F2EC /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; EBCAA0461E7C30F000DC2E9D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; @@ -84,6 +76,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E967004028C142010071FDB3 /* Models in Frameworks */, E9E29EC42896E0A7000DE660 /* ComposableCoreLocation in Frameworks */, E9A22D3D2896C7A3006DC054 /* ComposableArchitecture in Frameworks */, E9AF349F289C58DD00C0F763 /* Bugsnag in Frameworks */, @@ -153,6 +146,7 @@ EBF4B7431E7951D300B7A616 = { isa = PBXGroup; children = ( + E9720BB828C140E200771EC4 /* uv-today-ios */, EBF4B74E1E7951D400B7A616 /* swiftUV */, EBDA88941FAE5C89009514AB /* swiftUVTests */, EBF4B74D1E7951D400B7A616 /* Products */, @@ -205,11 +199,6 @@ EBF4B77A1E799BC600B7A616 /* Models */ = { isa = PBXGroup; children = ( - EBB061D121A1DC980084E975 /* Constants.swift */, - EBB061D521A1DC980084E975 /* Forecast.swift */, - EBB061D621A1DC980084E975 /* Index.swift */, - EBB061D421A1DC980084E975 /* Location.swift */, - EBB061D221A1DC980084E975 /* String.swift */, ); name = Models; sourceTree = ""; @@ -257,6 +246,7 @@ E9A22D3C2896C7A3006DC054 /* ComposableArchitecture */, E9E29EC32896E0A7000DE660 /* ComposableCoreLocation */, E9AF349E289C58DD00C0F763 /* Bugsnag */, + E967003F28C142010071FDB3 /* Models */, ); productName = swiftUV; productReference = EBF4B74C1E7951D400B7A616 /* swiftUV.app */; @@ -399,16 +389,11 @@ buildActionMask = 2147483647; files = ( E9E29EC02896CD7C000DE660 /* UVClient.swift in Sources */, - EBB061DC21A1DC980084E975 /* Forecast.swift in Sources */, E938D4E1289B0C7000B8C968 /* AppReducer.swift in Sources */, E938D4E7289B0DCD00B8C968 /* Live.swift in Sources */, E938D4E9289B0E0000B8C968 /* Mock.swift in Sources */, E9AF34A3289C606D00C0F763 /* App.swift in Sources */, - EBB061DB21A1DC980084E975 /* Location.swift in Sources */, - EBB061D821A1DC980084E975 /* Constants.swift in Sources */, - EBB061D921A1DC980084E975 /* String.swift in Sources */, E931727F2896C5E1003EC842 /* ContentVIew.swift in Sources */, - EBB061DD21A1DC980084E975 /* Index.swift in Sources */, E9E29EC62896E346000DE660 /* LocationReducer.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -743,6 +728,10 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + E967003F28C142010071FDB3 /* Models */ = { + isa = XCSwiftPackageProductDependency; + productName = Models; + }; E9A22D3C2896C7A3006DC054 /* ComposableArchitecture */ = { isa = XCSwiftPackageProductDependency; package = E9A22D3B2896C7A3006DC054 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */; diff --git a/swiftUV.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from swiftUV.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to swiftUV/swiftUV.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme b/swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme similarity index 100% rename from swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme rename to swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme diff --git a/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUVTests.xcscheme b/swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUVTests.xcscheme similarity index 100% rename from swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUVTests.xcscheme rename to swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUVTests.xcscheme diff --git a/swiftUV.xcworkspace/contents.xcworkspacedata b/swiftUV/swiftUV.xcworkspace/contents.xcworkspacedata similarity index 100% rename from swiftUV.xcworkspace/contents.xcworkspacedata rename to swiftUV/swiftUV.xcworkspace/contents.xcworkspacedata diff --git a/swiftUV.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/swiftUV/swiftUV.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from swiftUV.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to swiftUV/swiftUV.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/swiftUV.xcworkspace/xcshareddata/swiftpm/Package.resolved b/swiftUV/swiftUV.xcworkspace/xcshareddata/swiftpm/Package.resolved similarity index 100% rename from swiftUV.xcworkspace/xcshareddata/swiftpm/Package.resolved rename to swiftUV/swiftUV.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/swiftUV/App.swift b/swiftUV/swiftUV/App.swift similarity index 100% rename from swiftUV/App.swift rename to swiftUV/swiftUV/App.swift diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Contents.json b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Contents.json rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/swiftUV/Assets.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png b/swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png similarity index 100% rename from swiftUV/Assets.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png rename to swiftUV/swiftUV/Assets.xcassets/AppIcon.appiconset/iTunesArtwork@2x.png diff --git a/swiftUV/Assets.xcassets/Contents.json b/swiftUV/swiftUV/Assets.xcassets/Contents.json similarity index 100% rename from swiftUV/Assets.xcassets/Contents.json rename to swiftUV/swiftUV/Assets.xcassets/Contents.json diff --git a/swiftUV/Assets.xcassets/LaunchImage.launchimage/Contents.json b/swiftUV/swiftUV/Assets.xcassets/LaunchImage.launchimage/Contents.json similarity index 100% rename from swiftUV/Assets.xcassets/LaunchImage.launchimage/Contents.json rename to swiftUV/swiftUV/Assets.xcassets/LaunchImage.launchimage/Contents.json diff --git a/swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7-1.png b/swiftUV/swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7-1.png similarity index 100% rename from swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7-1.png rename to swiftUV/swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7-1.png diff --git a/swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7-2.png b/swiftUV/swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7-2.png similarity index 100% rename from swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7-2.png rename to swiftUV/swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7-2.png diff --git a/swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7.png b/swiftUV/swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7.png similarity index 100% rename from swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7.png rename to swiftUV/swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen-4.7.png diff --git a/swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen.png b/swiftUV/swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen.png similarity index 100% rename from swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen.png rename to swiftUV/swiftUV/Assets.xcassets/LaunchImage.launchimage/splashScreen.png diff --git a/swiftUV/Assets.xcassets/SplashScreen.imageset/Contents.json b/swiftUV/swiftUV/Assets.xcassets/SplashScreen.imageset/Contents.json similarity index 100% rename from swiftUV/Assets.xcassets/SplashScreen.imageset/Contents.json rename to swiftUV/swiftUV/Assets.xcassets/SplashScreen.imageset/Contents.json diff --git a/swiftUV/Assets.xcassets/SplashScreen.imageset/splashScreen-4.7-2.png b/swiftUV/swiftUV/Assets.xcassets/SplashScreen.imageset/splashScreen-4.7-2.png similarity index 100% rename from swiftUV/Assets.xcassets/SplashScreen.imageset/splashScreen-4.7-2.png rename to swiftUV/swiftUV/Assets.xcassets/SplashScreen.imageset/splashScreen-4.7-2.png diff --git a/swiftUV/Assets.xcassets/SplashScreen.imageset/splashScreen-4.7-3.png b/swiftUV/swiftUV/Assets.xcassets/SplashScreen.imageset/splashScreen-4.7-3.png similarity index 100% rename from swiftUV/Assets.xcassets/SplashScreen.imageset/splashScreen-4.7-3.png rename to swiftUV/swiftUV/Assets.xcassets/SplashScreen.imageset/splashScreen-4.7-3.png diff --git a/swiftUV/Assets.xcassets/SplashSreen.imageset/Contents.json b/swiftUV/swiftUV/Assets.xcassets/SplashSreen.imageset/Contents.json similarity index 100% rename from swiftUV/Assets.xcassets/SplashSreen.imageset/Contents.json rename to swiftUV/swiftUV/Assets.xcassets/SplashSreen.imageset/Contents.json diff --git a/swiftUV/Assets.xcassets/refresh.imageset/Contents.json b/swiftUV/swiftUV/Assets.xcassets/refresh.imageset/Contents.json similarity index 100% rename from swiftUV/Assets.xcassets/refresh.imageset/Contents.json rename to swiftUV/swiftUV/Assets.xcassets/refresh.imageset/Contents.json diff --git a/swiftUV/Assets.xcassets/refresh.imageset/refresh-1.png b/swiftUV/swiftUV/Assets.xcassets/refresh.imageset/refresh-1.png similarity index 100% rename from swiftUV/Assets.xcassets/refresh.imageset/refresh-1.png rename to swiftUV/swiftUV/Assets.xcassets/refresh.imageset/refresh-1.png diff --git a/swiftUV/Assets.xcassets/refresh.imageset/refresh-2.png b/swiftUV/swiftUV/Assets.xcassets/refresh.imageset/refresh-2.png similarity index 100% rename from swiftUV/Assets.xcassets/refresh.imageset/refresh-2.png rename to swiftUV/swiftUV/Assets.xcassets/refresh.imageset/refresh-2.png diff --git a/swiftUV/Assets.xcassets/refresh.imageset/refresh.png b/swiftUV/swiftUV/Assets.xcassets/refresh.imageset/refresh.png similarity index 100% rename from swiftUV/Assets.xcassets/refresh.imageset/refresh.png rename to swiftUV/swiftUV/Assets.xcassets/refresh.imageset/refresh.png diff --git a/swiftUV/Base.lproj/LaunchScreen.storyboard b/swiftUV/swiftUV/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from swiftUV/Base.lproj/LaunchScreen.storyboard rename to swiftUV/swiftUV/Base.lproj/LaunchScreen.storyboard diff --git a/swiftUV/Base.lproj/Localizable.strings b/swiftUV/swiftUV/Base.lproj/Localizable.strings similarity index 100% rename from swiftUV/Base.lproj/Localizable.strings rename to swiftUV/swiftUV/Base.lproj/Localizable.strings diff --git a/swiftUV/Colors.xcassets/Contents.json b/swiftUV/swiftUV/Colors.xcassets/Contents.json similarity index 100% rename from swiftUV/Colors.xcassets/Contents.json rename to swiftUV/swiftUV/Colors.xcassets/Contents.json diff --git a/swiftUV/Colors.xcassets/LightRed.colorset/Contents.json b/swiftUV/swiftUV/Colors.xcassets/LightRed.colorset/Contents.json similarity index 100% rename from swiftUV/Colors.xcassets/LightRed.colorset/Contents.json rename to swiftUV/swiftUV/Colors.xcassets/LightRed.colorset/Contents.json diff --git a/swiftUV/Info.plist b/swiftUV/swiftUV/Info.plist similarity index 100% rename from swiftUV/Info.plist rename to swiftUV/swiftUV/Info.plist diff --git a/swiftUV/app/UVClient/Live.swift b/swiftUV/swiftUV/app/UVClient/Live.swift similarity index 98% rename from swiftUV/app/UVClient/Live.swift rename to swiftUV/swiftUV/app/UVClient/Live.swift index a569749..a3a3c88 100644 --- a/swiftUV/app/UVClient/Live.swift +++ b/swiftUV/swiftUV/app/UVClient/Live.swift @@ -10,6 +10,7 @@ import Foundation import ComposableArchitecture import CoreLocation import Keys +import Models extension UVClient { static let live = UVClient( diff --git a/swiftUV/app/UVClient/Mock.swift b/swiftUV/swiftUV/app/UVClient/Mock.swift similarity index 97% rename from swiftUV/app/UVClient/Mock.swift rename to swiftUV/swiftUV/app/UVClient/Mock.swift index 2895f0e..3a63562 100644 --- a/swiftUV/app/UVClient/Mock.swift +++ b/swiftUV/swiftUV/app/UVClient/Mock.swift @@ -9,6 +9,7 @@ import Foundation import ComposableArchitecture import XCTestDynamicOverlay +import Models #if DEBUG extension UVClient { diff --git a/swiftUV/app/UVClient/UVClient.swift b/swiftUV/swiftUV/app/UVClient/UVClient.swift similarity index 97% rename from swiftUV/app/UVClient/UVClient.swift rename to swiftUV/swiftUV/app/UVClient/UVClient.swift index 94d65be..89f999c 100644 --- a/swiftUV/app/UVClient/UVClient.swift +++ b/swiftUV/swiftUV/app/UVClient/UVClient.swift @@ -7,6 +7,7 @@ // import ComposableArchitecture +import Models struct UVClientRequest { let lat: Double diff --git a/swiftUV/app/Views/AppReducer.swift b/swiftUV/swiftUV/app/Views/AppReducer.swift similarity index 98% rename from swiftUV/app/Views/AppReducer.swift rename to swiftUV/swiftUV/app/Views/AppReducer.swift index 3381cb6..051f648 100644 --- a/swiftUV/app/Views/AppReducer.swift +++ b/swiftUV/swiftUV/app/Views/AppReducer.swift @@ -8,6 +8,7 @@ import ComposableArchitecture import ComposableCoreLocation +import Models struct AppState: Equatable { var uvIndex: Index = 0 @@ -16,7 +17,7 @@ struct AppState: Equatable { var getCityNameRequestInFlight = false var errorText = "" - var userLocation: Location? + var userLocation: Models.Location? var isRequestingCurrentLocation = false var hasAlreadyRequestLocation = false var isLocationRefused = false diff --git a/swiftUV/app/Views/ContentVIew.swift b/swiftUV/swiftUV/app/Views/ContentVIew.swift similarity index 100% rename from swiftUV/app/Views/ContentVIew.swift rename to swiftUV/swiftUV/app/Views/ContentVIew.swift diff --git a/swiftUV/app/Views/LocationReducer.swift b/swiftUV/swiftUV/app/Views/LocationReducer.swift similarity index 84% rename from swiftUV/app/Views/LocationReducer.swift rename to swiftUV/swiftUV/app/Views/LocationReducer.swift index 03267cb..f1e4db8 100644 --- a/swiftUV/app/Views/LocationReducer.swift +++ b/swiftUV/swiftUV/app/Views/LocationReducer.swift @@ -9,6 +9,7 @@ import ComposableArchitecture import ComposableCoreLocation import Foundation +import Models let locationManagerReducer = Reducer { state, action, _ in switch action { @@ -19,7 +20,7 @@ let locationManagerReducer = Reducer { stat state.isRequestingCurrentLocation = false guard let location = locations.first else { return .none } - state.userLocation = Location(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude) + state.userLocation = Models.Location(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude) state.hasAlreadyRequestLocation = true return .task { .getUVRequest } diff --git a/swiftUV/en.lproj/Localizable.strings b/swiftUV/swiftUV/en.lproj/Localizable.strings similarity index 100% rename from swiftUV/en.lproj/Localizable.strings rename to swiftUV/swiftUV/en.lproj/Localizable.strings diff --git a/swiftUV/fr.lproj/LaunchScreen.strings b/swiftUV/swiftUV/fr.lproj/LaunchScreen.strings similarity index 100% rename from swiftUV/fr.lproj/LaunchScreen.strings rename to swiftUV/swiftUV/fr.lproj/LaunchScreen.strings diff --git a/swiftUV/fr.lproj/Localizable.strings b/swiftUV/swiftUV/fr.lproj/Localizable.strings similarity index 100% rename from swiftUV/fr.lproj/Localizable.strings rename to swiftUV/swiftUV/fr.lproj/Localizable.strings diff --git a/swiftUVTests/AppReducerTests.swift b/swiftUV/swiftUVTests/AppReducerTests.swift similarity index 100% rename from swiftUVTests/AppReducerTests.swift rename to swiftUV/swiftUVTests/AppReducerTests.swift diff --git a/swiftUVTests/IndexTests.swift b/swiftUV/swiftUVTests/IndexTests.swift similarity index 100% rename from swiftUVTests/IndexTests.swift rename to swiftUV/swiftUVTests/IndexTests.swift diff --git a/swiftUVTests/Info.plist b/swiftUV/swiftUVTests/Info.plist similarity index 100% rename from swiftUVTests/Info.plist rename to swiftUV/swiftUVTests/Info.plist From 23949e454591549f7947da635d859bae536ae593 Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Thu, 1 Sep 2022 22:25:22 +0200 Subject: [PATCH 04/18] feat(modularization): create module for UVClient --- Package.swift | 6 ++-- .../app => Sources}/UVClient/Live.swift | 7 ++-- .../app => Sources}/UVClient/Mock.swift | 4 +-- Sources/UVClient/UVClient.swift | 29 +++++++++++++++ Sources/uv-today-ios/uv_today_ios.swift | 6 ---- swiftUV/swiftUV.xcodeproj/project.pbxproj | 36 ++++--------------- swiftUV/swiftUV/App.swift | 2 +- swiftUV/swiftUV/app/UVClient/UVClient.swift | 24 ------------- swiftUV/swiftUV/app/Views/AppReducer.swift | 1 + 9 files changed, 46 insertions(+), 69 deletions(-) rename {swiftUV/swiftUV/app => Sources}/UVClient/Live.swift (86%) rename {swiftUV/swiftUV/app => Sources}/UVClient/Mock.swift (88%) create mode 100644 Sources/UVClient/UVClient.swift delete mode 100644 Sources/uv-today-ios/uv_today_ios.swift delete mode 100644 swiftUV/swiftUV/app/UVClient/UVClient.swift diff --git a/Package.swift b/Package.swift index b796de2..95efe2f 100644 --- a/Package.swift +++ b/Package.swift @@ -7,11 +7,13 @@ let package = Package( name: "uv-today-ios", platforms: [.iOS(.v15)], products: [ - .library(name: "Models", targets: ["Models"]) + .library(name: "Models", targets: ["Models"]), + .library(name: "UVClient", targets: ["UVClient"]) ], dependencies: [ ], targets: [ - .target(name: "Models") + .target(name: "Models"), + .target(name: "UVClient") ] ) diff --git a/swiftUV/swiftUV/app/UVClient/Live.swift b/Sources/UVClient/Live.swift similarity index 86% rename from swiftUV/swiftUV/app/UVClient/Live.swift rename to Sources/UVClient/Live.swift index a3a3c88..ec6c882 100644 --- a/swiftUV/swiftUV/app/UVClient/Live.swift +++ b/Sources/UVClient/Live.swift @@ -6,17 +6,14 @@ // Copyright © 2022 Thomas Guilleminot. All rights reserved. // -import Foundation -import ComposableArchitecture import CoreLocation -import Keys import Models extension UVClient { - static let live = UVClient( + public static let live = UVClient( fetchUVIndex: { request in let url = URL( - string: K.Api.baseURL + String(format: K.Api.Endpoints.getUV, arguments: [request.lat, request.long, SwiftUVKeys().openWeatherMapApiKey]))! + string: K.Api.baseURL + String(format: K.Api.Endpoints.getUV, arguments: [request.lat, request.long, "73c6746593df6d9b6d412026b3db9239"]))! do { let (response, _) = try await URLSession.shared.data(from: url) let forectast = try JSONDecoder().decode(Forecast.self, from: response) diff --git a/swiftUV/swiftUV/app/UVClient/Mock.swift b/Sources/UVClient/Mock.swift similarity index 88% rename from swiftUV/swiftUV/app/UVClient/Mock.swift rename to Sources/UVClient/Mock.swift index 3a63562..8e44db2 100644 --- a/swiftUV/swiftUV/app/UVClient/Mock.swift +++ b/Sources/UVClient/Mock.swift @@ -13,7 +13,7 @@ import Models #if DEBUG extension UVClient { - static let mock = Self( + public static let mock = Self( fetchUVIndex: { _ in Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5) }, @@ -24,7 +24,7 @@ extension UVClient { } extension UVClient { - static let unimplemented = Self( + public static let unimplemented = Self( fetchUVIndex: XCTUnimplemented("\(Self.self).fetchUVIndex)"), fetchCityName: XCTUnimplemented("\(Self.self).fetchCityName") ) diff --git a/Sources/UVClient/UVClient.swift b/Sources/UVClient/UVClient.swift new file mode 100644 index 0000000..52b0cc6 --- /dev/null +++ b/Sources/UVClient/UVClient.swift @@ -0,0 +1,29 @@ +// +// WeatherClient.swift +// swiftUV +// +// Created by Thomas Guilleminot on 31/07/2022. +// Copyright © 2022 Thomas Guilleminot. All rights reserved. +// + +import ComposableArchitecture +import Models + +public struct UVClientRequest { + public let lat: Double + public let long: Double + + public init(lat: Double, long: Double) { + self.lat = lat + self.long = long + } +} + +public struct UVClient { + public var fetchUVIndex: @Sendable (UVClientRequest) async throws -> Forecast + public var fetchCityName: @Sendable (Location) async throws -> String + + struct Failure: Error, Equatable { + let errorDescription: String + } +} diff --git a/Sources/uv-today-ios/uv_today_ios.swift b/Sources/uv-today-ios/uv_today_ios.swift deleted file mode 100644 index caddfba..0000000 --- a/Sources/uv-today-ios/uv_today_ios.swift +++ /dev/null @@ -1,6 +0,0 @@ -public struct uv_today_ios { - public private(set) var text = "Hello, World!" - - public init() { - } -} diff --git a/swiftUV/swiftUV.xcodeproj/project.pbxproj b/swiftUV/swiftUV.xcodeproj/project.pbxproj index 4eb6e10..96a0659 100644 --- a/swiftUV/swiftUV.xcodeproj/project.pbxproj +++ b/swiftUV/swiftUV.xcodeproj/project.pbxproj @@ -10,15 +10,13 @@ 6538403F53A4E29309F2F084 /* Pods_swiftUV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 59AC0D333DE20DF5F86F5B5C /* Pods_swiftUV.framework */; }; E931727F2896C5E1003EC842 /* ContentVIew.swift in Sources */ = {isa = PBXBuildFile; fileRef = E931727E2896C5E1003EC842 /* ContentVIew.swift */; }; E938D4E1289B0C7000B8C968 /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E938D4E0289B0C7000B8C968 /* AppReducer.swift */; }; - E938D4E7289B0DCD00B8C968 /* Live.swift in Sources */ = {isa = PBXBuildFile; fileRef = E938D4E6289B0DCD00B8C968 /* Live.swift */; }; - E938D4E9289B0E0000B8C968 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E938D4E8289B0E0000B8C968 /* Mock.swift */; }; E967004028C142010071FDB3 /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = E967003F28C142010071FDB3 /* Models */; }; E9A116352322E96C004DC9FB /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E9A116342322E96C004DC9FB /* Colors.xcassets */; }; E9A22D3D2896C7A3006DC054 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = E9A22D3C2896C7A3006DC054 /* ComposableArchitecture */; }; + E9A259B828C145F20092218A /* UVClient in Frameworks */ = {isa = PBXBuildFile; productRef = E9A259B728C145F20092218A /* UVClient */; }; E9AF349C289C4FE600C0F763 /* AppReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AF349B289C4FE600C0F763 /* AppReducerTests.swift */; }; E9AF349F289C58DD00C0F763 /* Bugsnag in Frameworks */ = {isa = PBXBuildFile; productRef = E9AF349E289C58DD00C0F763 /* Bugsnag */; }; E9AF34A3289C606D00C0F763 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AF34A2289C606D00C0F763 /* App.swift */; }; - E9E29EC02896CD7C000DE660 /* UVClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E29EBF2896CD7C000DE660 /* UVClient.swift */; }; E9E29EC42896E0A7000DE660 /* ComposableCoreLocation in Frameworks */ = {isa = PBXBuildFile; productRef = E9E29EC32896E0A7000DE660 /* ComposableCoreLocation */; }; E9E29EC62896E346000DE660 /* LocationReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E29EC52896E346000DE660 /* LocationReducer.swift */; }; EBB0615B21A1CF9A0084E975 /* IndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB0615A21A1CF9A0084E975 /* IndexTests.swift */; }; @@ -43,13 +41,10 @@ 84B83E859B0ADFAB9D8946E1 /* Pods-swiftUV.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-swiftUV.release.xcconfig"; path = "Pods/Target Support Files/Pods-swiftUV/Pods-swiftUV.release.xcconfig"; sourceTree = ""; }; E931727E2896C5E1003EC842 /* ContentVIew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ContentVIew.swift; path = app/Views/ContentVIew.swift; sourceTree = ""; }; E938D4E0289B0C7000B8C968 /* AppReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppReducer.swift; path = app/Views/AppReducer.swift; sourceTree = ""; }; - E938D4E6289B0DCD00B8C968 /* Live.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Live.swift; sourceTree = ""; }; - E938D4E8289B0E0000B8C968 /* Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mock.swift; sourceTree = ""; }; E9720BB828C140E200771EC4 /* uv-today-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "uv-today-ios"; path = ..; sourceTree = ""; }; E9A116342322E96C004DC9FB /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; E9AF349B289C4FE600C0F763 /* AppReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducerTests.swift; sourceTree = ""; }; E9AF34A2289C606D00C0F763 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; - E9E29EBF2896CD7C000DE660 /* UVClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UVClient.swift; sourceTree = ""; }; E9E29EC52896E346000DE660 /* LocationReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LocationReducer.swift; path = app/Views/LocationReducer.swift; sourceTree = ""; }; EBB0615A21A1CF9A0084E975 /* IndexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexTests.swift; sourceTree = ""; }; EBC1904E1E8EEAB600B9F2EC /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/LaunchScreen.strings; sourceTree = ""; }; @@ -76,6 +71,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E9A259B828C145F20092218A /* UVClient in Frameworks */, E967004028C142010071FDB3 /* Models in Frameworks */, E9E29EC42896E0A7000DE660 /* ComposableCoreLocation in Frameworks */, E9A22D3D2896C7A3006DC054 /* ComposableArchitecture in Frameworks */, @@ -104,17 +100,6 @@ name = Pods; sourceTree = ""; }; - E938D4E5289B0D0500B8C968 /* UVClient */ = { - isa = PBXGroup; - children = ( - E9E29EBF2896CD7C000DE660 /* UVClient.swift */, - E938D4E6289B0DCD00B8C968 /* Live.swift */, - E938D4E8289B0E0000B8C968 /* Mock.swift */, - ); - name = UVClient; - path = app/UVClient; - sourceTree = ""; - }; EBB061462198956B0084E975 /* Views */ = { isa = PBXGroup; children = ( @@ -180,9 +165,7 @@ EBF4B7631E79917000B7A616 /* app */ = { isa = PBXGroup; children = ( - E938D4E5289B0D0500B8C968 /* UVClient */, EBB061462198956B0084E975 /* Views */, - EBF4B77A1E799BC600B7A616 /* Models */, E9AF34A2289C606D00C0F763 /* App.swift */, ); name = app; @@ -196,13 +179,6 @@ name = Resources; sourceTree = ""; }; - EBF4B77A1E799BC600B7A616 /* Models */ = { - isa = PBXGroup; - children = ( - ); - name = Models; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -247,6 +223,7 @@ E9E29EC32896E0A7000DE660 /* ComposableCoreLocation */, E9AF349E289C58DD00C0F763 /* Bugsnag */, E967003F28C142010071FDB3 /* Models */, + E9A259B728C145F20092218A /* UVClient */, ); productName = swiftUV; productReference = EBF4B74C1E7951D400B7A616 /* swiftUV.app */; @@ -388,10 +365,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E9E29EC02896CD7C000DE660 /* UVClient.swift in Sources */, E938D4E1289B0C7000B8C968 /* AppReducer.swift in Sources */, - E938D4E7289B0DCD00B8C968 /* Live.swift in Sources */, - E938D4E9289B0E0000B8C968 /* Mock.swift in Sources */, E9AF34A3289C606D00C0F763 /* App.swift in Sources */, E931727F2896C5E1003EC842 /* ContentVIew.swift in Sources */, E9E29EC62896E346000DE660 /* LocationReducer.swift in Sources */, @@ -737,6 +711,10 @@ package = E9A22D3B2896C7A3006DC054 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */; productName = ComposableArchitecture; }; + E9A259B728C145F20092218A /* UVClient */ = { + isa = XCSwiftPackageProductDependency; + productName = UVClient; + }; E9AF349E289C58DD00C0F763 /* Bugsnag */ = { isa = XCSwiftPackageProductDependency; package = E9AF349D289C58DD00C0F763 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */; diff --git a/swiftUV/swiftUV/App.swift b/swiftUV/swiftUV/App.swift index 0740477..53bdc0a 100644 --- a/swiftUV/swiftUV/App.swift +++ b/swiftUV/swiftUV/App.swift @@ -34,7 +34,7 @@ struct SwiftUVApp: App { class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { #if RELEASE - Bugsnag.start(withApiKey: SwiftUVKeys().bugsnagApiKey) + Bugsnag.start(withApiKey: "b6ee27da8e2f6e0b9641e9c2f2fc6d41") #endif return true } diff --git a/swiftUV/swiftUV/app/UVClient/UVClient.swift b/swiftUV/swiftUV/app/UVClient/UVClient.swift deleted file mode 100644 index 89f999c..0000000 --- a/swiftUV/swiftUV/app/UVClient/UVClient.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// WeatherClient.swift -// swiftUV -// -// Created by Thomas Guilleminot on 31/07/2022. -// Copyright © 2022 Thomas Guilleminot. All rights reserved. -// - -import ComposableArchitecture -import Models - -struct UVClientRequest { - let lat: Double - let long: Double -} - -struct UVClient { - var fetchUVIndex: @Sendable (UVClientRequest) async throws -> Forecast - var fetchCityName: @Sendable (Location) async throws -> String - - struct Failure: Error, Equatable { - let errorDescription: String - } -} diff --git a/swiftUV/swiftUV/app/Views/AppReducer.swift b/swiftUV/swiftUV/app/Views/AppReducer.swift index 051f648..65ddbcc 100644 --- a/swiftUV/swiftUV/app/Views/AppReducer.swift +++ b/swiftUV/swiftUV/app/Views/AppReducer.swift @@ -9,6 +9,7 @@ import ComposableArchitecture import ComposableCoreLocation import Models +import UVClient struct AppState: Equatable { var uvIndex: Index = 0 From 621e6b5d9ba26eda0846079189fcda68eaf227f0 Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Thu, 1 Sep 2022 22:54:11 +0200 Subject: [PATCH 05/18] feat(modularization): completely remove cocoapods --- Package.swift | 9 +- swiftUV/Podfile | 27 ------ swiftUV/Podfile.lock | 23 ----- swiftUV/swiftUV.xcodeproj/project.pbxproj | 88 ++++--------------- .../contents.xcworkspacedata | 2 +- .../xcshareddata/swiftpm/Package.resolved | 12 +-- .../contents.xcworkspacedata | 10 --- .../xcshareddata/IDEWorkspaceChecks.plist | 8 -- swiftUV/swiftUV/App.swift | 1 - 9 files changed, 30 insertions(+), 150 deletions(-) delete mode 100644 swiftUV/Podfile delete mode 100644 swiftUV/Podfile.lock rename swiftUV/{swiftUV.xcworkspace => swiftUV.xcodeproj/project.xcworkspace}/xcshareddata/swiftpm/Package.resolved (89%) delete mode 100644 swiftUV/swiftUV.xcworkspace/contents.xcworkspacedata delete mode 100644 swiftUV/swiftUV.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/Package.swift b/Package.swift index 95efe2f..8de3e05 100644 --- a/Package.swift +++ b/Package.swift @@ -11,9 +11,16 @@ let package = Package( .library(name: "UVClient", targets: ["UVClient"]) ], dependencies: [ + .package(url: "https://github.com/pointfreeco/swift-composable-architecture", exact: "0.39.1") ], targets: [ .target(name: "Models"), - .target(name: "UVClient") + .target( + name: "UVClient", + dependencies: [ + "Models", + .product(name: "ComposableArchitecture", package: "swift-composable-architecture") + ] + ) ] ) diff --git a/swiftUV/Podfile b/swiftUV/Podfile deleted file mode 100644 index a8e3015..0000000 --- a/swiftUV/Podfile +++ /dev/null @@ -1,27 +0,0 @@ -source 'https://cdn.cocoapods.org/' - -inhibit_all_warnings! - -platform :ios, '14.0' - -target 'swiftUV' do - use_frameworks! - - # Pods for swiftUV - pod 'SwiftLint', '= 0.48.0' -end - -plugin 'cocoapods-keys', { - :project => "swiftUV", - :keys => [ - "OpenWeatherMapApiKey", - "BugsnagApiKey" -]} - -post_install do |pi| - pi.pods_project.targets.each do |t| - t.build_configurations.each do |config| - config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0' - end - end -end diff --git a/swiftUV/Podfile.lock b/swiftUV/Podfile.lock deleted file mode 100644 index ff005d7..0000000 --- a/swiftUV/Podfile.lock +++ /dev/null @@ -1,23 +0,0 @@ -PODS: - - Keys (1.0.1) - - SwiftLint (0.48.0) - -DEPENDENCIES: - - Keys (from `Pods/CocoaPodsKeys`) - - SwiftLint (= 0.48.0) - -SPEC REPOS: - trunk: - - SwiftLint - -EXTERNAL SOURCES: - Keys: - :path: Pods/CocoaPodsKeys - -SPEC CHECKSUMS: - Keys: a576f4c9c1c641ca913a959a9c62ed3f215a8de9 - SwiftLint: 284cea64b6187c5d6bd83e9a548a64104d546447 - -PODFILE CHECKSUM: a4a78f1ad95aec9fc5bc5c53a54a54098520791d - -COCOAPODS: 1.11.3 diff --git a/swiftUV/swiftUV.xcodeproj/project.pbxproj b/swiftUV/swiftUV.xcodeproj/project.pbxproj index 96a0659..9ee38f0 100644 --- a/swiftUV/swiftUV.xcodeproj/project.pbxproj +++ b/swiftUV/swiftUV.xcodeproj/project.pbxproj @@ -7,13 +7,12 @@ objects = { /* Begin PBXBuildFile section */ - 6538403F53A4E29309F2F084 /* Pods_swiftUV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 59AC0D333DE20DF5F86F5B5C /* Pods_swiftUV.framework */; }; E931727F2896C5E1003EC842 /* ContentVIew.swift in Sources */ = {isa = PBXBuildFile; fileRef = E931727E2896C5E1003EC842 /* ContentVIew.swift */; }; E938D4E1289B0C7000B8C968 /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E938D4E0289B0C7000B8C968 /* AppReducer.swift */; }; - E967004028C142010071FDB3 /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = E967003F28C142010071FDB3 /* Models */; }; + E989314D28C14F2C00F973FD /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = E989314C28C14F2C00F973FD /* Models */; }; + E989314F28C14F3300F973FD /* UVClient in Frameworks */ = {isa = PBXBuildFile; productRef = E989314E28C14F3300F973FD /* UVClient */; }; E9A116352322E96C004DC9FB /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E9A116342322E96C004DC9FB /* Colors.xcassets */; }; E9A22D3D2896C7A3006DC054 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = E9A22D3C2896C7A3006DC054 /* ComposableArchitecture */; }; - E9A259B828C145F20092218A /* UVClient in Frameworks */ = {isa = PBXBuildFile; productRef = E9A259B728C145F20092218A /* UVClient */; }; E9AF349C289C4FE600C0F763 /* AppReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AF349B289C4FE600C0F763 /* AppReducerTests.swift */; }; E9AF349F289C58DD00C0F763 /* Bugsnag in Frameworks */ = {isa = PBXBuildFile; productRef = E9AF349E289C58DD00C0F763 /* Bugsnag */; }; E9AF34A3289C606D00C0F763 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AF34A2289C606D00C0F763 /* App.swift */; }; @@ -36,9 +35,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 4F32DAD95650411C27D7704A /* Pods-swiftUV.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-swiftUV.debug.xcconfig"; path = "Pods/Target Support Files/Pods-swiftUV/Pods-swiftUV.debug.xcconfig"; sourceTree = ""; }; - 59AC0D333DE20DF5F86F5B5C /* Pods_swiftUV.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_swiftUV.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 84B83E859B0ADFAB9D8946E1 /* Pods-swiftUV.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-swiftUV.release.xcconfig"; path = "Pods/Target Support Files/Pods-swiftUV/Pods-swiftUV.release.xcconfig"; sourceTree = ""; }; E931727E2896C5E1003EC842 /* ContentVIew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ContentVIew.swift; path = app/Views/ContentVIew.swift; sourceTree = ""; }; E938D4E0289B0C7000B8C968 /* AppReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppReducer.swift; path = app/Views/AppReducer.swift; sourceTree = ""; }; E9720BB828C140E200771EC4 /* uv-today-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "uv-today-ios"; path = ..; sourceTree = ""; }; @@ -71,35 +67,24 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E9A259B828C145F20092218A /* UVClient in Frameworks */, - E967004028C142010071FDB3 /* Models in Frameworks */, E9E29EC42896E0A7000DE660 /* ComposableCoreLocation in Frameworks */, E9A22D3D2896C7A3006DC054 /* ComposableArchitecture in Frameworks */, + E989314D28C14F2C00F973FD /* Models in Frameworks */, E9AF349F289C58DD00C0F763 /* Bugsnag in Frameworks */, - 6538403F53A4E29309F2F084 /* Pods_swiftUV.framework in Frameworks */, + E989314F28C14F3300F973FD /* UVClient in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 768820A9F08C906FE75D8B77 /* Frameworks */ = { + E989314B28C14F2C00F973FD /* Frameworks */ = { isa = PBXGroup; children = ( - 59AC0D333DE20DF5F86F5B5C /* Pods_swiftUV.framework */, ); name = Frameworks; sourceTree = ""; }; - E022E5CBE6E03142E37391DA /* Pods */ = { - isa = PBXGroup; - children = ( - 4F32DAD95650411C27D7704A /* Pods-swiftUV.debug.xcconfig */, - 84B83E859B0ADFAB9D8946E1 /* Pods-swiftUV.release.xcconfig */, - ); - name = Pods; - sourceTree = ""; - }; EBB061462198956B0084E975 /* Views */ = { isa = PBXGroup; children = ( @@ -135,8 +120,7 @@ EBF4B74E1E7951D400B7A616 /* swiftUV */, EBDA88941FAE5C89009514AB /* swiftUVTests */, EBF4B74D1E7951D400B7A616 /* Products */, - E022E5CBE6E03142E37391DA /* Pods */, - 768820A9F08C906FE75D8B77 /* Frameworks */, + E989314B28C14F2C00F973FD /* Frameworks */, ); sourceTree = ""; }; @@ -196,8 +180,6 @@ EBDA88991FAE5C89009514AB /* PBXTargetDependency */, ); name = swiftUVTests; - packageProductDependencies = ( - ); productName = swiftUVTests; productReference = EBDA88931FAE5C89009514AB /* swiftUVTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -206,11 +188,9 @@ isa = PBXNativeTarget; buildConfigurationList = EBF4B75E1E7951D400B7A616 /* Build configuration list for PBXNativeTarget "swiftUV" */; buildPhases = ( - FF2CBAE60D64C458AECA6782 /* [CP] Check Pods Manifest.lock */, EBF4B7481E7951D300B7A616 /* Sources */, EBF4B7491E7951D300B7A616 /* Frameworks */, EBF4B74A1E7951D300B7A616 /* Resources */, - 8DAFA6781280E0C278DF29AA /* [CP] Embed Pods Frameworks */, EBC31C9F1FAF7FC6004DC13C /* SwiftLint */, ); buildRules = ( @@ -222,8 +202,8 @@ E9A22D3C2896C7A3006DC054 /* ComposableArchitecture */, E9E29EC32896E0A7000DE660 /* ComposableCoreLocation */, E9AF349E289C58DD00C0F763 /* Bugsnag */, - E967003F28C142010071FDB3 /* Models */, - E9A259B728C145F20092218A /* UVClient */, + E989314C28C14F2C00F973FD /* Models */, + E989314E28C14F3300F973FD /* UVClient */, ); productName = swiftUV; productReference = EBF4B74C1E7951D400B7A616 /* swiftUV.app */; @@ -299,24 +279,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 8DAFA6781280E0C278DF29AA /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-swiftUV/Pods-swiftUV-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/Keys-framework/Keys.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Keys.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-swiftUV/Pods-swiftUV-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; EBC31C9F1FAF7FC6004DC13C /* SwiftLint */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 12; @@ -329,25 +291,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\"\n"; - }; - FF2CBAE60D64C458AECA6782 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-swiftUV-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\nif which swiftlint > /dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -590,7 +534,6 @@ }; EBF4B75F1E7951D400B7A616 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4F32DAD95650411C27D7704A /* Pods-swiftUV.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Apple Development"; @@ -618,7 +561,6 @@ }; EBF4B7601E7951D400B7A616 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 84B83E859B0ADFAB9D8946E1 /* Pods-swiftUV.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; @@ -680,7 +622,7 @@ repositoryURL = "https://github.com/pointfreeco/swift-composable-architecture"; requirement = { kind = exactVersion; - version = 0.39.0; + version = 0.39.1; }; }; E9AF349D289C58DD00C0F763 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */ = { @@ -702,19 +644,19 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - E967003F28C142010071FDB3 /* Models */ = { + E989314C28C14F2C00F973FD /* Models */ = { isa = XCSwiftPackageProductDependency; productName = Models; }; + E989314E28C14F3300F973FD /* UVClient */ = { + isa = XCSwiftPackageProductDependency; + productName = UVClient; + }; E9A22D3C2896C7A3006DC054 /* ComposableArchitecture */ = { isa = XCSwiftPackageProductDependency; package = E9A22D3B2896C7A3006DC054 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */; productName = ComposableArchitecture; }; - E9A259B728C145F20092218A /* UVClient */ = { - isa = XCSwiftPackageProductDependency; - productName = UVClient; - }; E9AF349E289C58DD00C0F763 /* Bugsnag */ = { isa = XCSwiftPackageProductDependency; package = E9AF349D289C58DD00C0F763 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */; diff --git a/swiftUV/swiftUV.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 03dd840..919434a 100644 --- a/swiftUV/swiftUV.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/swiftUV/swiftUV.xcworkspace/xcshareddata/swiftpm/Package.resolved b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved similarity index 89% rename from swiftUV/swiftUV.xcworkspace/xcshareddata/swiftpm/Package.resolved rename to swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 998e0b1..fb6c23b 100644 --- a/swiftUV/swiftUV.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/combine-schedulers", "state" : { - "revision" : "f7c8277f05f27a5bfb2f6ecccb0bad126ffcf472", - "version" : "0.7.0" + "revision" : "9e42b4b0453da417a44daa17174103e7d1c5be07", + "version" : "0.7.3" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-composable-architecture", "state" : { - "revision" : "108e3a536fcebb16c4f247ef92c2d7326baf9fe3", - "version" : "0.39.0" + "revision" : "a518935116b2bada7234f47073159b433d432af1", + "version" : "0.39.1" } }, { @@ -77,8 +77,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", "state" : { - "revision" : "ef8e14e7ce1c0c304c644c6ba365d06c468ded6b", - "version" : "0.3.3" + "revision" : "38bc9242e4388b80bd23ddfdf3071428859e3260", + "version" : "0.4.0" } } ], diff --git a/swiftUV/swiftUV.xcworkspace/contents.xcworkspacedata b/swiftUV/swiftUV.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index ef12187..0000000 --- a/swiftUV/swiftUV.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/swiftUV/swiftUV.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/swiftUV/swiftUV.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/swiftUV/swiftUV.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/swiftUV/swiftUV/App.swift b/swiftUV/swiftUV/App.swift index 53bdc0a..f1f7b8f 100644 --- a/swiftUV/swiftUV/App.swift +++ b/swiftUV/swiftUV/App.swift @@ -9,7 +9,6 @@ import SwiftUI import ComposableArchitecture import Bugsnag -import Keys @main struct SwiftUVApp: App { From 294c0fa6554643744296ebbc8aac09e5c51cb9e9 Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Thu, 1 Sep 2022 23:05:22 +0200 Subject: [PATCH 06/18] feat(modularization): add appFeature module --- Package.swift | 11 +++- .../AppFeature}/AppReducer.swift | 57 ++++++++++++++----- .../AppFeature}/ContentVIew.swift | 8 ++- .../AppFeature}/LocationReducer.swift | 0 swiftUV/swiftUV.xcodeproj/project.pbxproj | 27 +++------ swiftUV/swiftUV/App.swift | 1 + 6 files changed, 67 insertions(+), 37 deletions(-) rename {swiftUV/swiftUV/app/Views => Sources/AppFeature}/AppReducer.swift (70%) rename {swiftUV/swiftUV/app/Views => Sources/AppFeature}/ContentVIew.swift (95%) rename {swiftUV/swiftUV/app/Views => Sources/AppFeature}/LocationReducer.swift (100%) diff --git a/Package.swift b/Package.swift index 8de3e05..770245f 100644 --- a/Package.swift +++ b/Package.swift @@ -7,13 +7,22 @@ let package = Package( name: "uv-today-ios", platforms: [.iOS(.v15)], products: [ + .library(name: "AppFeature", targets: ["AppFeature"]), .library(name: "Models", targets: ["Models"]), .library(name: "UVClient", targets: ["UVClient"]) ], dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", exact: "0.39.1") + .package(url: "https://github.com/pointfreeco/swift-composable-architecture", exact: "0.39.1"), + .package(url: "https://github.com/pointfreeco/composable-core-location", exact: "0.2.0"), ], targets: [ + .target( + name: "AppFeature", + dependencies: [ + .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + .product(name: "ComposableCoreLocation", package: "composable-core-location") + ] + ), .target(name: "Models"), .target( name: "UVClient", diff --git a/swiftUV/swiftUV/app/Views/AppReducer.swift b/Sources/AppFeature/AppReducer.swift similarity index 70% rename from swiftUV/swiftUV/app/Views/AppReducer.swift rename to Sources/AppFeature/AppReducer.swift index 65ddbcc..5c0a256 100644 --- a/swiftUV/swiftUV/app/Views/AppReducer.swift +++ b/Sources/AppFeature/AppReducer.swift @@ -11,22 +11,46 @@ import ComposableCoreLocation import Models import UVClient -struct AppState: Equatable { - var uvIndex: Index = 0 - var cityName = "loading" - var weatherRequestInFlight = false - var getCityNameRequestInFlight = false - var errorText = "" +public struct AppState: Equatable { + var uvIndex: Index + var cityName: String + var weatherRequestInFlight: Bool + var getCityNameRequestInFlight: Bool + var errorText: String var userLocation: Models.Location? - var isRequestingCurrentLocation = false - var hasAlreadyRequestLocation = false - var isLocationRefused = false + var isRequestingCurrentLocation: Bool + var hasAlreadyRequestLocation: Bool + var isLocationRefused: Bool @BindableState var shouldShowErrorPopup = false + + public init( + uvIndex: Index = 0, + cityName: String = "loading", + weatherRequestInFlight: Bool = false, + getCityNameRequestInFlight: Bool = false, + errorText: String = "", + + userLocation: Models.Location? = nil, + isRequestingCurrentLocation: Bool = false, + hasAlreadyRequestLocation: Bool = false, + isLocationRefused: Bool = false + ) { + self.uvIndex = uvIndex + self.cityName = cityName + self.weatherRequestInFlight = weatherRequestInFlight + self.getCityNameRequestInFlight = getCityNameRequestInFlight + self.errorText = errorText + + self.userLocation = userLocation + self.isRequestingCurrentLocation = isRequestingCurrentLocation + self.hasAlreadyRequestLocation = hasAlreadyRequestLocation + self.isLocationRefused = isLocationRefused + } } -enum AppAction: Equatable, BindableAction { +public enum AppAction: Equatable, BindableAction { case getUVRequest case getUVResponse(TaskResult) case getCityNameResponse(TaskResult) @@ -37,12 +61,17 @@ enum AppAction: Equatable, BindableAction { case binding(BindingAction) } -struct AppEnvironment { - var uvClient: UVClient - var locationManager: LocationManager +public struct AppEnvironment { + public var uvClient: UVClient + public var locationManager: LocationManager + + public init(uvClient: UVClient, locationManager: LocationManager) { + self.uvClient = uvClient + self.locationManager = locationManager + } } -let appReducer = Reducer { state, action, environment in +public let appReducer = Reducer { state, action, environment in switch action { case .onAppear: state.weatherRequestInFlight = true diff --git a/swiftUV/swiftUV/app/Views/ContentVIew.swift b/Sources/AppFeature/ContentVIew.swift similarity index 95% rename from swiftUV/swiftUV/app/Views/ContentVIew.swift rename to Sources/AppFeature/ContentVIew.swift index 14d0d26..ee39981 100644 --- a/swiftUV/swiftUV/app/Views/ContentVIew.swift +++ b/Sources/AppFeature/ContentVIew.swift @@ -10,10 +10,14 @@ import ComposableArchitecture import ComposableCoreLocation import SwiftUI -struct ContentView: View { +public struct ContentView: View { let store: Store - var body: some View { + public init(store: Store) { + self.store = store + } + + public var body: some View { WithViewStore(self.store) { viewStore in ZStack { Rectangle() diff --git a/swiftUV/swiftUV/app/Views/LocationReducer.swift b/Sources/AppFeature/LocationReducer.swift similarity index 100% rename from swiftUV/swiftUV/app/Views/LocationReducer.swift rename to Sources/AppFeature/LocationReducer.swift diff --git a/swiftUV/swiftUV.xcodeproj/project.pbxproj b/swiftUV/swiftUV.xcodeproj/project.pbxproj index 9ee38f0..3640685 100644 --- a/swiftUV/swiftUV.xcodeproj/project.pbxproj +++ b/swiftUV/swiftUV.xcodeproj/project.pbxproj @@ -7,17 +7,15 @@ objects = { /* Begin PBXBuildFile section */ - E931727F2896C5E1003EC842 /* ContentVIew.swift in Sources */ = {isa = PBXBuildFile; fileRef = E931727E2896C5E1003EC842 /* ContentVIew.swift */; }; - E938D4E1289B0C7000B8C968 /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E938D4E0289B0C7000B8C968 /* AppReducer.swift */; }; E989314D28C14F2C00F973FD /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = E989314C28C14F2C00F973FD /* Models */; }; E989314F28C14F3300F973FD /* UVClient in Frameworks */ = {isa = PBXBuildFile; productRef = E989314E28C14F3300F973FD /* UVClient */; }; + E989315128C1541700F973FD /* AppFeature in Frameworks */ = {isa = PBXBuildFile; productRef = E989315028C1541700F973FD /* AppFeature */; }; E9A116352322E96C004DC9FB /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E9A116342322E96C004DC9FB /* Colors.xcassets */; }; E9A22D3D2896C7A3006DC054 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = E9A22D3C2896C7A3006DC054 /* ComposableArchitecture */; }; E9AF349C289C4FE600C0F763 /* AppReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AF349B289C4FE600C0F763 /* AppReducerTests.swift */; }; E9AF349F289C58DD00C0F763 /* Bugsnag in Frameworks */ = {isa = PBXBuildFile; productRef = E9AF349E289C58DD00C0F763 /* Bugsnag */; }; E9AF34A3289C606D00C0F763 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AF34A2289C606D00C0F763 /* App.swift */; }; E9E29EC42896E0A7000DE660 /* ComposableCoreLocation in Frameworks */ = {isa = PBXBuildFile; productRef = E9E29EC32896E0A7000DE660 /* ComposableCoreLocation */; }; - E9E29EC62896E346000DE660 /* LocationReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E29EC52896E346000DE660 /* LocationReducer.swift */; }; EBB0615B21A1CF9A0084E975 /* IndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB0615A21A1CF9A0084E975 /* IndexTests.swift */; }; EBCAA0451E7C30F000DC2E9D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = EBCAA0471E7C30F000DC2E9D /* Localizable.strings */; }; EBF4B7571E7951D400B7A616 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EBF4B7561E7951D400B7A616 /* Assets.xcassets */; }; @@ -35,13 +33,10 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - E931727E2896C5E1003EC842 /* ContentVIew.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ContentVIew.swift; path = app/Views/ContentVIew.swift; sourceTree = ""; }; - E938D4E0289B0C7000B8C968 /* AppReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppReducer.swift; path = app/Views/AppReducer.swift; sourceTree = ""; }; E9720BB828C140E200771EC4 /* uv-today-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "uv-today-ios"; path = ..; sourceTree = ""; }; E9A116342322E96C004DC9FB /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; E9AF349B289C4FE600C0F763 /* AppReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducerTests.swift; sourceTree = ""; }; E9AF34A2289C606D00C0F763 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; - E9E29EC52896E346000DE660 /* LocationReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LocationReducer.swift; path = app/Views/LocationReducer.swift; sourceTree = ""; }; EBB0615A21A1CF9A0084E975 /* IndexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexTests.swift; sourceTree = ""; }; EBC1904E1E8EEAB600B9F2EC /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/LaunchScreen.strings; sourceTree = ""; }; EBC1904F1E8EEAB600B9F2EC /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; @@ -68,6 +63,7 @@ buildActionMask = 2147483647; files = ( E9E29EC42896E0A7000DE660 /* ComposableCoreLocation in Frameworks */, + E989315128C1541700F973FD /* AppFeature in Frameworks */, E9A22D3D2896C7A3006DC054 /* ComposableArchitecture in Frameworks */, E989314D28C14F2C00F973FD /* Models in Frameworks */, E9AF349F289C58DD00C0F763 /* Bugsnag in Frameworks */, @@ -85,16 +81,6 @@ name = Frameworks; sourceTree = ""; }; - EBB061462198956B0084E975 /* Views */ = { - isa = PBXGroup; - children = ( - E931727E2896C5E1003EC842 /* ContentVIew.swift */, - E938D4E0289B0C7000B8C968 /* AppReducer.swift */, - E9E29EC52896E346000DE660 /* LocationReducer.swift */, - ); - name = Views; - sourceTree = ""; - }; EBCAA0421E7C30CE00DC2E9D /* Translation */ = { isa = PBXGroup; children = ( @@ -149,7 +135,6 @@ EBF4B7631E79917000B7A616 /* app */ = { isa = PBXGroup; children = ( - EBB061462198956B0084E975 /* Views */, E9AF34A2289C606D00C0F763 /* App.swift */, ); name = app; @@ -204,6 +189,7 @@ E9AF349E289C58DD00C0F763 /* Bugsnag */, E989314C28C14F2C00F973FD /* Models */, E989314E28C14F3300F973FD /* UVClient */, + E989315028C1541700F973FD /* AppFeature */, ); productName = swiftUV; productReference = EBF4B74C1E7951D400B7A616 /* swiftUV.app */; @@ -309,10 +295,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E938D4E1289B0C7000B8C968 /* AppReducer.swift in Sources */, E9AF34A3289C606D00C0F763 /* App.swift in Sources */, - E931727F2896C5E1003EC842 /* ContentVIew.swift in Sources */, - E9E29EC62896E346000DE660 /* LocationReducer.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -652,6 +635,10 @@ isa = XCSwiftPackageProductDependency; productName = UVClient; }; + E989315028C1541700F973FD /* AppFeature */ = { + isa = XCSwiftPackageProductDependency; + productName = AppFeature; + }; E9A22D3C2896C7A3006DC054 /* ComposableArchitecture */ = { isa = XCSwiftPackageProductDependency; package = E9A22D3B2896C7A3006DC054 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */; diff --git a/swiftUV/swiftUV/App.swift b/swiftUV/swiftUV/App.swift index f1f7b8f..cc0f7db 100644 --- a/swiftUV/swiftUV/App.swift +++ b/swiftUV/swiftUV/App.swift @@ -6,6 +6,7 @@ // Copyright © 2022 Thomas Guilleminot. All rights reserved. // +import AppFeature import SwiftUI import ComposableArchitecture import Bugsnag From d5d589573a921d5d98cb0bb4349beb764b8c6c7d Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Fri, 2 Sep 2022 09:20:52 +0200 Subject: [PATCH 07/18] feat(modularization): add test modules --- Package.swift | 17 +++++++++ Sources/AppFeature/AppReducer.swift | 25 ++++++------ Sources/AppFeature/ContentVIew.swift | 2 +- Sources/Models/Index.swift | 2 +- .../AppFeatureTests}/AppReducerTests.swift | 4 +- .../ModelsTests}/IndexTests.swift | 5 +-- .../uv-today-iosTests/uv_today_iosTests.swift | 11 ------ swiftUV/swiftUV.xcodeproj/project.pbxproj | 12 ------ .../xcshareddata/xcschemes/swiftUV.xcscheme | 20 ++++++++++ swiftUV/swiftUV/Colors.xcassets/Contents.json | 6 --- .../LightRed.colorset/Contents.json | 38 ------------------- 11 files changed, 56 insertions(+), 86 deletions(-) rename {swiftUV/swiftUVTests => Tests/AppFeatureTests}/AppReducerTests.swift (99%) rename {swiftUV/swiftUVTests => Tests/ModelsTests}/IndexTests.swift (91%) delete mode 100644 Tests/uv-today-iosTests/uv_today_iosTests.swift delete mode 100644 swiftUV/swiftUV/Colors.xcassets/Contents.json delete mode 100644 swiftUV/swiftUV/Colors.xcassets/LightRed.colorset/Contents.json diff --git a/Package.swift b/Package.swift index 770245f..6263b35 100644 --- a/Package.swift +++ b/Package.swift @@ -19,6 +19,8 @@ let package = Package( .target( name: "AppFeature", dependencies: [ + "Models", + "UVClient", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "ComposableCoreLocation", package: "composable-core-location") ] @@ -30,6 +32,21 @@ let package = Package( "Models", .product(name: "ComposableArchitecture", package: "swift-composable-architecture") ] + ), + .testTarget( + name: "AppFeatureTests", + dependencies: [ + "AppFeature", + "Models", + "UVClient", + .product(name: "ComposableArchitecture", package: "swift-composable-architecture") + ] + ), + .testTarget( + name: "ModelsTests", + dependencies: [ + "Models" + ] ) ] ) diff --git a/Sources/AppFeature/AppReducer.swift b/Sources/AppFeature/AppReducer.swift index 5c0a256..5a33b18 100644 --- a/Sources/AppFeature/AppReducer.swift +++ b/Sources/AppFeature/AppReducer.swift @@ -12,18 +12,18 @@ import Models import UVClient public struct AppState: Equatable { - var uvIndex: Index - var cityName: String - var weatherRequestInFlight: Bool - var getCityNameRequestInFlight: Bool - var errorText: String + public var uvIndex: Index + public var cityName: String + public var weatherRequestInFlight: Bool + public var getCityNameRequestInFlight: Bool + public var errorText: String - var userLocation: Models.Location? - var isRequestingCurrentLocation: Bool - var hasAlreadyRequestLocation: Bool - var isLocationRefused: Bool + public var userLocation: Models.Location? + public var isRequestingCurrentLocation: Bool + public var hasAlreadyRequestLocation: Bool + public var isLocationRefused: Bool - @BindableState var shouldShowErrorPopup = false + @BindableState public var shouldShowErrorPopup = false public init( uvIndex: Index = 0, @@ -31,11 +31,11 @@ public struct AppState: Equatable { weatherRequestInFlight: Bool = false, getCityNameRequestInFlight: Bool = false, errorText: String = "", - userLocation: Models.Location? = nil, isRequestingCurrentLocation: Bool = false, hasAlreadyRequestLocation: Bool = false, - isLocationRefused: Bool = false + isLocationRefused: Bool = false, + shouldShowErrorPopup: Bool = false ) { self.uvIndex = uvIndex self.cityName = cityName @@ -47,6 +47,7 @@ public struct AppState: Equatable { self.isRequestingCurrentLocation = isRequestingCurrentLocation self.hasAlreadyRequestLocation = hasAlreadyRequestLocation self.isLocationRefused = isLocationRefused + self.shouldShowErrorPopup = shouldShowErrorPopup } } diff --git a/Sources/AppFeature/ContentVIew.swift b/Sources/AppFeature/ContentVIew.swift index ee39981..935348a 100644 --- a/Sources/AppFeature/ContentVIew.swift +++ b/Sources/AppFeature/ContentVIew.swift @@ -101,7 +101,7 @@ struct ContentView_Previews: PreviewProvider { ContentView( store: Store( initialState: AppState( - uvIndex: 1, + uvIndex: 6, cityName: "Gueugnon", weatherRequestInFlight: false, getCityNameRequestInFlight: false diff --git a/Sources/Models/Index.swift b/Sources/Models/Index.swift index 2540015..c1043e4 100644 --- a/Sources/Models/Index.swift +++ b/Sources/Models/Index.swift @@ -17,7 +17,7 @@ extension Index { case 0: return UIColor.systemBlue case 1, 2: return UIColor.systemGreen case 3, 4, 5: return UIColor.systemYellow - case 6, 7: return UIColor(named: "LightRed")! + case 6, 7: return UIColor(red: 0.906, green: 0.373, blue: 0.184, alpha: 1) case 8, 9, 10: return UIColor.systemRed case 11, 12, 13, 14: return UIColor.systemPurple default : return UIColor.black diff --git a/swiftUV/swiftUVTests/AppReducerTests.swift b/Tests/AppFeatureTests/AppReducerTests.swift similarity index 99% rename from swiftUV/swiftUVTests/AppReducerTests.swift rename to Tests/AppFeatureTests/AppReducerTests.swift index 4a6ad08..eefe3f0 100644 --- a/swiftUV/swiftUVTests/AppReducerTests.swift +++ b/Tests/AppFeatureTests/AppReducerTests.swift @@ -9,8 +9,8 @@ import Foundation import ComposableArchitecture import XCTest - -@testable import swiftUV +import AppFeature +import Models @MainActor class AppReducerTests: XCTestCase { diff --git a/swiftUV/swiftUVTests/IndexTests.swift b/Tests/ModelsTests/IndexTests.swift similarity index 91% rename from swiftUV/swiftUVTests/IndexTests.swift rename to Tests/ModelsTests/IndexTests.swift index fccc0b0..8e79502 100644 --- a/swiftUV/swiftUVTests/IndexTests.swift +++ b/Tests/ModelsTests/IndexTests.swift @@ -7,8 +7,7 @@ // import XCTest - -@testable import swiftUV +import Models class IndexTests: XCTestCase { @@ -18,7 +17,7 @@ class IndexTests: XCTestCase { case 0: XCTAssertEqual(index.associatedColor, UIColor.systemBlue) case 1, 2: XCTAssertEqual(index.associatedColor, UIColor.systemGreen) case 3, 4, 5: XCTAssertEqual(index.associatedColor, UIColor.systemYellow) - case 6, 7: XCTAssertEqual(index.associatedColor, UIColor(named: "LightRed")!) + case 6, 7: XCTAssertEqual(index.associatedColor, UIColor(red: 0.906, green: 0.373, blue: 0.184, alpha: 1)) case 8, 9, 10: XCTAssertEqual(index.associatedColor, UIColor.systemRed) case 11, 12, 13, 14: XCTAssertEqual(index.associatedColor, UIColor.systemPurple) default : XCTAssertEqual(index.associatedColor, UIColor.black) diff --git a/Tests/uv-today-iosTests/uv_today_iosTests.swift b/Tests/uv-today-iosTests/uv_today_iosTests.swift deleted file mode 100644 index 07a794b..0000000 --- a/Tests/uv-today-iosTests/uv_today_iosTests.swift +++ /dev/null @@ -1,11 +0,0 @@ -import XCTest -@testable import uv_today_ios - -final class uv_today_iosTests: XCTestCase { - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. - XCTAssertEqual(uv_today_ios().text, "Hello, World!") - } -} diff --git a/swiftUV/swiftUV.xcodeproj/project.pbxproj b/swiftUV/swiftUV.xcodeproj/project.pbxproj index 3640685..792e043 100644 --- a/swiftUV/swiftUV.xcodeproj/project.pbxproj +++ b/swiftUV/swiftUV.xcodeproj/project.pbxproj @@ -10,13 +10,10 @@ E989314D28C14F2C00F973FD /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = E989314C28C14F2C00F973FD /* Models */; }; E989314F28C14F3300F973FD /* UVClient in Frameworks */ = {isa = PBXBuildFile; productRef = E989314E28C14F3300F973FD /* UVClient */; }; E989315128C1541700F973FD /* AppFeature in Frameworks */ = {isa = PBXBuildFile; productRef = E989315028C1541700F973FD /* AppFeature */; }; - E9A116352322E96C004DC9FB /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E9A116342322E96C004DC9FB /* Colors.xcassets */; }; E9A22D3D2896C7A3006DC054 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = E9A22D3C2896C7A3006DC054 /* ComposableArchitecture */; }; - E9AF349C289C4FE600C0F763 /* AppReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AF349B289C4FE600C0F763 /* AppReducerTests.swift */; }; E9AF349F289C58DD00C0F763 /* Bugsnag in Frameworks */ = {isa = PBXBuildFile; productRef = E9AF349E289C58DD00C0F763 /* Bugsnag */; }; E9AF34A3289C606D00C0F763 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AF34A2289C606D00C0F763 /* App.swift */; }; E9E29EC42896E0A7000DE660 /* ComposableCoreLocation in Frameworks */ = {isa = PBXBuildFile; productRef = E9E29EC32896E0A7000DE660 /* ComposableCoreLocation */; }; - EBB0615B21A1CF9A0084E975 /* IndexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBB0615A21A1CF9A0084E975 /* IndexTests.swift */; }; EBCAA0451E7C30F000DC2E9D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = EBCAA0471E7C30F000DC2E9D /* Localizable.strings */; }; EBF4B7571E7951D400B7A616 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EBF4B7561E7951D400B7A616 /* Assets.xcassets */; }; EBF4B75A1E7951D400B7A616 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EBF4B7581E7951D400B7A616 /* LaunchScreen.storyboard */; }; @@ -34,10 +31,7 @@ /* Begin PBXFileReference section */ E9720BB828C140E200771EC4 /* uv-today-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "uv-today-ios"; path = ..; sourceTree = ""; }; - E9A116342322E96C004DC9FB /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; - E9AF349B289C4FE600C0F763 /* AppReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducerTests.swift; sourceTree = ""; }; E9AF34A2289C606D00C0F763 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; - EBB0615A21A1CF9A0084E975 /* IndexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexTests.swift; sourceTree = ""; }; EBC1904E1E8EEAB600B9F2EC /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/LaunchScreen.strings; sourceTree = ""; }; EBC1904F1E8EEAB600B9F2EC /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; EBCAA0461E7C30F000DC2E9D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; @@ -93,8 +87,6 @@ isa = PBXGroup; children = ( EBDA88971FAE5C89009514AB /* Info.plist */, - EBB0615A21A1CF9A0084E975 /* IndexTests.swift */, - E9AF349B289C4FE600C0F763 /* AppReducerTests.swift */, ); path = swiftUVTests; sourceTree = ""; @@ -125,7 +117,6 @@ EBF4B7631E79917000B7A616 /* app */, EBF4B7641E79918100B7A616 /* Resources */, EBF4B7561E7951D400B7A616 /* Assets.xcassets */, - E9A116342322E96C004DC9FB /* Colors.xcassets */, EBF4B7581E7951D400B7A616 /* LaunchScreen.storyboard */, EBF4B75B1E7951D400B7A616 /* Info.plist */, ); @@ -258,7 +249,6 @@ EBF4B75A1E7951D400B7A616 /* LaunchScreen.storyboard in Resources */, EBCAA0451E7C30F000DC2E9D /* Localizable.strings in Resources */, EBF4B7571E7951D400B7A616 /* Assets.xcassets in Resources */, - E9A116352322E96C004DC9FB /* Colors.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -286,8 +276,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E9AF349C289C4FE600C0F763 /* AppReducerTests.swift in Sources */, - EBB0615B21A1CF9A0084E975 /* IndexTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme b/swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme index 38cc31c..a866acf 100644 --- a/swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme +++ b/swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme @@ -48,6 +48,26 @@ + + + + + + + + Date: Fri, 2 Sep 2022 18:02:56 +0200 Subject: [PATCH 08/18] feat(modularization): add github action for PR --- .github/workflows/run-tests.yml | 18 ++++++++++++++++++ .swift-version | 2 +- swiftUV/fastlane/Fastfile | 5 +---- 3 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/run-tests.yml diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..d7d6090 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,18 @@ +name: run-tests +on: + pull_request: + branches: + - 'develop' +jobs: + build: + runs-on: macos-12 + steps: + - uses: swift-actions/setup-swift@v1 + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '13.4.1' + - uses: actions/checkout@v3 + - uses: maierj/fastlane-action@v2.2.0 + with: + lane: 'tests' + subdirectory: 'swiftUV/fastlane' diff --git a/.swift-version b/.swift-version index 819e07a..2df33d7 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -5.0 +5.6 diff --git a/swiftUV/fastlane/Fastfile b/swiftUV/fastlane/Fastfile index edfb7b6..77d80c1 100644 --- a/swiftUV/fastlane/Fastfile +++ b/swiftUV/fastlane/Fastfile @@ -18,11 +18,8 @@ default_platform(:ios) platform :ios do desc "Run tests" lane :tests do - cocoapods run_tests( - workspace: "swiftUV.xcworkspace", - devices: "iPhone 11 Pro", - reinstall_app: true, + devices: "iPhone 13 Pro", scheme: "swiftUV" ) end From ead65b1f99ef9e070a0b432753dc6ddc2edaa03c Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Sun, 16 Oct 2022 14:39:12 +0200 Subject: [PATCH 09/18] feat(reducer-protocol): migrate reducers to ReducerProtocol --- Package.swift | 2 +- Sources/AppFeature/AppReducer.swift | 302 +++++++++--------- Sources/AppFeature/ContentVIew.swift | 14 +- Sources/AppFeature/LocationReducer.swift | 31 +- swiftUV/swiftUV.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 +- swiftUV/swiftUV/App.swift | 8 +- 7 files changed, 181 insertions(+), 186 deletions(-) diff --git a/Package.swift b/Package.swift index 6263b35..b2e459e 100644 --- a/Package.swift +++ b/Package.swift @@ -12,7 +12,7 @@ let package = Package( .library(name: "UVClient", targets: ["UVClient"]) ], dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", exact: "0.39.1"), + .package(url: "https://github.com/pointfreeco/swift-composable-architecture", exact: "0.42.0"), .package(url: "https://github.com/pointfreeco/composable-core-location", exact: "0.2.0"), ], targets: [ diff --git a/Sources/AppFeature/AppReducer.swift b/Sources/AppFeature/AppReducer.swift index 5a33b18..793f44c 100644 --- a/Sources/AppFeature/AppReducer.swift +++ b/Sources/AppFeature/AppReducer.swift @@ -11,171 +11,169 @@ import ComposableCoreLocation import Models import UVClient -public struct AppState: Equatable { - public var uvIndex: Index - public var cityName: String - public var weatherRequestInFlight: Bool - public var getCityNameRequestInFlight: Bool - public var errorText: String - - public var userLocation: Models.Location? - public var isRequestingCurrentLocation: Bool - public var hasAlreadyRequestLocation: Bool - public var isLocationRefused: Bool - - @BindableState public var shouldShowErrorPopup = false +public struct AppReducer: ReducerProtocol { + public struct State: Equatable { + public var uvIndex: Index + public var cityName: String + public var weatherRequestInFlight: Bool + public var getCityNameRequestInFlight: Bool + public var errorText: String + public var userLocation: Models.Location? + public var isRequestingCurrentLocation: Bool + public var hasAlreadyRequestLocation: Bool + public var isLocationRefused: Bool + + @BindableState public var shouldShowErrorPopup: Bool + + public init( + uvIndex: Index = 0, + cityName: String = "loading", + weatherRequestInFlight: Bool = false, + getCityNameRequestInFlight: Bool = false, + errorText: String = "", + userLocation: Models.Location? = nil, + isRequestingCurrentLocation: Bool = false, + hasAlreadyRequestLocation: Bool = false, + isLocationRefused: Bool = false, + shouldShowErrorPopup: Bool = false + ) { + self.uvIndex = uvIndex + self.cityName = cityName + self.weatherRequestInFlight = weatherRequestInFlight + self.getCityNameRequestInFlight = getCityNameRequestInFlight + self.errorText = errorText + self.userLocation = userLocation + self.isRequestingCurrentLocation = isRequestingCurrentLocation + self.hasAlreadyRequestLocation = hasAlreadyRequestLocation + self.isLocationRefused = isLocationRefused + self.shouldShowErrorPopup = shouldShowErrorPopup + } + } - public init( - uvIndex: Index = 0, - cityName: String = "loading", - weatherRequestInFlight: Bool = false, - getCityNameRequestInFlight: Bool = false, - errorText: String = "", - userLocation: Models.Location? = nil, - isRequestingCurrentLocation: Bool = false, - hasAlreadyRequestLocation: Bool = false, - isLocationRefused: Bool = false, - shouldShowErrorPopup: Bool = false - ) { - self.uvIndex = uvIndex - self.cityName = cityName - self.weatherRequestInFlight = weatherRequestInFlight - self.getCityNameRequestInFlight = getCityNameRequestInFlight - self.errorText = errorText + public enum Action: Equatable, BindableAction { + case getUVRequest + case getUVResponse(TaskResult) + case getCityNameResponse(TaskResult) - self.userLocation = userLocation - self.isRequestingCurrentLocation = isRequestingCurrentLocation - self.hasAlreadyRequestLocation = hasAlreadyRequestLocation - self.isLocationRefused = isLocationRefused - self.shouldShowErrorPopup = shouldShowErrorPopup + case onAppear + case onDisappear + case locationManager(LocationManager.Action) + case binding(BindingAction) } -} - -public enum AppAction: Equatable, BindableAction { - case getUVRequest - case getUVResponse(TaskResult) - case getCityNameResponse(TaskResult) - - case onAppear - case onDisappear - case locationManager(LocationManager.Action) - case binding(BindingAction) -} - -public struct AppEnvironment { - public var uvClient: UVClient - public var locationManager: LocationManager + + public let uvClient: UVClient + public let locationManager: LocationManager public init(uvClient: UVClient, locationManager: LocationManager) { self.uvClient = uvClient self.locationManager = locationManager } -} - -public let appReducer = Reducer { state, action, environment in - switch action { - case .onAppear: - state.weatherRequestInFlight = true - state.getCityNameRequestInFlight = true - state.isRequestingCurrentLocation = true - state.isLocationRefused = false - - switch environment.locationManager.authorizationStatus() { - case .notDetermined: - return .merge( - environment.locationManager - .delegate() - .map(AppAction.locationManager), - - environment.locationManager - .requestWhenInUseAuthorization() - .fireAndForget() - ) - - case .authorizedAlways, .authorizedWhenInUse: - return .merge( - environment.locationManager - .delegate() - .map(AppAction.locationManager), - environment.locationManager - .requestLocation() - .fireAndForget() - ) - - case .restricted, .denied: - state.shouldShowErrorPopup = true - state.errorText = "app.error.localisationDisabled".localized - state.isLocationRefused = true - return .none - - @unknown default: - return .none - } - - case .onDisappear: - state.hasAlreadyRequestLocation = false - return .none + public var body: some ReducerProtocol { + BindingReducer() - case .getUVRequest: - state.weatherRequestInFlight = true - state.getCityNameRequestInFlight = true - - guard let location = state.userLocation else { - state.shouldShowErrorPopup = true - state.errorText = "app.error.couldNotLocalise".localized - return .none - } - - return .run { send in - async let fetchUV: Void = send( - .getUVResponse(TaskResult { try await environment.uvClient.fetchUVIndex(UVClientRequest(lat: location.latitude, long: location.longitude)) }) - ) - - async let fetchCityName: Void = send( - .getCityNameResponse(TaskResult { try await environment.uvClient.fetchCityName(location) }) - ) + Reduce { state, action in + switch action { + case .onAppear: + state.weatherRequestInFlight = true + state.getCityNameRequestInFlight = true + state.isRequestingCurrentLocation = true + state.isLocationRefused = false + + switch locationManager.authorizationStatus() { + case .notDetermined: + return .merge( + locationManager + .delegate() + .map(AppReducer.Action.locationManager), - _ = await [fetchUV, fetchCityName] - } + locationManager + .requestWhenInUseAuthorization() + .fireAndForget() + ) - case .getUVResponse(.success(let forecast)): - state.weatherRequestInFlight = false - state.uvIndex = Int(forecast.value) - return .none - - case .getUVResponse(.failure(let error)): - state.weatherRequestInFlight = false - state.shouldShowErrorPopup = true - state.errorText = error.localizedDescription - state.uvIndex = 0 - return .none + case .authorizedAlways, .authorizedWhenInUse: + return .merge( + locationManager + .delegate() + .map(AppReducer.Action.locationManager), - case .getCityNameResponse(.success(let city)): - state.getCityNameRequestInFlight = false - state.cityName = city - return .none - - case .getCityNameResponse(.failure(let error)): - state.getCityNameRequestInFlight = false - state.cityName = "app.label.unknown".localized - return .none - - case .binding(\.$shouldShowErrorPopup): - state.shouldShowErrorPopup = false - return .none - - case .binding: - return .none + locationManager + .requestLocation() + .fireAndForget() + ) + + case .restricted, .denied: + state.shouldShowErrorPopup = true + state.errorText = "app.error.localisationDisabled".localized + state.isLocationRefused = true + return .none + + @unknown default: + return .none + } + + case .onDisappear: + state.hasAlreadyRequestLocation = false + return .none + + case .getUVRequest: + state.weatherRequestInFlight = true + state.getCityNameRequestInFlight = true + + guard let location = state.userLocation else { + state.shouldShowErrorPopup = true + state.errorText = "app.error.couldNotLocalise".localized + return .none + } + + return .run { send in + async let fetchUV: Void = send( + .getUVResponse(TaskResult { try await uvClient.fetchUVIndex(UVClientRequest(lat: location.latitude, long: location.longitude)) }) + ) + + async let fetchCityName: Void = send( + .getCityNameResponse(TaskResult { try await uvClient.fetchCityName(location) }) + ) + + _ = await [fetchUV, fetchCityName] + } + + case .getUVResponse(.success(let forecast)): + state.weatherRequestInFlight = false + state.uvIndex = Int(forecast.value) + return .none + + case .getUVResponse(.failure(let error)): + state.weatherRequestInFlight = false + state.shouldShowErrorPopup = true + state.errorText = error.localizedDescription + state.uvIndex = 0 + return .none + + case .getCityNameResponse(.success(let city)): + state.getCityNameRequestInFlight = false + state.cityName = city + return .none + + case .getCityNameResponse(.failure): + state.getCityNameRequestInFlight = false + state.cityName = "app.label.unknown".localized + return .none + + case .binding(\.$shouldShowErrorPopup): + state.shouldShowErrorPopup = false + return .none + + case .binding: + return .none + + case .locationManager: + return .none + } + } + ._printChanges() - case .locationManager: - return .none + LocationReducer() } } -.combined( - with: - locationManagerReducer - .pullback(state: \.self, action: /AppAction.self, environment: { $0 }) -) -.binding() -.debug() diff --git a/Sources/AppFeature/ContentVIew.swift b/Sources/AppFeature/ContentVIew.swift index 935348a..ea749b4 100644 --- a/Sources/AppFeature/ContentVIew.swift +++ b/Sources/AppFeature/ContentVIew.swift @@ -11,14 +11,14 @@ import ComposableCoreLocation import SwiftUI public struct ContentView: View { - let store: Store + let store: StoreOf - public init(store: Store) { + public init(store: StoreOf) { self.store = store } public var body: some View { - WithViewStore(self.store) { viewStore in + WithViewStore(self.store, observe: { $0 }) { viewStore in ZStack { Rectangle() .animation(.easeIn(duration: 0.5), value: viewStore.uvIndex.associatedColor) @@ -100,17 +100,13 @@ struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView( store: Store( - initialState: AppState( + initialState: AppReducer.State( uvIndex: 6, cityName: "Gueugnon", weatherRequestInFlight: false, getCityNameRequestInFlight: false ), - reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - locationManager: .live - ) + reducer: AppReducer(uvClient: .mock, locationManager: .live) ) ) } diff --git a/Sources/AppFeature/LocationReducer.swift b/Sources/AppFeature/LocationReducer.swift index f1e4db8..baa4ab7 100644 --- a/Sources/AppFeature/LocationReducer.swift +++ b/Sources/AppFeature/LocationReducer.swift @@ -11,20 +11,25 @@ import ComposableCoreLocation import Foundation import Models -let locationManagerReducer = Reducer { state, action, _ in - switch action { - case let .locationManager(.didUpdateLocations(locations)): - guard state.hasAlreadyRequestLocation == false else { +struct LocationReducer: ReducerProtocol { + typealias State = AppReducer.State + typealias Action = AppReducer.Action + + func reduce(into state: inout AppReducer.State, action: AppReducer.Action) -> Effect { + switch action { + case let .locationManager(.didUpdateLocations(locations)): + guard state.hasAlreadyRequestLocation == false else { + return .none + } + + state.isRequestingCurrentLocation = false + guard let location = locations.first else { return .none } + state.userLocation = Models.Location(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude) + state.hasAlreadyRequestLocation = true + return .task { .getUVRequest } + + default: return .none } - - state.isRequestingCurrentLocation = false - guard let location = locations.first else { return .none } - state.userLocation = Models.Location(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude) - state.hasAlreadyRequestLocation = true - return .task { .getUVRequest } - - default: - return .none } } diff --git a/swiftUV/swiftUV.xcodeproj/project.pbxproj b/swiftUV/swiftUV.xcodeproj/project.pbxproj index 792e043..273953f 100644 --- a/swiftUV/swiftUV.xcodeproj/project.pbxproj +++ b/swiftUV/swiftUV.xcodeproj/project.pbxproj @@ -593,7 +593,7 @@ repositoryURL = "https://github.com/pointfreeco/swift-composable-architecture"; requirement = { kind = exactVersion; - version = 0.39.1; + version = 0.42.0; }; }; E9AF349D289C58DD00C0F763 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */ = { diff --git a/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index fb6c23b..a89e8f3 100644 --- a/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/combine-schedulers", "state" : { - "revision" : "9e42b4b0453da417a44daa17174103e7d1c5be07", - "version" : "0.7.3" + "revision" : "aa3e575929f2bcc5bad012bd2575eae716cbcdf7", + "version" : "0.8.0" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-composable-architecture", "state" : { - "revision" : "a518935116b2bada7234f47073159b433d432af1", - "version" : "0.39.1" + "revision" : "5c476994eaa79af8e466041f6de1ab116f37c528", + "version" : "0.42.0" } }, { diff --git a/swiftUV/swiftUV/App.swift b/swiftUV/swiftUV/App.swift index cc0f7db..95195b9 100644 --- a/swiftUV/swiftUV/App.swift +++ b/swiftUV/swiftUV/App.swift @@ -19,12 +19,8 @@ struct SwiftUVApp: App { WindowGroup { ContentView( store: Store( - initialState: AppState(), - reducer: appReducer, - environment: AppEnvironment( - uvClient: .live, - locationManager: .live - ) + initialState: AppReducer.State(), + reducer: AppReducer(uvClient: .live, locationManager: .live) ) ) } From 896bae7a61dda3bb5d239243ff9cd92dc85635cd Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Sun, 16 Oct 2022 14:51:04 +0200 Subject: [PATCH 10/18] feat(reducer-protocol): convert dependencies to @Dependency --- Package.swift | 8 +++++++ Sources/AppFeature/AppReducer.swift | 10 ++++----- Sources/AppFeature/ContentVIew.swift | 2 +- .../LocationManagerDependency.swift | 21 +++++++++++++++++++ Sources/UVClient/UVClient.swift | 15 ++++++++++++- swiftUV/swiftUV/App.swift | 2 +- 6 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 Sources/LocationManager/LocationManagerDependency.swift diff --git a/Package.swift b/Package.swift index b2e459e..b8e4a21 100644 --- a/Package.swift +++ b/Package.swift @@ -8,6 +8,7 @@ let package = Package( platforms: [.iOS(.v15)], products: [ .library(name: "AppFeature", targets: ["AppFeature"]), + .library(name: "LocationManager", targets: ["LocationManager"]), .library(name: "Models", targets: ["Models"]), .library(name: "UVClient", targets: ["UVClient"]) ], @@ -19,12 +20,19 @@ let package = Package( .target( name: "AppFeature", dependencies: [ + "LocationManager", "Models", "UVClient", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "ComposableCoreLocation", package: "composable-core-location") ] ), + .target( + name: "LocationManager", + dependencies: [ + .product(name: "ComposableCoreLocation", package: "composable-core-location") + ] + ), .target(name: "Models"), .target( name: "UVClient", diff --git a/Sources/AppFeature/AppReducer.swift b/Sources/AppFeature/AppReducer.swift index 793f44c..8b49982 100644 --- a/Sources/AppFeature/AppReducer.swift +++ b/Sources/AppFeature/AppReducer.swift @@ -8,6 +8,7 @@ import ComposableArchitecture import ComposableCoreLocation +import LocationManager import Models import UVClient @@ -61,13 +62,10 @@ public struct AppReducer: ReducerProtocol { case binding(BindingAction) } - public let uvClient: UVClient - public let locationManager: LocationManager + @Dependency(\.uvClient) public var uvClient: UVClient + @Dependency(\.locationManager) public var locationManager: LocationManager - public init(uvClient: UVClient, locationManager: LocationManager) { - self.uvClient = uvClient - self.locationManager = locationManager - } + public init() {} public var body: some ReducerProtocol { BindingReducer() diff --git a/Sources/AppFeature/ContentVIew.swift b/Sources/AppFeature/ContentVIew.swift index ea749b4..9936bd8 100644 --- a/Sources/AppFeature/ContentVIew.swift +++ b/Sources/AppFeature/ContentVIew.swift @@ -106,7 +106,7 @@ struct ContentView_Previews: PreviewProvider { weatherRequestInFlight: false, getCityNameRequestInFlight: false ), - reducer: AppReducer(uvClient: .mock, locationManager: .live) + reducer: AppReducer() ) ) } diff --git a/Sources/LocationManager/LocationManagerDependency.swift b/Sources/LocationManager/LocationManagerDependency.swift new file mode 100644 index 0000000..a23ca62 --- /dev/null +++ b/Sources/LocationManager/LocationManagerDependency.swift @@ -0,0 +1,21 @@ +// +// File.swift +// +// +// Created by Thomas Guilleminot on 16/10/2022. +// + +import ComposableCoreLocation +import Foundation + +private enum LocationManagerKey: DependencyKey { + static var liveValue = LocationManager.live + static var testValue = LocationManager.failing +} + +public extension DependencyValues { + var locationManager: LocationManager { + get { self[LocationManagerKey.self] } + set { self[LocationManagerKey.self] = newValue } + } +} diff --git a/Sources/UVClient/UVClient.swift b/Sources/UVClient/UVClient.swift index 52b0cc6..dec2215 100644 --- a/Sources/UVClient/UVClient.swift +++ b/Sources/UVClient/UVClient.swift @@ -21,9 +21,22 @@ public struct UVClientRequest { public struct UVClient { public var fetchUVIndex: @Sendable (UVClientRequest) async throws -> Forecast - public var fetchCityName: @Sendable (Location) async throws -> String + public var fetchCityName: @Sendable (Models.Location) async throws -> String struct Failure: Error, Equatable { let errorDescription: String } } + +private enum UVClientKey: DependencyKey { + static let liveValue = UVClient.live + static var testValue = UVClient.unimplemented + static var previewValue = UVClient.mock +} + +public extension DependencyValues { + var uvClient: UVClient { + get { self[UVClientKey.self] } + set { self[UVClientKey.self] = newValue } + } +} diff --git a/swiftUV/swiftUV/App.swift b/swiftUV/swiftUV/App.swift index 95195b9..8b011c2 100644 --- a/swiftUV/swiftUV/App.swift +++ b/swiftUV/swiftUV/App.swift @@ -20,7 +20,7 @@ struct SwiftUVApp: App { ContentView( store: Store( initialState: AppReducer.State(), - reducer: AppReducer(uvClient: .live, locationManager: .live) + reducer: AppReducer() ) ) } From 421a57028f85d241d88c07b6f19d91dba56b2176 Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Sun, 16 Oct 2022 14:53:54 +0200 Subject: [PATCH 11/18] feat(reducer-protocol): update tests --- Tests/AppFeatureTests/AppReducerTests.swift | 40 ++++++++------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/Tests/AppFeatureTests/AppReducerTests.swift b/Tests/AppFeatureTests/AppReducerTests.swift index eefe3f0..21fdaeb 100644 --- a/Tests/AppFeatureTests/AppReducerTests.swift +++ b/Tests/AppFeatureTests/AppReducerTests.swift @@ -18,16 +18,15 @@ class AppReducerTests: XCTestCase { let expectedForecast = Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5) let store = TestStore( initialState: - AppState( + AppReducer.State( getCityNameRequestInFlight: true, userLocation: Location(latitude: 12.0, longitude: 13.0) ), - reducer: appReducer, - environment: .unimplemented + reducer: AppReducer() ) - store.environment.uvClient.fetchUVIndex = { _ in Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5) } - store.environment.uvClient.fetchCityName = { _ in "Gueugnon" } + store.dependencies.uvClient.fetchUVIndex = { _ in Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5) } + store.dependencies.uvClient.fetchCityName = { _ in "Gueugnon" } await store.send(.getUVRequest) { $0.weatherRequestInFlight = true @@ -48,16 +47,15 @@ class AppReducerTests: XCTestCase { func testGetUVRequestFailure() async { let store = TestStore( initialState: - AppState( + AppReducer.State( getCityNameRequestInFlight: true, userLocation: Location(latitude: 12.0, longitude: 13.0) ), - reducer: appReducer, - environment: .unimplemented + reducer: AppReducer() ) - store.environment.uvClient.fetchUVIndex = { _ in throw "test" } - store.environment.uvClient.fetchCityName = { _ in throw "no city" } + store.dependencies.uvClient.fetchUVIndex = { _ in throw "test" } + store.dependencies.uvClient.fetchCityName = { _ in throw "no city" } await store.send(.getUVRequest) { $0.weatherRequestInFlight = true @@ -80,11 +78,10 @@ class AppReducerTests: XCTestCase { func testGetUVRequestFailureNoLocation() { let store = TestStore( initialState: - AppState( + AppReducer.State( getCityNameRequestInFlight: true ), - reducer: appReducer, - environment: .unimplemented + reducer: AppReducer() ) store.send(.getUVRequest) { @@ -97,11 +94,10 @@ class AppReducerTests: XCTestCase { func testDismissErrorPopup() { let store = TestStore( initialState: - AppState( + AppReducer.State( shouldShowErrorPopup: true ), - reducer: appReducer, - environment: .unimplemented + reducer: AppReducer() ) store.send(.set(\.$shouldShowErrorPopup, true)) { @@ -111,9 +107,8 @@ class AppReducerTests: XCTestCase { func testOnDisappear() { let store = TestStore( - initialState: AppState(hasAlreadyRequestLocation: true), - reducer: appReducer, - environment: .unimplemented + initialState: AppReducer.State(hasAlreadyRequestLocation: true), + reducer: AppReducer() ) store.send(.onDisappear) { @@ -122,13 +117,6 @@ class AppReducerTests: XCTestCase { } } -extension AppEnvironment { - static let unimplemented = Self( - uvClient: .unimplemented, - locationManager: .failing - ) -} - extension String: Error {} extension String: LocalizedError { public var errorDescription: String? { self } From 50869829b30f88848736b2bc711cda61cfec403d Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Tue, 18 Oct 2022 12:01:43 +0200 Subject: [PATCH 12/18] feat(reducer-protocol): update TCA to 0.43.0 --- .github/workflows/run-tests.yml | 2 +- Package.swift | 2 +- swiftUV/swiftUV.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ .../xcshareddata/swiftpm/Package.resolved | 4 ++-- .../xcshareddata/xcschemes/swiftUV.xcscheme | 2 +- 6 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index d7d6090..035f01a 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,7 +10,7 @@ jobs: - uses: swift-actions/setup-swift@v1 - uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '13.4.1' + xcode-version: '14.0.1' - uses: actions/checkout@v3 - uses: maierj/fastlane-action@v2.2.0 with: diff --git a/Package.swift b/Package.swift index b8e4a21..a8c036a 100644 --- a/Package.swift +++ b/Package.swift @@ -13,7 +13,7 @@ let package = Package( .library(name: "UVClient", targets: ["UVClient"]) ], dependencies: [ - .package(url: "https://github.com/pointfreeco/swift-composable-architecture", exact: "0.42.0"), + .package(url: "https://github.com/pointfreeco/swift-composable-architecture", exact: "0.43.0"), .package(url: "https://github.com/pointfreeco/composable-core-location", exact: "0.2.0"), ], targets: [ diff --git a/swiftUV/swiftUV.xcodeproj/project.pbxproj b/swiftUV/swiftUV.xcodeproj/project.pbxproj index 273953f..9441388 100644 --- a/swiftUV/swiftUV.xcodeproj/project.pbxproj +++ b/swiftUV/swiftUV.xcodeproj/project.pbxproj @@ -593,7 +593,7 @@ repositoryURL = "https://github.com/pointfreeco/swift-composable-architecture"; requirement = { kind = exactVersion; - version = 0.42.0; + version = 0.43.0; }; }; E9AF349D289C58DD00C0F763 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */ = { diff --git a/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a89e8f3..4c3394c 100644 --- a/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-composable-architecture", "state" : { - "revision" : "5c476994eaa79af8e466041f6de1ab116f37c528", - "version" : "0.42.0" + "revision" : "5bd450a8ac6a802f82d485bac219cbfacffa69fb", + "version" : "0.43.0" } }, { diff --git a/swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme b/swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme index a866acf..7aeaad2 100644 --- a/swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme +++ b/swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme @@ -91,7 +91,7 @@ From 374e19714df6f56b949d636b4b0bda81a18a52e8 Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Thu, 20 Oct 2022 16:09:11 +0200 Subject: [PATCH 13/18] feat(weatherkit): add weatherkit for ios16 --- .env.default.template | 2 -- .swift-version | 1 - Package.swift | 13 +++++---- Sources/AppFeature/AppReducer.swift | 8 +++--- Sources/Models/UVError.swift | 33 +++++++++++++++++++++++ Sources/UVClient/Live.swift | 26 ++++++++++++------ Sources/UVClient/Mock.swift | 2 +- Sources/UVClient/UVClient.swift | 2 +- swiftUV/swiftUV.xcodeproj/project.pbxproj | 8 ++++++ swiftUV/swiftUV/swiftUV.entitlements | 8 ++++++ 10 files changed, 81 insertions(+), 22 deletions(-) delete mode 100644 .env.default.template delete mode 100644 .swift-version create mode 100644 Sources/Models/UVError.swift create mode 100644 swiftUV/swiftUV/swiftUV.entitlements diff --git a/.env.default.template b/.env.default.template deleted file mode 100644 index a386da4..0000000 --- a/.env.default.template +++ /dev/null @@ -1,2 +0,0 @@ -APP_IDENTIFIER=APP_IDENTIFIER -APPLE_ID=APPLE_ID diff --git a/.swift-version b/.swift-version deleted file mode 100644 index 2df33d7..0000000 --- a/.swift-version +++ /dev/null @@ -1 +0,0 @@ -5.6 diff --git a/Package.swift b/Package.swift index a8c036a..76bc612 100644 --- a/Package.swift +++ b/Package.swift @@ -3,6 +3,9 @@ import PackageDescription +let tca: Target.Dependency = .product(name: "ComposableArchitecture", package: "swift-composable-architecture") +let tcaCoreLocation: Target.Dependency = .product(name: "ComposableCoreLocation", package: "composable-core-location") + let package = Package( name: "uv-today-ios", platforms: [.iOS(.v15)], @@ -23,14 +26,14 @@ let package = Package( "LocationManager", "Models", "UVClient", - .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ComposableCoreLocation", package: "composable-core-location") + tca, + tcaCoreLocation ] ), .target( name: "LocationManager", dependencies: [ - .product(name: "ComposableCoreLocation", package: "composable-core-location") + tcaCoreLocation ] ), .target(name: "Models"), @@ -38,7 +41,7 @@ let package = Package( name: "UVClient", dependencies: [ "Models", - .product(name: "ComposableArchitecture", package: "swift-composable-architecture") + tca ] ), .testTarget( @@ -47,7 +50,7 @@ let package = Package( "AppFeature", "Models", "UVClient", - .product(name: "ComposableArchitecture", package: "swift-composable-architecture") + tca ] ), .testTarget( diff --git a/Sources/AppFeature/AppReducer.swift b/Sources/AppFeature/AppReducer.swift index 8b49982..4b8741c 100644 --- a/Sources/AppFeature/AppReducer.swift +++ b/Sources/AppFeature/AppReducer.swift @@ -53,7 +53,7 @@ public struct AppReducer: ReducerProtocol { public enum Action: Equatable, BindableAction { case getUVRequest - case getUVResponse(TaskResult) + case getUVResponse(TaskResult) case getCityNameResponse(TaskResult) case onAppear @@ -137,9 +137,9 @@ public struct AppReducer: ReducerProtocol { _ = await [fetchUV, fetchCityName] } - case .getUVResponse(.success(let forecast)): + case .getUVResponse(.success(let index)): state.weatherRequestInFlight = false - state.uvIndex = Int(forecast.value) + state.uvIndex = index return .none case .getUVResponse(.failure(let error)): @@ -170,7 +170,7 @@ public struct AppReducer: ReducerProtocol { return .none } } - ._printChanges() + //._printChanges() LocationReducer() } diff --git a/Sources/Models/UVError.swift b/Sources/Models/UVError.swift new file mode 100644 index 0000000..ebd9bc3 --- /dev/null +++ b/Sources/Models/UVError.swift @@ -0,0 +1,33 @@ +// +// File.swift +// +// +// Created by Thomas Guilleminot on 20/10/2022. +// + +import Foundation + +public enum UVError: Error, Equatable { + + case urlNotValid + case noData(String) + case couldNotDecodeJSON + case noWeatherAvailable + case customError(String) + + public var localizedDescription: String { + switch self { + case .urlNotValid: + return "Url is invalid" + case .noData(let message): + return message + case .couldNotDecodeJSON: + return "Could not parse JSON" + case .noWeatherAvailable: + return "No weather data available" + case .customError(let message): + return message + } + } + +} diff --git a/Sources/UVClient/Live.swift b/Sources/UVClient/Live.swift index ec6c882..14db5f6 100644 --- a/Sources/UVClient/Live.swift +++ b/Sources/UVClient/Live.swift @@ -8,18 +8,28 @@ import CoreLocation import Models +import WeatherKit extension UVClient { public static let live = UVClient( fetchUVIndex: { request in - let url = URL( - string: K.Api.baseURL + String(format: K.Api.Endpoints.getUV, arguments: [request.lat, request.long, "73c6746593df6d9b6d412026b3db9239"]))! - do { - let (response, _) = try await URLSession.shared.data(from: url) - let forectast = try JSONDecoder().decode(Forecast.self, from: response) - return forectast - } catch { - throw error + if #available(iOS 16.0, *) { + let clLocation = CLLocation(latitude: request.lat, longitude: request.long) + let weather = try? await WeatherService.shared.weather(for: clLocation, including: .current) + + guard let weather else { throw UVError.noWeatherAvailable } + + return weather.uvIndex.value + } else { + let url = URL( + string: K.Api.baseURL + String(format: K.Api.Endpoints.getUV, arguments: [request.lat, request.long, "73c6746593df6d9b6d412026b3db9239"]))! + do { + let (response, _) = try await URLSession.shared.data(from: url) + let forectast = try JSONDecoder().decode(Forecast.self, from: response) + return Int(forectast.value) + } catch { + throw error + } } }, fetchCityName: { location in diff --git a/Sources/UVClient/Mock.swift b/Sources/UVClient/Mock.swift index 8e44db2..7db3ea8 100644 --- a/Sources/UVClient/Mock.swift +++ b/Sources/UVClient/Mock.swift @@ -15,7 +15,7 @@ import Models extension UVClient { public static let mock = Self( fetchUVIndex: { _ in - Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5) + 5 }, fetchCityName: { _ in "Gueugnon" diff --git a/Sources/UVClient/UVClient.swift b/Sources/UVClient/UVClient.swift index dec2215..954c5fb 100644 --- a/Sources/UVClient/UVClient.swift +++ b/Sources/UVClient/UVClient.swift @@ -20,7 +20,7 @@ public struct UVClientRequest { } public struct UVClient { - public var fetchUVIndex: @Sendable (UVClientRequest) async throws -> Forecast + public var fetchUVIndex: @Sendable (UVClientRequest) async throws -> Index public var fetchCityName: @Sendable (Models.Location) async throws -> String struct Failure: Error, Equatable { diff --git a/swiftUV/swiftUV.xcodeproj/project.pbxproj b/swiftUV/swiftUV.xcodeproj/project.pbxproj index 9441388..b5ec9d1 100644 --- a/swiftUV/swiftUV.xcodeproj/project.pbxproj +++ b/swiftUV/swiftUV.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + E91A10ED2901518F00C55E78 /* swiftUV.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = swiftUV.entitlements; sourceTree = ""; }; E9720BB828C140E200771EC4 /* uv-today-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "uv-today-ios"; path = ..; sourceTree = ""; }; E9AF34A2289C606D00C0F763 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; EBC1904E1E8EEAB600B9F2EC /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/LaunchScreen.strings; sourceTree = ""; }; @@ -114,6 +115,7 @@ EBF4B74E1E7951D400B7A616 /* swiftUV */ = { isa = PBXGroup; children = ( + E91A10ED2901518F00C55E78 /* swiftUV.entitlements */, EBF4B7631E79917000B7A616 /* app */, EBF4B7641E79918100B7A616 /* Resources */, EBF4B7561E7951D400B7A616 /* Assets.xcassets */, @@ -507,12 +509,14 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = swiftUV/swiftUV.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = XDX42S5M9N; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = XDX42S5M9N; INFOPLIST_FILE = swiftUV/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -524,6 +528,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "ff2f93fb-7946-4ffc-a9c8-b1a55133e36e"; PROVISIONING_PROFILE_SPECIFIER = "uv-today_dev"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "uv-today_dev"; SWIFT_SWIFT3_OBJC_INFERENCE = Off; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -534,9 +539,11 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = swiftUV/swiftUV.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = XDX42S5M9N; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = XDX42S5M9N; INFOPLIST_FILE = swiftUV/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -548,6 +555,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "f7c749cb-6205-4c81-895e-2b74e79d6421"; PROVISIONING_PROFILE_SPECIFIER = "uv-today_appstore"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "uv-today_appstore"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = RELEASE; SWIFT_SWIFT3_OBJC_INFERENCE = Off; SWIFT_VERSION = 5.0; diff --git a/swiftUV/swiftUV/swiftUV.entitlements b/swiftUV/swiftUV/swiftUV.entitlements new file mode 100644 index 0000000..ec84c03 --- /dev/null +++ b/swiftUV/swiftUV/swiftUV.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.developer.weatherkit + + + From 4d498f258e36ceb4007fdbb49e6dc8933fc1d7c8 Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Thu, 20 Oct 2022 16:10:20 +0200 Subject: [PATCH 14/18] feat(weatherkit): update tests --- Tests/AppFeatureTests/AppReducerTests.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Tests/AppFeatureTests/AppReducerTests.swift b/Tests/AppFeatureTests/AppReducerTests.swift index 21fdaeb..87ae978 100644 --- a/Tests/AppFeatureTests/AppReducerTests.swift +++ b/Tests/AppFeatureTests/AppReducerTests.swift @@ -15,7 +15,6 @@ import Models @MainActor class AppReducerTests: XCTestCase { func testGetUVRequestSuccess() async { - let expectedForecast = Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5) let store = TestStore( initialState: AppReducer.State( @@ -25,7 +24,7 @@ class AppReducerTests: XCTestCase { reducer: AppReducer() ) - store.dependencies.uvClient.fetchUVIndex = { _ in Forecast(lat: 12.0, lon: 13.0, dateIso: "32323", date: 1234, value: 5) } + store.dependencies.uvClient.fetchUVIndex = { _ in 5 } store.dependencies.uvClient.fetchCityName = { _ in "Gueugnon" } await store.send(.getUVRequest) { @@ -33,7 +32,7 @@ class AppReducerTests: XCTestCase { $0.getCityNameRequestInFlight = true } - await store.receive(.getUVResponse(.success(expectedForecast))) { + await store.receive(.getUVResponse(.success(5))) { $0.weatherRequestInFlight = false $0.uvIndex = 5 } From 7dbf6be6a5a166c4163ddb9b67e7598e606bdd70 Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Thu, 27 Oct 2022 22:21:09 +0200 Subject: [PATCH 15/18] feat(weatherkit): add attribution logo and link when using weatherkit --- Sources/AppFeature/AppReducer.swift | 19 ++++++++++++- Sources/AppFeature/ContentVIew.swift | 21 ++++++++++++++ Sources/Models/File.swift | 18 ++++++++++++ Sources/Models/UVError.swift | 3 ++ Sources/UVClient/Live.swift | 13 +++++++++ Sources/UVClient/Mock.swift | 9 +++++- Sources/UVClient/UVClient.swift | 1 + Tests/AppFeatureTests/AppReducerTests.swift | 31 +++++++++++++++++++++ 8 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 Sources/Models/File.swift diff --git a/Sources/AppFeature/AppReducer.swift b/Sources/AppFeature/AppReducer.swift index 4b8741c..5b86444 100644 --- a/Sources/AppFeature/AppReducer.swift +++ b/Sources/AppFeature/AppReducer.swift @@ -18,6 +18,8 @@ public struct AppReducer: ReducerProtocol { public var cityName: String public var weatherRequestInFlight: Bool public var getCityNameRequestInFlight: Bool + public var attributionLogo: URL? + public var attributionLink: URL? public var errorText: String public var userLocation: Models.Location? public var isRequestingCurrentLocation: Bool @@ -55,6 +57,8 @@ public struct AppReducer: ReducerProtocol { case getUVRequest case getUVResponse(TaskResult) case getCityNameResponse(TaskResult) + case getAttribution + case getAttributionResponse(TaskResult) case onAppear case onDisappear @@ -159,6 +163,19 @@ public struct AppReducer: ReducerProtocol { state.cityName = "app.label.unknown".localized return .none + case .getAttribution: + return .task { + await .getAttributionResponse(TaskResult { try await uvClient.fetchWeatherKitAttribution() }) + } + + case .getAttributionResponse(.success(let attribution)): + state.attributionLogo = attribution.logo + state.attributionLink = attribution.link + return .none + + case .getAttributionResponse(.failure): + return .none + case .binding(\.$shouldShowErrorPopup): state.shouldShowErrorPopup = false return .none @@ -170,7 +187,7 @@ public struct AppReducer: ReducerProtocol { return .none } } - //._printChanges() + ._printChanges() LocationReducer() } diff --git a/Sources/AppFeature/ContentVIew.swift b/Sources/AppFeature/ContentVIew.swift index 9936bd8..357f747 100644 --- a/Sources/AppFeature/ContentVIew.swift +++ b/Sources/AppFeature/ContentVIew.swift @@ -80,6 +80,22 @@ public struct ContentView: View { .foregroundColor(.white) .font(.system(size: 12)) .redacted(reason: viewStore.weatherRequestInFlight ? .placeholder : []) + + if let attributionLogo = viewStore.attributionLogo { + AsyncImage(url: attributionLogo, content: { image in + image + .resizable() + .scaledToFit() + .frame(maxWidth: 100) + }, placeholder: { + + }) + } + + if let attributionLink = viewStore.attributionLink { + Link(destination: attributionLink, label: { Text("Other data sources")}) + .foregroundColor(.white) + } } } .alert(isPresented: viewStore.binding(\.$shouldShowErrorPopup)) { @@ -91,6 +107,11 @@ public struct ContentView: View { .onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in viewStore.send(.onDisappear) } + .task { + if #available(iOS 16.0, *) { + viewStore.send(.getAttribution) + } + } } } } diff --git a/Sources/Models/File.swift b/Sources/Models/File.swift new file mode 100644 index 0000000..47ec6b8 --- /dev/null +++ b/Sources/Models/File.swift @@ -0,0 +1,18 @@ +// +// File.swift +// +// +// Created by Thomas Guilleminot on 27/10/2022. +// + +import Foundation + +public struct AttributionResponse: Equatable { + public let logo: URL + public let link: URL + + public init(logo: URL, link: URL) { + self.logo = logo + self.link = link + } +} diff --git a/Sources/Models/UVError.swift b/Sources/Models/UVError.swift index ebd9bc3..17676c1 100644 --- a/Sources/Models/UVError.swift +++ b/Sources/Models/UVError.swift @@ -13,6 +13,7 @@ public enum UVError: Error, Equatable { case noData(String) case couldNotDecodeJSON case noWeatherAvailable + case noAttributionAvailable case customError(String) public var localizedDescription: String { @@ -25,6 +26,8 @@ public enum UVError: Error, Equatable { return "Could not parse JSON" case .noWeatherAvailable: return "No weather data available" + case .noAttributionAvailable: + return "No attribution available for WeatherKit" case .customError(let message): return message } diff --git a/Sources/UVClient/Live.swift b/Sources/UVClient/Live.swift index 14db5f6..9a87db1 100644 --- a/Sources/UVClient/Live.swift +++ b/Sources/UVClient/Live.swift @@ -46,6 +46,19 @@ extension UVClient { continuation.resume(returning: cityName) } } + }, + fetchWeatherKitAttribution: { + if #available(iOS 16.0, *) { + do { + let attribution = try await WeatherService.shared.attribution + + return AttributionResponse(logo: attribution.combinedMarkDarkURL, link: attribution.legalPageURL) + } catch { + throw UVError.noAttributionAvailable + } + } else { + fatalError("Trying to call WeatherKit from non iOS 16 device") + } } ) } diff --git a/Sources/UVClient/Mock.swift b/Sources/UVClient/Mock.swift index 7db3ea8..08c82d0 100644 --- a/Sources/UVClient/Mock.swift +++ b/Sources/UVClient/Mock.swift @@ -19,6 +19,12 @@ extension UVClient { }, fetchCityName: { _ in "Gueugnon" + }, + fetchWeatherKitAttribution: { + AttributionResponse( + logo: URL(string:"https://www.logo.com")!, + link: URL(string: "https://www.link.com")! + ) } ) } @@ -26,7 +32,8 @@ extension UVClient { extension UVClient { public static let unimplemented = Self( fetchUVIndex: XCTUnimplemented("\(Self.self).fetchUVIndex)"), - fetchCityName: XCTUnimplemented("\(Self.self).fetchCityName") + fetchCityName: XCTUnimplemented("\(Self.self).fetchCityName"), + fetchWeatherKitAttribution: XCTUnimplemented("\(Self.self).fetchWeatherKitAttribution") ) } #endif diff --git a/Sources/UVClient/UVClient.swift b/Sources/UVClient/UVClient.swift index 954c5fb..7101c41 100644 --- a/Sources/UVClient/UVClient.swift +++ b/Sources/UVClient/UVClient.swift @@ -22,6 +22,7 @@ public struct UVClientRequest { public struct UVClient { public var fetchUVIndex: @Sendable (UVClientRequest) async throws -> Index public var fetchCityName: @Sendable (Models.Location) async throws -> String + public var fetchWeatherKitAttribution: @Sendable () async throws -> AttributionResponse struct Failure: Error, Equatable { let errorDescription: String diff --git a/Tests/AppFeatureTests/AppReducerTests.swift b/Tests/AppFeatureTests/AppReducerTests.swift index 87ae978..7d0defd 100644 --- a/Tests/AppFeatureTests/AppReducerTests.swift +++ b/Tests/AppFeatureTests/AppReducerTests.swift @@ -89,6 +89,37 @@ class AppReducerTests: XCTestCase { $0.errorText = "app.error.couldNotLocalise".localized } } + + func testGetAttributionSuccess() async { + let store = TestStore( + initialState: .init(), + reducer: AppReducer() + ) + + let url1 = URL(string: "https://www.url1.com")! + let url2 = URL(string: "https://www.url2.com")! + let attribution = AttributionResponse(logo: url1, link: url2) + + store.dependencies.uvClient.fetchWeatherKitAttribution = { attribution } + + await store.send(.getAttribution) + await store.receive(.getAttributionResponse(.success(attribution))) { + $0.attributionLogo = attribution.logo + $0.attributionLink = attribution.link + } + } + + func testGetAttributionFailure() async { + let store = TestStore( + initialState: .init(), + reducer: AppReducer() + ) + + store.dependencies.uvClient.fetchWeatherKitAttribution = { throw UVError.noAttributionAvailable } + + await store.send(.getAttribution) + await store.receive(.getAttributionResponse(.failure(UVError.noAttributionAvailable))) + } func testDismissErrorPopup() { let store = TestStore( From a7d370e2ce0423b50af3ad9b4cbe8d33c1c014c5 Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Tue, 8 Nov 2022 21:01:55 +0100 Subject: [PATCH 16/18] feat(develop): add firebase and remove bugsnag --- swiftUV/swiftUV.xcodeproj/project.pbxproj | 32 ++++-- .../xcshareddata/swiftpm/Package.resolved | 107 +++++++++++++++++- swiftUV/swiftUV/App.swift | 12 +- swiftUV/swiftUV/GoogleService-Info.plist | 34 ++++++ 4 files changed, 164 insertions(+), 21 deletions(-) create mode 100644 swiftUV/swiftUV/GoogleService-Info.plist diff --git a/swiftUV/swiftUV.xcodeproj/project.pbxproj b/swiftUV/swiftUV.xcodeproj/project.pbxproj index b5ec9d1..aa753d1 100644 --- a/swiftUV/swiftUV.xcodeproj/project.pbxproj +++ b/swiftUV/swiftUV.xcodeproj/project.pbxproj @@ -11,8 +11,10 @@ E989314F28C14F3300F973FD /* UVClient in Frameworks */ = {isa = PBXBuildFile; productRef = E989314E28C14F3300F973FD /* UVClient */; }; E989315128C1541700F973FD /* AppFeature in Frameworks */ = {isa = PBXBuildFile; productRef = E989315028C1541700F973FD /* AppFeature */; }; E9A22D3D2896C7A3006DC054 /* ComposableArchitecture in Frameworks */ = {isa = PBXBuildFile; productRef = E9A22D3C2896C7A3006DC054 /* ComposableArchitecture */; }; - E9AF349F289C58DD00C0F763 /* Bugsnag in Frameworks */ = {isa = PBXBuildFile; productRef = E9AF349E289C58DD00C0F763 /* Bugsnag */; }; E9AF34A3289C606D00C0F763 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9AF34A2289C606D00C0F763 /* App.swift */; }; + E9CB9A32291AEA71008A44DB /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */ = {isa = PBXBuildFile; productRef = E9CB9A31291AEA71008A44DB /* FirebaseAnalyticsWithoutAdIdSupport */; }; + E9CB9A34291AEA71008A44DB /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = E9CB9A33291AEA71008A44DB /* FirebaseCrashlytics */; }; + E9CB9A36291AEAB3008A44DB /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = E9CB9A35291AEAB3008A44DB /* GoogleService-Info.plist */; }; E9E29EC42896E0A7000DE660 /* ComposableCoreLocation in Frameworks */ = {isa = PBXBuildFile; productRef = E9E29EC32896E0A7000DE660 /* ComposableCoreLocation */; }; EBCAA0451E7C30F000DC2E9D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = EBCAA0471E7C30F000DC2E9D /* Localizable.strings */; }; EBF4B7571E7951D400B7A616 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EBF4B7561E7951D400B7A616 /* Assets.xcassets */; }; @@ -33,6 +35,7 @@ E91A10ED2901518F00C55E78 /* swiftUV.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = swiftUV.entitlements; sourceTree = ""; }; E9720BB828C140E200771EC4 /* uv-today-ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "uv-today-ios"; path = ..; sourceTree = ""; }; E9AF34A2289C606D00C0F763 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; + E9CB9A35291AEAB3008A44DB /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; EBC1904E1E8EEAB600B9F2EC /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/LaunchScreen.strings; sourceTree = ""; }; EBC1904F1E8EEAB600B9F2EC /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = ""; }; EBCAA0461E7C30F000DC2E9D /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; @@ -61,7 +64,8 @@ E989315128C1541700F973FD /* AppFeature in Frameworks */, E9A22D3D2896C7A3006DC054 /* ComposableArchitecture in Frameworks */, E989314D28C14F2C00F973FD /* Models in Frameworks */, - E9AF349F289C58DD00C0F763 /* Bugsnag in Frameworks */, + E9CB9A32291AEA71008A44DB /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */, + E9CB9A34291AEA71008A44DB /* FirebaseCrashlytics in Frameworks */, E989314F28C14F3300F973FD /* UVClient in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -121,6 +125,7 @@ EBF4B7561E7951D400B7A616 /* Assets.xcassets */, EBF4B7581E7951D400B7A616 /* LaunchScreen.storyboard */, EBF4B75B1E7951D400B7A616 /* Info.plist */, + E9CB9A35291AEAB3008A44DB /* GoogleService-Info.plist */, ); path = swiftUV; sourceTree = ""; @@ -179,10 +184,11 @@ packageProductDependencies = ( E9A22D3C2896C7A3006DC054 /* ComposableArchitecture */, E9E29EC32896E0A7000DE660 /* ComposableCoreLocation */, - E9AF349E289C58DD00C0F763 /* Bugsnag */, E989314C28C14F2C00F973FD /* Models */, E989314E28C14F3300F973FD /* UVClient */, E989315028C1541700F973FD /* AppFeature */, + E9CB9A31291AEA71008A44DB /* FirebaseAnalyticsWithoutAdIdSupport */, + E9CB9A33291AEA71008A44DB /* FirebaseCrashlytics */, ); productName = swiftUV; productReference = EBF4B74C1E7951D400B7A616 /* swiftUV.app */; @@ -224,7 +230,7 @@ packageReferences = ( E9A22D3B2896C7A3006DC054 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */, E9E29EC22896E0A7000DE660 /* XCRemoteSwiftPackageReference "composable-core-location" */, - E9AF349D289C58DD00C0F763 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */, + E9CB9A30291AEA71008A44DB /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, ); productRefGroup = EBF4B74D1E7951D400B7A616 /* Products */; projectDirPath = ""; @@ -249,6 +255,7 @@ buildActionMask = 2147483647; files = ( EBF4B75A1E7951D400B7A616 /* LaunchScreen.storyboard in Resources */, + E9CB9A36291AEAB3008A44DB /* GoogleService-Info.plist in Resources */, EBCAA0451E7C30F000DC2E9D /* Localizable.strings in Resources */, EBF4B7571E7951D400B7A616 /* Assets.xcassets in Resources */, ); @@ -604,12 +611,12 @@ version = 0.43.0; }; }; - E9AF349D289C58DD00C0F763 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */ = { + E9CB9A30291AEA71008A44DB /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/bugsnag/bugsnag-cocoa"; + repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { kind = exactVersion; - version = 6.21.0; + version = 10.1.0; }; }; E9E29EC22896E0A7000DE660 /* XCRemoteSwiftPackageReference "composable-core-location" */ = { @@ -640,10 +647,15 @@ package = E9A22D3B2896C7A3006DC054 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */; productName = ComposableArchitecture; }; - E9AF349E289C58DD00C0F763 /* Bugsnag */ = { + E9CB9A31291AEA71008A44DB /* FirebaseAnalyticsWithoutAdIdSupport */ = { isa = XCSwiftPackageProductDependency; - package = E9AF349D289C58DD00C0F763 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */; - productName = Bugsnag; + package = E9CB9A30291AEA71008A44DB /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAnalyticsWithoutAdIdSupport; + }; + E9CB9A33291AEA71008A44DB /* FirebaseCrashlytics */ = { + isa = XCSwiftPackageProductDependency; + package = E9CB9A30291AEA71008A44DB /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseCrashlytics; }; E9E29EC32896E0A7000DE660 /* ComposableCoreLocation */ = { isa = XCSwiftPackageProductDependency; diff --git a/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4c3394c..29bdf3d 100644 --- a/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,12 +1,21 @@ { "pins" : [ { - "identity" : "bugsnag-cocoa", + "identity" : "abseil-cpp-swiftpm", "kind" : "remoteSourceControl", - "location" : "https://github.com/bugsnag/bugsnag-cocoa", + "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git", "state" : { - "revision" : "1a5afefae616dbafcab3ad93c8bed0db1c841bc0", - "version" : "6.21.0" + "revision" : "583de9bd60f66b40e78d08599cc92036c2e7e4e1", + "version" : "0.20220203.2" + } + }, + { + "identity" : "boringssl-swiftpm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/boringssl-SwiftPM.git", + "state" : { + "revision" : "dd3eda2b05a3f459fc3073695ad1b28659066eab", + "version" : "0.9.1" } }, { @@ -27,6 +36,87 @@ "version" : "0.2.0" } }, + { + "identity" : "firebase-ios-sdk", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/firebase-ios-sdk.git", + "state" : { + "revision" : "f0926478fda955aef0dfbf99263b5c24b2ca5db4", + "version" : "10.1.0" + } + }, + { + "identity" : "googleappmeasurement", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleAppMeasurement.git", + "state" : { + "revision" : "71eb6700dd53a851473c48d392f00a3ab26699a6", + "version" : "10.1.0" + } + }, + { + "identity" : "googledatatransport", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleDataTransport.git", + "state" : { + "revision" : "5056b15c5acbb90cd214fe4d6138bdf5a740e5a8", + "version" : "9.2.0" + } + }, + { + "identity" : "googleutilities", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/GoogleUtilities.git", + "state" : { + "revision" : "68ea347bdb1a69e2d2ae2e25cd085b6ef92f64cb", + "version" : "7.9.0" + } + }, + { + "identity" : "grpc-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/grpc/grpc-ios.git", + "state" : { + "revision" : "8440b914756e0d26d4f4d054a1c1581daedfc5b6", + "version" : "1.44.3-grpc" + } + }, + { + "identity" : "gtm-session-fetcher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/gtm-session-fetcher.git", + "state" : { + "revision" : "5ccda3981422a84186387dbb763ba739178b529c", + "version" : "2.3.0" + } + }, + { + "identity" : "leveldb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/leveldb.git", + "state" : { + "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", + "version" : "1.22.2" + } + }, + { + "identity" : "nanopb", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/nanopb.git", + "state" : { + "revision" : "819d0a2173aff699fb8c364b6fb906f7cdb1a692", + "version" : "2.30909.0" + } + }, + { + "identity" : "promises", + "kind" : "remoteSourceControl", + "location" : "https://github.com/google/promises.git", + "state" : { + "revision" : "3e4e743631e86c8c70dbc6efdc7beaa6e90fd3bb", + "version" : "2.1.1" + } + }, { "identity" : "swift-case-paths", "kind" : "remoteSourceControl", @@ -72,6 +162,15 @@ "version" : "0.4.0" } }, + { + "identity" : "swift-protobuf", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-protobuf.git", + "state" : { + "revision" : "ab3a58b7209a17d781c0d1dbb3e1ff3da306bae8", + "version" : "1.20.3" + } + }, { "identity" : "xctest-dynamic-overlay", "kind" : "remoteSourceControl", diff --git a/swiftUV/swiftUV/App.swift b/swiftUV/swiftUV/App.swift index 8b011c2..c05c00b 100644 --- a/swiftUV/swiftUV/App.swift +++ b/swiftUV/swiftUV/App.swift @@ -9,7 +9,7 @@ import AppFeature import SwiftUI import ComposableArchitecture -import Bugsnag +import FirebaseCore @main struct SwiftUVApp: App { @@ -28,10 +28,8 @@ struct SwiftUVApp: App { } class AppDelegate: NSObject, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { - #if RELEASE - Bugsnag.start(withApiKey: "b6ee27da8e2f6e0b9641e9c2f2fc6d41") - #endif - return true - } + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { + FirebaseApp.configure() + return true + } } diff --git a/swiftUV/swiftUV/GoogleService-Info.plist b/swiftUV/swiftUV/GoogleService-Info.plist new file mode 100644 index 0000000..b0b8dfa --- /dev/null +++ b/swiftUV/swiftUV/GoogleService-Info.plist @@ -0,0 +1,34 @@ + + + + + CLIENT_ID + 448720498588-eunhsocri7df189h91hksobn80gh71q6.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.448720498588-eunhsocri7df189h91hksobn80gh71q6 + API_KEY + AIzaSyD-_LGTI2mjqWyZR2OAd4gnpSAAh-EU6rQ + GCM_SENDER_ID + 448720498588 + PLIST_VERSION + 1 + BUNDLE_ID + com.zlatan.swiftUV + PROJECT_ID + uv-today-2141b + STORAGE_BUCKET + uv-today-2141b.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:448720498588:ios:22b37a37eb79f29e4ee259 + + \ No newline at end of file From 5b5b8a81d1f64aea1a7befb9f9ca82585be74b2c Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Tue, 8 Nov 2022 21:02:38 +0100 Subject: [PATCH 17/18] chore(develop): update build number to 2.2.0 --- swiftUV/swiftUV.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/swiftUV/swiftUV.xcodeproj/project.pbxproj b/swiftUV/swiftUV.xcodeproj/project.pbxproj index aa753d1..293e808 100644 --- a/swiftUV/swiftUV.xcodeproj/project.pbxproj +++ b/swiftUV/swiftUV.xcodeproj/project.pbxproj @@ -530,7 +530,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.0; + MARKETING_VERSION = 2.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.zlatan.swiftUV; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "ff2f93fb-7946-4ffc-a9c8-b1a55133e36e"; @@ -557,7 +557,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.0; + MARKETING_VERSION = 2.2.0; PRODUCT_BUNDLE_IDENTIFIER = com.zlatan.swiftUV; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "f7c749cb-6205-4c81-895e-2b74e79d6421"; From e16d69bd897039c130c201c90ab231cb7e780207 Mon Sep 17 00:00:00 2001 From: Thomas Guilleminot Date: Tue, 8 Nov 2022 21:28:00 +0100 Subject: [PATCH 18/18] feat(develop): add -objc to linker flag for firebase --- Sources/UVClient/UVClient+Dependency.swift | 20 +++++++++++++++++++ .../{Live.swift => UVClient+Live.swift} | 0 .../{Mock.swift => UVClient+Mock.swift} | 5 +++++ Sources/UVClient/UVClient.swift | 13 ------------ swiftUV/swiftUV.xcodeproj/project.pbxproj | 2 ++ 5 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 Sources/UVClient/UVClient+Dependency.swift rename Sources/UVClient/{Live.swift => UVClient+Live.swift} (100%) rename Sources/UVClient/{Mock.swift => UVClient+Mock.swift} (88%) diff --git a/Sources/UVClient/UVClient+Dependency.swift b/Sources/UVClient/UVClient+Dependency.swift new file mode 100644 index 0000000..1b96444 --- /dev/null +++ b/Sources/UVClient/UVClient+Dependency.swift @@ -0,0 +1,20 @@ +// +// File.swift +// +// +// Created by Thomas Guilleminot on 08/11/2022. +// + +import Foundation +import Dependencies + +enum UVClientKey: DependencyKey { + static let liveValue = UVClient.live +} + +public extension DependencyValues { + var uvClient: UVClient { + get { self[UVClientKey.self] } + set { self[UVClientKey.self] = newValue } + } +} diff --git a/Sources/UVClient/Live.swift b/Sources/UVClient/UVClient+Live.swift similarity index 100% rename from Sources/UVClient/Live.swift rename to Sources/UVClient/UVClient+Live.swift diff --git a/Sources/UVClient/Mock.swift b/Sources/UVClient/UVClient+Mock.swift similarity index 88% rename from Sources/UVClient/Mock.swift rename to Sources/UVClient/UVClient+Mock.swift index 08c82d0..35beebc 100644 --- a/Sources/UVClient/Mock.swift +++ b/Sources/UVClient/UVClient+Mock.swift @@ -29,6 +29,11 @@ extension UVClient { ) } +extension UVClientKey { + static let testValue = UVClient.unimplemented + static let previewValue = UVClient.mock +} + extension UVClient { public static let unimplemented = Self( fetchUVIndex: XCTUnimplemented("\(Self.self).fetchUVIndex)"), diff --git a/Sources/UVClient/UVClient.swift b/Sources/UVClient/UVClient.swift index 7101c41..930f27d 100644 --- a/Sources/UVClient/UVClient.swift +++ b/Sources/UVClient/UVClient.swift @@ -28,16 +28,3 @@ public struct UVClient { let errorDescription: String } } - -private enum UVClientKey: DependencyKey { - static let liveValue = UVClient.live - static var testValue = UVClient.unimplemented - static var previewValue = UVClient.mock -} - -public extension DependencyValues { - var uvClient: UVClient { - get { self[UVClientKey.self] } - set { self[UVClientKey.self] = newValue } - } -} diff --git a/swiftUV/swiftUV.xcodeproj/project.pbxproj b/swiftUV/swiftUV.xcodeproj/project.pbxproj index 293e808..62aebfb 100644 --- a/swiftUV/swiftUV.xcodeproj/project.pbxproj +++ b/swiftUV/swiftUV.xcodeproj/project.pbxproj @@ -531,6 +531,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 2.2.0; + OTHER_LDFLAGS = "-ObjC"; PRODUCT_BUNDLE_IDENTIFIER = com.zlatan.swiftUV; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "ff2f93fb-7946-4ffc-a9c8-b1a55133e36e"; @@ -558,6 +559,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 2.2.0; + OTHER_LDFLAGS = "-ObjC"; PRODUCT_BUNDLE_IDENTIFIER = com.zlatan.swiftUV; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "f7c749cb-6205-4c81-895e-2b74e79d6421";