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/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000..035f01a --- /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: '14.0.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 deleted file mode 100644 index 819e07a..0000000 --- a/.swift-version +++ /dev/null @@ -1 +0,0 @@ -5.0 diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..76bc612 --- /dev/null +++ b/Package.swift @@ -0,0 +1,63 @@ +// swift-tools-version: 5.6 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +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)], + products: [ + .library(name: "AppFeature", targets: ["AppFeature"]), + .library(name: "LocationManager", targets: ["LocationManager"]), + .library(name: "Models", targets: ["Models"]), + .library(name: "UVClient", targets: ["UVClient"]) + ], + dependencies: [ + .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: [ + .target( + name: "AppFeature", + dependencies: [ + "LocationManager", + "Models", + "UVClient", + tca, + tcaCoreLocation + ] + ), + .target( + name: "LocationManager", + dependencies: [ + tcaCoreLocation + ] + ), + .target(name: "Models"), + .target( + name: "UVClient", + dependencies: [ + "Models", + tca + ] + ), + .testTarget( + name: "AppFeatureTests", + dependencies: [ + "AppFeature", + "Models", + "UVClient", + tca + ] + ), + .testTarget( + name: "ModelsTests", + dependencies: [ + "Models" + ] + ) + ] +) diff --git a/Podfile b/Podfile deleted file mode 100644 index 8c25960..0000000 --- a/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'] = '14.0' - end - end -end diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index 47a67a8..0000000 --- a/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: 05532806187603e2d3d823648e92d6ec2366b8e7 - -COCOAPODS: 1.11.3 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/AppFeature/AppReducer.swift b/Sources/AppFeature/AppReducer.swift new file mode 100644 index 0000000..5b86444 --- /dev/null +++ b/Sources/AppFeature/AppReducer.swift @@ -0,0 +1,194 @@ +// +// AppReducer.swift +// swiftUV +// +// Created by Thomas Guilleminot on 03/08/2022. +// Copyright © 2022 Thomas Guilleminot. All rights reserved. +// + +import ComposableArchitecture +import ComposableCoreLocation +import LocationManager +import Models +import UVClient + +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 attributionLogo: URL? + public var attributionLink: URL? + 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 enum Action: Equatable, BindableAction { + case getUVRequest + case getUVResponse(TaskResult) + case getCityNameResponse(TaskResult) + case getAttribution + case getAttributionResponse(TaskResult) + + case onAppear + case onDisappear + case locationManager(LocationManager.Action) + case binding(BindingAction) + } + + @Dependency(\.uvClient) public var uvClient: UVClient + @Dependency(\.locationManager) public var locationManager: LocationManager + + public init() {} + + public var body: some ReducerProtocol { + BindingReducer() + + 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), + + locationManager + .requestWhenInUseAuthorization() + .fireAndForget() + ) + + case .authorizedAlways, .authorizedWhenInUse: + return .merge( + locationManager + .delegate() + .map(AppReducer.Action.locationManager), + + 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 index)): + state.weatherRequestInFlight = false + state.uvIndex = index + 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 .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 + + case .binding: + return .none + + case .locationManager: + return .none + } + } + ._printChanges() + + LocationReducer() + } +} diff --git a/swiftUV/app/Views/ContentVIew.swift b/Sources/AppFeature/ContentVIew.swift similarity index 68% rename from swiftUV/app/Views/ContentVIew.swift rename to Sources/AppFeature/ContentVIew.swift index 15b4b5f..357f747 100644 --- a/swiftUV/app/Views/ContentVIew.swift +++ b/Sources/AppFeature/ContentVIew.swift @@ -10,14 +10,18 @@ import ComposableArchitecture import ComposableCoreLocation import SwiftUI -struct ContentView: View { - let store: Store +public struct ContentView: View { + let store: StoreOf - var body: some View { - WithViewStore(self.store) { viewStore in + public init(store: StoreOf) { + self.store = store + } + + public var body: some View { + WithViewStore(self.store, observe: { $0 }) { 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) @@ -76,14 +80,38 @@ 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(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 viewStore.send(.onAppear) } + .onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in + viewStore.send(.onDisappear) + } + .task { + if #available(iOS 16.0, *) { + viewStore.send(.getAttribution) + } + } } } } @@ -93,18 +121,13 @@ struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView( store: Store( - initialState: AppState( - uvIndex: 1, + initialState: AppReducer.State( + uvIndex: 6, cityName: "Gueugnon", weatherRequestInFlight: false, getCityNameRequestInFlight: false ), - reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: DispatchQueue.test.eraseToAnyScheduler(), - locationManager: .live - ) + reducer: AppReducer() ) ) } diff --git a/Sources/AppFeature/LocationReducer.swift b/Sources/AppFeature/LocationReducer.swift new file mode 100644 index 0000000..baa4ab7 --- /dev/null +++ b/Sources/AppFeature/LocationReducer.swift @@ -0,0 +1,35 @@ +// +// LocationReducer.swift +// swiftUV +// +// Created by Thomas Guilleminot on 31/07/2022. +// Copyright © 2022 Thomas Guilleminot. All rights reserved. +// + +import ComposableArchitecture +import ComposableCoreLocation +import Foundation +import Models + +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 + } + } +} 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/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/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/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 81% rename from swiftUV/app/Models/Index.swift rename to Sources/Models/Index.swift index f74b3ee..c1043e4 100644 --- a/swiftUV/app/Models/Index.swift +++ b/Sources/Models/Index.swift @@ -8,23 +8,23 @@ 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 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 } } - 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/Models/UVError.swift b/Sources/Models/UVError.swift new file mode 100644 index 0000000..17676c1 --- /dev/null +++ b/Sources/Models/UVError.swift @@ -0,0 +1,36 @@ +// +// 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 noAttributionAvailable + 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 .noAttributionAvailable: + return "No attribution available for WeatherKit" + case .customError(let message): + return message + } + } + +} 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/UVClient+Live.swift b/Sources/UVClient/UVClient+Live.swift new file mode 100644 index 0000000..9a87db1 --- /dev/null +++ b/Sources/UVClient/UVClient+Live.swift @@ -0,0 +1,64 @@ +// +// Live.swift +// swiftUV +// +// Created by Thomas Guilleminot on 03/08/2022. +// Copyright © 2022 Thomas Guilleminot. All rights reserved. +// + +import CoreLocation +import Models +import WeatherKit + +extension UVClient { + public static let live = UVClient( + fetchUVIndex: { request in + 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 + 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 { + continuation.resume(throwing: error!) + return + } + + let cityName = placemarks?.first?.locality ?? "Unknown" + 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/UVClient+Mock.swift b/Sources/UVClient/UVClient+Mock.swift new file mode 100644 index 0000000..35beebc --- /dev/null +++ b/Sources/UVClient/UVClient+Mock.swift @@ -0,0 +1,44 @@ +// +// Mock.swift +// swiftUV +// +// Created by Thomas Guilleminot on 03/08/2022. +// Copyright © 2022 Thomas Guilleminot. All rights reserved. +// + +import Foundation +import ComposableArchitecture +import XCTestDynamicOverlay +import Models + +#if DEBUG +extension UVClient { + public static let mock = Self( + fetchUVIndex: { _ in + 5 + }, + fetchCityName: { _ in + "Gueugnon" + }, + fetchWeatherKitAttribution: { + AttributionResponse( + logo: URL(string:"https://www.logo.com")!, + link: URL(string: "https://www.link.com")! + ) + } + ) +} + +extension UVClientKey { + static let testValue = UVClient.unimplemented + static let previewValue = UVClient.mock +} + +extension UVClient { + public static let unimplemented = Self( + fetchUVIndex: XCTUnimplemented("\(Self.self).fetchUVIndex)"), + fetchCityName: XCTUnimplemented("\(Self.self).fetchCityName"), + fetchWeatherKitAttribution: XCTUnimplemented("\(Self.self).fetchWeatherKitAttribution") + ) +} +#endif diff --git a/Sources/UVClient/UVClient.swift b/Sources/UVClient/UVClient.swift new file mode 100644 index 0000000..930f27d --- /dev/null +++ b/Sources/UVClient/UVClient.swift @@ -0,0 +1,30 @@ +// +// 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 -> 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 new file mode 100644 index 0000000..7d0defd --- /dev/null +++ b/Tests/AppFeatureTests/AppReducerTests.swift @@ -0,0 +1,153 @@ +// +// AppReducerTests.swift +// swiftUVTests +// +// Created by Thomas Guilleminot on 04/08/2022. +// Copyright © 2022 Thomas Guilleminot. All rights reserved. +// + +import Foundation +import ComposableArchitecture +import XCTest +import AppFeature +import Models + +@MainActor +class AppReducerTests: XCTestCase { + func testGetUVRequestSuccess() async { + let store = TestStore( + initialState: + AppReducer.State( + getCityNameRequestInFlight: true, + userLocation: Location(latitude: 12.0, longitude: 13.0) + ), + reducer: AppReducer() + ) + + store.dependencies.uvClient.fetchUVIndex = { _ in 5 } + store.dependencies.uvClient.fetchCityName = { _ in "Gueugnon" } + + await store.send(.getUVRequest) { + $0.weatherRequestInFlight = true + $0.getCityNameRequestInFlight = true + } + + await store.receive(.getUVResponse(.success(5))) { + $0.weatherRequestInFlight = false + $0.uvIndex = 5 + } + + await store.receive(.getCityNameResponse(.success("Gueugnon"))) { + $0.getCityNameRequestInFlight = false + $0.cityName = "Gueugnon" + } + } + + func testGetUVRequestFailure() async { + let store = TestStore( + initialState: + AppReducer.State( + getCityNameRequestInFlight: true, + userLocation: Location(latitude: 12.0, longitude: 13.0) + ), + reducer: AppReducer() + ) + + store.dependencies.uvClient.fetchUVIndex = { _ in throw "test" } + store.dependencies.uvClient.fetchCityName = { _ in throw "no city" } + + await store.send(.getUVRequest) { + $0.weatherRequestInFlight = true + $0.getCityNameRequestInFlight = true + } + + 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() { + let store = TestStore( + initialState: + AppReducer.State( + getCityNameRequestInFlight: true + ), + reducer: AppReducer() + ) + + store.send(.getUVRequest) { + $0.weatherRequestInFlight = true + $0.shouldShowErrorPopup = true + $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( + initialState: + AppReducer.State( + shouldShowErrorPopup: true + ), + reducer: AppReducer() + ) + + store.send(.set(\.$shouldShowErrorPopup, true)) { + $0.shouldShowErrorPopup = false + } + } + + func testOnDisappear() { + let store = TestStore( + initialState: AppReducer.State(hasAlreadyRequestLocation: true), + reducer: AppReducer() + ) + + store.send(.onDisappear) { + $0.hasAlreadyRequestLocation = false + } + } +} + +extension String: Error {} +extension String: LocalizedError { + public var errorDescription: String? { self } +} diff --git a/swiftUVTests/IndexTests.swift b/Tests/ModelsTests/IndexTests.swift similarity index 91% rename from swiftUVTests/IndexTests.swift rename to Tests/ModelsTests/IndexTests.swift index fccc0b0..8e79502 100644 --- a/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/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/swiftUV.xcworkspace/contents.xcworkspacedata b/swiftUV.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index ef12187..0000000 --- a/swiftUV.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/swiftUV.xcworkspace/xcshareddata/swiftpm/Package.resolved b/swiftUV.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index ad118c9..0000000 --- a/swiftUV.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,86 +0,0 @@ -{ - "pins" : [ - { - "identity" : "bugsnag-cocoa", - "kind" : "remoteSourceControl", - "location" : "https://github.com/bugsnag/bugsnag-cocoa", - "state" : { - "revision" : "1a5afefae616dbafcab3ad93c8bed0db1c841bc0", - "version" : "6.21.0" - } - }, - { - "identity" : "combine-schedulers", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/combine-schedulers", - "state" : { - "revision" : "0ba3a562716efabb585ccb169450c5389107286b", - "version" : "0.6.0" - } - }, - { - "identity" : "composable-core-location", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/composable-core-location", - "state" : { - "revision" : "95d45d71a4e9c21bd97b02189d2d8342d41ea527", - "version" : "0.2.0" - } - }, - { - "identity" : "swift-case-paths", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-case-paths", - "state" : { - "revision" : "a09839348486db8866f85a727b8550be1d671c50", - "version" : "0.9.1" - } - }, - { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections", - "state" : { - "revision" : "48254824bb4248676bf7ce56014ff57b142b77eb", - "version" : "1.0.2" - } - }, - { - "identity" : "swift-composable-architecture", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-composable-architecture", - "state" : { - "revision" : "0a38f2c860a64a005afeb8a6fe186eb2818c9a3f", - "version" : "0.38.3" - } - }, - { - "identity" : "swift-custom-dump", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-custom-dump", - "state" : { - "revision" : "21ec1d717c07cea5a026979cb0471dd95c7087e7", - "version" : "0.5.0" - } - }, - { - "identity" : "swift-identified-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-identified-collections", - "state" : { - "revision" : "2d6b7ffcc67afd9077fac5e5a29bcd6d39b71076", - "version" : "0.4.0" - } - }, - { - "identity" : "xctest-dynamic-overlay", - "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", - "state" : { - "revision" : "ef8e14e7ce1c0c304c644c6ba365d06c468ded6b", - "version" : "0.3.3" - } - } - ], - "version" : 2 -} 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/App.swift b/swiftUV/App.swift deleted file mode 100644 index bd8db3a..0000000 --- a/swiftUV/App.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// App.swift -// swiftUV -// -// Created by Thomas Guilleminot on 04/08/2022. -// Copyright © 2022 Thomas Guilleminot. All rights reserved. -// - -import SwiftUI -import ComposableArchitecture -import Bugsnag -import Keys - -@main -struct SwiftUVApp: App { - @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate - - var body: some Scene { - WindowGroup { - ContentView( - store: Store( - initialState: AppState(), - reducer: appReducer, - environment: AppEnvironment( - uvClient: .live, - dispatchQueue: .main, - locationManager: .live - ) - ) - ) - } - } -} - -class AppDelegate: NSObject, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { - #if RELEASE - Bugsnag.start(withApiKey: SwiftUVKeys().bugsnagApiKey) - #endif - return true - } -} diff --git a/swiftUV/Colors.xcassets/Contents.json b/swiftUV/Colors.xcassets/Contents.json deleted file mode 100644 index da4a164..0000000 --- a/swiftUV/Colors.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/swiftUV/Colors.xcassets/LightRed.colorset/Contents.json b/swiftUV/Colors.xcassets/LightRed.colorset/Contents.json deleted file mode 100644 index 13e7316..0000000 --- a/swiftUV/Colors.xcassets/LightRed.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - }, - "colors" : [ - { - "idiom" : "universal", - "color" : { - "color-space" : "srgb", - "components" : { - "red" : "231", - "alpha" : "1.000", - "blue" : "47", - "green" : "95" - } - } - }, - { - "idiom" : "universal", - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "red" : "231", - "alpha" : "1.000", - "blue" : "47", - "green" : "95" - } - } - } - ] -} \ No newline at end of file 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/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/swiftUV/app/UVClient/Live.swift b/swiftUV/app/UVClient/Live.swift deleted file mode 100644 index 39d1b9e..0000000 --- a/swiftUV/app/UVClient/Live.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// Live.swift -// swiftUV -// -// Created by Thomas Guilleminot on 03/08/2022. -// Copyright © 2022 Thomas Guilleminot. All rights reserved. -// - -import Foundation -import ComposableArchitecture -import CoreLocation -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() - }, - fetchCityName: { location in - Effect.future { callback 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))) - return - } - - let cityName = placemarks?.first?.locality ?? "Unknown" - callback(.success(cityName)) - } - } - } - ) -} diff --git a/swiftUV/app/UVClient/Mock.swift b/swiftUV/app/UVClient/Mock.swift deleted file mode 100644 index 783c951..0000000 --- a/swiftUV/app/UVClient/Mock.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Mock.swift -// swiftUV -// -// Created by Thomas Guilleminot on 03/08/2022. -// Copyright © 2022 Thomas Guilleminot. All rights reserved. -// - -import Foundation -import ComposableArchitecture - -#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)) - }, - fetchCityName: { _ in - Effect(value: "Gueugnon") - } - ) -} -#endif diff --git a/swiftUV/app/UVClient/UVClient.swift b/swiftUV/app/UVClient/UVClient.swift deleted file mode 100644 index 2e7d4e2..0000000 --- a/swiftUV/app/UVClient/UVClient.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// WeatherClient.swift -// swiftUV -// -// Created by Thomas Guilleminot on 31/07/2022. -// Copyright © 2022 Thomas Guilleminot. All rights reserved. -// - -import ComposableArchitecture - -struct UVClientRequest { - let lat: Double - let long: Double -} - -struct UVClient { - var fetchUVIndex: (UVClientRequest) -> Effect - var fetchCityName: (Location) -> Effect - - struct Failure: Error, Equatable { - let errorDescription: String - } -} diff --git a/swiftUV/app/Views/AppReducer.swift b/swiftUV/app/Views/AppReducer.swift deleted file mode 100644 index c781081..0000000 --- a/swiftUV/app/Views/AppReducer.swift +++ /dev/null @@ -1,144 +0,0 @@ -// -// AppReducer.swift -// swiftUV -// -// Created by Thomas Guilleminot on 03/08/2022. -// Copyright © 2022 Thomas Guilleminot. All rights reserved. -// - -import ComposableArchitecture -import ComposableCoreLocation - -struct AppState: Equatable { - var uvIndex: Index = 0 - var cityName = "loading" - var weatherRequestInFlight = false - var getCityNameRequestInFlight = false - var shouldShowErrorPopup = false - var errorText = "" - - var userLocation: Location? - var isRequestingCurrentLocation = false - var isLocationRefused = false -} - -enum AppAction: Equatable { - case getUVRequest - case getUVResponse(Result) - case getCityNameResponse(Result) - case dismissErrorPopup - - case onAppear - case locationManager(LocationManager.Action) -} - -struct AppEnvironment { - var uvClient: UVClient - var dispatchQueue: AnySchedulerOf - var locationManager: LocationManager -} - -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: - state.isRequestingCurrentLocation = true - - 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 .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 environment.uvClient - .fetchUVIndex(UVClientRequest(lat: location.latitude, long: location.longitude)) - .receive(on: environment.dispatchQueue) - .catchToEffect() - .map { .getUVResponse($0) } - - 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) } - - case .getUVResponse(.failure(let error)): - state.weatherRequestInFlight = false - state.shouldShowErrorPopup = true - state.errorText = error.errorDescription - state.uvIndex = 0 - return .none - - 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 .dismissErrorPopup: - state.shouldShowErrorPopup = false - return .none - - case .locationManager: - return .none - } -} -.combined( - with: - locationManagerReducer - .pullback(state: \.self, action: /AppAction.self, environment: { $0 }) -) diff --git a/swiftUV/app/Views/LocationReducer.swift b/swiftUV/app/Views/LocationReducer.swift deleted file mode 100644 index 31f6ba4..0000000 --- a/swiftUV/app/Views/LocationReducer.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// LocationReducer.swift -// swiftUV -// -// Created by Thomas Guilleminot on 31/07/2022. -// Copyright © 2022 Thomas Guilleminot. All rights reserved. -// - -import ComposableArchitecture -import ComposableCoreLocation -import Foundation - -let locationManagerReducer = Reducer { state, action, environment 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)): - 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) - - default: - return .none - } -} 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 83% rename from fastlane/Fastfile rename to swiftUV/fastlane/Fastfile index edfb7b6..77d80c1 100644 --- a/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 diff --git a/swiftUV.xcodeproj/project.pbxproj b/swiftUV/swiftUV.xcodeproj/project.pbxproj similarity index 66% rename from swiftUV.xcodeproj/project.pbxproj rename to swiftUV/swiftUV.xcodeproj/project.pbxproj index 5726781..62aebfb 100644 --- a/swiftUV.xcodeproj/project.pbxproj +++ b/swiftUV/swiftUV.xcodeproj/project.pbxproj @@ -7,25 +7,15 @@ 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 */; }; - E938D4E7289B0DCD00B8C968 /* Live.swift in Sources */ = {isa = PBXBuildFile; fileRef = E938D4E6289B0DCD00B8C968 /* Live.swift */; }; - E938D4E9289B0E0000B8C968 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = E938D4E8289B0E0000B8C968 /* Mock.swift */; }; - E9A116352322E96C004DC9FB /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E9A116342322E96C004DC9FB /* Colors.xcassets */; }; + 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 */; }; 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 */; }; - E9E29EC02896CD7C000DE660 /* UVClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E29EBF2896CD7C000DE660 /* UVClient.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 */; }; - 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 */; }; @@ -42,24 +32,10 @@ /* 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 = ""; }; - E938D4E6289B0DCD00B8C968 /* Live.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Live.swift; sourceTree = ""; }; - E938D4E8289B0E0000B8C968 /* Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mock.swift; 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 = ""; }; + 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 = ""; }; - 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 = ""; }; + 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 = ""; }; @@ -85,53 +61,25 @@ buildActionMask = 2147483647; files = ( E9E29EC42896E0A7000DE660 /* ComposableCoreLocation in Frameworks */, + E989315128C1541700F973FD /* AppFeature in Frameworks */, E9A22D3D2896C7A3006DC054 /* ComposableArchitecture in Frameworks */, - E9AF349F289C58DD00C0F763 /* Bugsnag in Frameworks */, - 6538403F53A4E29309F2F084 /* Pods_swiftUV.framework in Frameworks */, + E989314D28C14F2C00F973FD /* Models in Frameworks */, + E9CB9A32291AEA71008A44DB /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */, + E9CB9A34291AEA71008A44DB /* FirebaseCrashlytics 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 = ""; - }; - 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 = ( - E931727E2896C5E1003EC842 /* ContentVIew.swift */, - E938D4E0289B0C7000B8C968 /* AppReducer.swift */, - E9E29EC52896E346000DE660 /* LocationReducer.swift */, - ); - name = Views; - sourceTree = ""; - }; EBCAA0421E7C30CE00DC2E9D /* Translation */ = { isa = PBXGroup; children = ( @@ -144,8 +92,6 @@ isa = PBXGroup; children = ( EBDA88971FAE5C89009514AB /* Info.plist */, - EBB0615A21A1CF9A0084E975 /* IndexTests.swift */, - E9AF349B289C4FE600C0F763 /* AppReducerTests.swift */, ); path = swiftUVTests; sourceTree = ""; @@ -153,11 +99,11 @@ EBF4B7431E7951D300B7A616 = { isa = PBXGroup; children = ( + E9720BB828C140E200771EC4 /* uv-today-ios */, EBF4B74E1E7951D400B7A616 /* swiftUV */, EBDA88941FAE5C89009514AB /* swiftUVTests */, EBF4B74D1E7951D400B7A616 /* Products */, - E022E5CBE6E03142E37391DA /* Pods */, - 768820A9F08C906FE75D8B77 /* Frameworks */, + E989314B28C14F2C00F973FD /* Frameworks */, ); sourceTree = ""; }; @@ -173,12 +119,13 @@ EBF4B74E1E7951D400B7A616 /* swiftUV */ = { isa = PBXGroup; children = ( + E91A10ED2901518F00C55E78 /* swiftUV.entitlements */, EBF4B7631E79917000B7A616 /* app */, EBF4B7641E79918100B7A616 /* Resources */, EBF4B7561E7951D400B7A616 /* Assets.xcassets */, - E9A116342322E96C004DC9FB /* Colors.xcassets */, EBF4B7581E7951D400B7A616 /* LaunchScreen.storyboard */, EBF4B75B1E7951D400B7A616 /* Info.plist */, + E9CB9A35291AEAB3008A44DB /* GoogleService-Info.plist */, ); path = swiftUV; sourceTree = ""; @@ -186,9 +133,6 @@ EBF4B7631E79917000B7A616 /* app */ = { isa = PBXGroup; children = ( - E938D4E5289B0D0500B8C968 /* UVClient */, - EBB061462198956B0084E975 /* Views */, - EBF4B77A1E799BC600B7A616 /* Models */, E9AF34A2289C606D00C0F763 /* App.swift */, ); name = app; @@ -202,18 +146,6 @@ name = Resources; sourceTree = ""; }; - EBF4B77A1E799BC600B7A616 /* Models */ = { - isa = PBXGroup; - children = ( - EBB061D121A1DC980084E975 /* Constants.swift */, - EBB061D521A1DC980084E975 /* Forecast.swift */, - EBB061D621A1DC980084E975 /* Index.swift */, - EBB061D421A1DC980084E975 /* Location.swift */, - EBB061D221A1DC980084E975 /* String.swift */, - ); - name = Models; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -231,8 +163,6 @@ EBDA88991FAE5C89009514AB /* PBXTargetDependency */, ); name = swiftUVTests; - packageProductDependencies = ( - ); productName = swiftUVTests; productReference = EBDA88931FAE5C89009514AB /* swiftUVTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -241,11 +171,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 = ( @@ -256,7 +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 */; @@ -298,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 = ""; @@ -323,33 +255,15 @@ buildActionMask = 2147483647; files = ( EBF4B75A1E7951D400B7A616 /* LaunchScreen.storyboard in Resources */, + E9CB9A36291AEAB3008A44DB /* GoogleService-Info.plist in Resources */, EBCAA0451E7C30F000DC2E9D /* Localizable.strings in Resources */, EBF4B7571E7951D400B7A616 /* Assets.xcassets in Resources */, - E9A116352322E96C004DC9FB /* Colors.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* 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; @@ -362,25 +276,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 */ @@ -389,8 +285,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E9AF349C289C4FE600C0F763 /* AppReducerTests.swift in Sources */, - EBB0615B21A1CF9A0084E975 /* IndexTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -398,18 +292,7 @@ isa = PBXSourcesBuildPhase; 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; }; @@ -465,7 +348,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 +384,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 +450,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 +503,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; @@ -631,26 +514,29 @@ }; EBF4B75F1E7951D400B7A616 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4F32DAD95650411C27D7704A /* Pods-swiftUV.debug.xcconfig */; 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 = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.0; + 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"; 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"; @@ -659,23 +545,26 @@ }; EBF4B7601E7951D400B7A616 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 84B83E859B0ADFAB9D8946E1 /* Pods-swiftUV.release.xcconfig */; 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 = 14.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 2.1.0; + 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"; 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; @@ -721,15 +610,15 @@ repositoryURL = "https://github.com/pointfreeco/swift-composable-architecture"; requirement = { kind = exactVersion; - version = 0.38.3; + 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" */ = { @@ -743,15 +632,32 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + E989314C28C14F2C00F973FD /* Models */ = { + isa = XCSwiftPackageProductDependency; + productName = Models; + }; + E989314E28C14F3300F973FD /* UVClient */ = { + isa = XCSwiftPackageProductDependency; + productName = UVClient; + }; + E989315028C1541700F973FD /* AppFeature */ = { + isa = XCSwiftPackageProductDependency; + productName = AppFeature; + }; E9A22D3C2896C7A3006DC054 /* ComposableArchitecture */ = { isa = XCSwiftPackageProductDependency; package = E9A22D3B2896C7A3006DC054 /* XCRemoteSwiftPackageReference "swift-composable-architecture" */; productName = ComposableArchitecture; }; - E9AF349E289C58DD00C0F763 /* Bugsnag */ = { + E9CB9A31291AEA71008A44DB /* FirebaseAnalyticsWithoutAdIdSupport */ = { + isa = XCSwiftPackageProductDependency; + package = E9CB9A30291AEA71008A44DB /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseAnalyticsWithoutAdIdSupport; + }; + E9CB9A33291AEA71008A44DB /* FirebaseCrashlytics */ = { isa = XCSwiftPackageProductDependency; - package = E9AF349D289C58DD00C0F763 /* XCRemoteSwiftPackageReference "bugsnag-cocoa" */; - productName = Bugsnag; + package = E9CB9A30291AEA71008A44DB /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseCrashlytics; }; E9E29EC32896E0A7000DE660 /* ComposableCoreLocation */ = { isa = XCSwiftPackageProductDependency; diff --git a/swiftUV.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 71% rename from swiftUV.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to swiftUV/swiftUV.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 03dd840..919434a 100644 --- a/swiftUV.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/swiftUV.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from swiftUV.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..29bdf3d --- /dev/null +++ b/swiftUV/swiftUV.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,185 @@ +{ + "pins" : [ + { + "identity" : "abseil-cpp-swiftpm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git", + "state" : { + "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" + } + }, + { + "identity" : "combine-schedulers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/combine-schedulers", + "state" : { + "revision" : "aa3e575929f2bcc5bad012bd2575eae716cbcdf7", + "version" : "0.8.0" + } + }, + { + "identity" : "composable-core-location", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/composable-core-location", + "state" : { + "revision" : "95d45d71a4e9c21bd97b02189d2d8342d41ea527", + "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", + "location" : "https://github.com/pointfreeco/swift-case-paths", + "state" : { + "revision" : "a09839348486db8866f85a727b8550be1d671c50", + "version" : "0.9.1" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections", + "state" : { + "revision" : "48254824bb4248676bf7ce56014ff57b142b77eb", + "version" : "1.0.2" + } + }, + { + "identity" : "swift-composable-architecture", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-composable-architecture", + "state" : { + "revision" : "5bd450a8ac6a802f82d485bac219cbfacffa69fb", + "version" : "0.43.0" + } + }, + { + "identity" : "swift-custom-dump", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-custom-dump", + "state" : { + "revision" : "21ec1d717c07cea5a026979cb0471dd95c7087e7", + "version" : "0.5.0" + } + }, + { + "identity" : "swift-identified-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-identified-collections", + "state" : { + "revision" : "2d6b7ffcc67afd9077fac5e5a29bcd6d39b71076", + "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", + "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay", + "state" : { + "revision" : "38bc9242e4388b80bd23ddfdf3071428859e3260", + "version" : "0.4.0" + } + } + ], + "version" : 2 +} diff --git a/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme b/swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme similarity index 82% rename from swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme rename to swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme index fab2342..7aeaad2 100644 --- a/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme +++ b/swiftUV/swiftUV.xcodeproj/xcshareddata/xcschemes/swiftUV.xcscheme @@ -48,6 +48,26 @@ + + + + + + + + Bool { + FirebaseApp.configure() + return true + } +} 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/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 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/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/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 + + + 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 diff --git a/swiftUVTests/AppReducerTests.swift b/swiftUVTests/AppReducerTests.swift deleted file mode 100644 index cd3c607..0000000 --- a/swiftUVTests/AppReducerTests.swift +++ /dev/null @@ -1,238 +0,0 @@ -// -// AppReducerTests.swift -// swiftUVTests -// -// Created by Thomas Guilleminot on 04/08/2022. -// Copyright © 2022 Thomas Guilleminot. All rights reserved. -// - -import Foundation -import ComposableArchitecture -import XCTest - -@testable import swiftUV - -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: - AppState( - getCityNameRequestInFlight: true, - userLocation: Location(latitude: 12.0, longitude: 13.0) - ), - reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: mainQueue.eraseToAnyScheduler(), - locationManager: .failing - ) - ) - - 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" - } - } - - func testGetUVRequestFailure() { - let mainQueue = DispatchQueue.test - let store = TestStore( - initialState: - AppState( - getCityNameRequestInFlight: true, - 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 - ) - ) - - 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 - $0.errorText = "test" - $0.uvIndex = 0 - } - } - - func testGetUVRequestFailureNoLocation() { - let store = TestStore( - initialState: - AppState( - getCityNameRequestInFlight: true - ), - reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: DispatchQueue.main.eraseToAnyScheduler(), - locationManager: .failing - ) - ) - - store.send(.getUVRequest) { - $0.weatherRequestInFlight = true - $0.shouldShowErrorPopup = true - $0.errorText = "app.error.couldNotLocalise".localized - } - } - - 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: - AppState( - getCityNameRequestInFlight: true, - userLocation: Location(latitude: 12.0, longitude: 13.0) - ), - reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: mainQueue.eraseToAnyScheduler(), - locationManager: .failing - ) - ) - - store.send(.getUVResponse(.success(expectedForecast))) { - $0.weatherRequestInFlight = false - $0.uvIndex = 5 - } - - mainQueue.advance() - - 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: AppEnvironment( - uvClient: .mock, - dispatchQueue: DispatchQueue.test.eraseToAnyScheduler(), - locationManager: .failing - ) - ) - - store.send(.getUVResponse(.success(expectedForecast))) { - $0.weatherRequestInFlight = false - $0.uvIndex = 5 - $0.cityName = "app.label.unknown".localized - } - } - - func testGetUVResponseFailure() { - let store = TestStore( - initialState: - AppState( - getCityNameRequestInFlight: true - ), - reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: DispatchQueue.test.eraseToAnyScheduler(), - locationManager: .failing - ) - ) - - 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: - AppState( - getCityNameRequestInFlight: true - ), - reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: DispatchQueue.test.eraseToAnyScheduler(), - locationManager: .failing - ) - ) - - store.send(.getCityNameResponse(.success("Gueugnon"))) { - $0.getCityNameRequestInFlight = false - $0.cityName = "Gueugnon" - } - } - - func testGetCityNameResponseFailure() { - let store = TestStore( - initialState: - AppState( - getCityNameRequestInFlight: true - ), - reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: DispatchQueue.test.eraseToAnyScheduler(), - locationManager: .failing - ) - ) - - store.send(.getCityNameResponse(.failure(UVClient.Failure(errorDescription: "test")))) { - $0.getCityNameRequestInFlight = false - $0.cityName = "app.label.unknown".localized - } - } - - func testDismissErrorPopup() { - let store = TestStore( - initialState: - AppState( - shouldShowErrorPopup: true - ), - reducer: appReducer, - environment: AppEnvironment( - uvClient: .mock, - dispatchQueue: DispatchQueue.test.eraseToAnyScheduler(), - locationManager: .failing - ) - ) - - store.send(.dismissErrorPopup) { - $0.shouldShowErrorPopup = false - } - } -}