diff --git a/BeeKit/ImageDownloadService.swift b/BeeKit/ImageDownloadService.swift new file mode 100644 index 000000000..8544f933a --- /dev/null +++ b/BeeKit/ImageDownloadService.swift @@ -0,0 +1,10 @@ +// Part of BeeSwift. Copyright Beeminder + +import AlamofireImage + +final public class ImageDownloadService { + static public let shared = ImageDownloadService() + public let downloader: ImageDownloader + + private init() { downloader = ImageDownloader.default } +} diff --git a/BeeKit/Managers/CurrentUserManager.swift b/BeeKit/Managers/CurrentUserManager.swift index da0df4487..402702f36 100644 --- a/BeeKit/Managers/CurrentUserManager.swift +++ b/BeeKit/Managers/CurrentUserManager.swift @@ -36,13 +36,13 @@ import SwiftyJSON internal static let keychainPrefix = "CurrentUserManager_" - private let requestManager: RequestManager + private let requestManager: RequestManaging fileprivate static var allKeys: [String] { [accessTokenKey, usernameKey, deadbeatKey, defaultLeadtimeKey, defaultAlertstartKey, defaultDeadlineKey, beemTZKey] } - init(requestManager: RequestManager, container: BeeminderPersistentContainer) { + init(requestManager: RequestManaging, container: BeeminderPersistentContainer) { self.requestManager = requestManager self.modelContainer = container let context = container.newBackgroundContext() diff --git a/BeeKit/Managers/DataPointManager.swift b/BeeKit/Managers/DataPointManager.swift index 1480c4fb1..dd0492379 100644 --- a/BeeKit/Managers/DataPointManager.swift +++ b/BeeKit/Managers/DataPointManager.swift @@ -12,9 +12,9 @@ import SwiftyJSON // prevents effectively no-op updates due to float rounding private let datapointValueEpsilon = 0.00000001 - let requestManager: RequestManager + let requestManager: RequestManaging - init(requestManager: RequestManager, container: BeeminderPersistentContainer) { + init(requestManager: RequestManaging, container: BeeminderPersistentContainer) { self.requestManager = requestManager self.modelContainer = container let context = container.newBackgroundContext() diff --git a/BeeKit/Managers/GoalManager.swift b/BeeKit/Managers/GoalManager.swift index 6d93a1fc4..1a7d0e007 100644 --- a/BeeKit/Managers/GoalManager.swift +++ b/BeeKit/Managers/GoalManager.swift @@ -16,12 +16,12 @@ import SwiftyJSON @NSModelActor(disableGenerateInit: true) public actor GoalManager { private let logger = Logger(subsystem: "com.beeminder.beeminder", category: "GoalManager") - private let requestManager: RequestManager + private let requestManager: RequestManaging private nonisolated let currentUserManager: CurrentUserManager private var queuedGoalsBackgroundTaskRunning: Bool = false - init(requestManager: RequestManager, currentUserManager: CurrentUserManager, container: BeeminderPersistentContainer) + init(requestManager: RequestManaging, currentUserManager: CurrentUserManager, container: BeeminderPersistentContainer) { modelContainer = container let context = container.newBackgroundContext() diff --git a/BeeKit/Managers/RequestManager.swift b/BeeKit/Managers/RequestManager.swift index 8113b6114..094de030c 100644 --- a/BeeKit/Managers/RequestManager.swift +++ b/BeeKit/Managers/RequestManager.swift @@ -11,36 +11,12 @@ import Foundation import OSLog import SwiftyJSON -public enum ServerError: LocalizedError { - case notFound - case unauthorized - case forbidden - case serverError(Int) - case custom(String, requestError: Error?) - public var errorDescription: String? { - switch self { - case .notFound: return "Not found" - case .unauthorized: return "Unauthorized" - case .forbidden: return "Permission denied" - case .serverError(let code): return "Server error (\(code)). Please try again later" - case .custom(let message, _): return message - } - } - var requestError: Error? { - switch self { - case .custom(_, let error): return error - default: return nil - } - } -} - -public class RequestManager { +public actor RequestManager { public let baseURLString = Config().baseURLString private let logger = Logger(subsystem: "com.beeminder.beeminder", category: "RequestManager") func rawRequest(url: String, method: HTTPMethod, parameters: [String: Any]? = nil, headers: HTTPHeaders) async throws -> Any? { - var urlWithSubstitutions = url if url.contains("{username}") { guard let username = await ServiceLocator.currentUserManager.username else { @@ -51,8 +27,7 @@ public class RequestManager { } urlWithSubstitutions = urlWithSubstitutions.replacingOccurrences(of: "{username}", with: username) } - - let encoding: ParameterEncoding = if method == .get { URLEncoding.default } else { JSONEncoding.default } // TODO + let encoding: ParameterEncoding = method == .get ? URLEncoding.default : JSONEncoding.default let response = await AF.request( "\(baseURLString)/\(urlWithSubstitutions)", method: method, @@ -60,27 +35,21 @@ public class RequestManager { encoding: encoding, headers: HTTPHeaders.default + headers ).validate().serializingData(emptyRequestMethods: [HTTPMethod.post]).response - switch response.result { case .success(let data): - let asJSON = try? JSONSerialization.jsonObject(with: data) - return asJSON - + return try await Task.detached(priority: .low) { try JSONSerialization.jsonObject(with: data) }.value case .failure(let error): logger.error("Error issuing request \(url): \(error, privacy: .public)") - // Log out the user on an unauthorized response if case .responseValidationFailed(let reason) = error { if case .unacceptableStatusCode(let code) = reason { if code == 401 { try? await ServiceLocator.currentUserManager.signOut() } } } - // If we receive an error message from the server use it as our user-visible error if let data = response.data, let errorMessage = try JSON(data: data)["error_message"].string { throw ServerError.custom(errorMessage, requestError: error) } - // Handle common HTTP errors with specific error types if case .responseValidationFailed(let reason) = error { if case .unacceptableStatusCode(let code) = reason { @@ -93,10 +62,16 @@ public class RequestManager { } } } - throw error } } + func authenticationHeaders() -> HTTPHeaders { + guard let accessToken = ServiceLocator.currentUserManager.accessToken else { return HTTPHeaders() } + return HTTPHeaders([HTTPHeader(name: "Authorization", value: "Bearer " + accessToken)]) + } +} + +extension RequestManager: RequestManaging { public func get(url: String, parameters: [String: Any]? = nil) async throws -> Any? { return try await rawRequest(url: url, method: .get, parameters: parameters, headers: authenticationHeaders()) } @@ -109,11 +84,6 @@ public class RequestManager { public func delete(url: String, parameters: [String: Any]? = nil) async throws -> Any? { return try await rawRequest(url: url, method: .delete, parameters: parameters, headers: authenticationHeaders()) } - func authenticationHeaders() -> HTTPHeaders { - guard let accessToken = ServiceLocator.currentUserManager.accessToken else { return HTTPHeaders() } - return HTTPHeaders([HTTPHeader(name: "Authorization", value: "Bearer " + accessToken)]) - } - public func addDatapoint(urtext: String, slug: String, requestId: String? = nil) async throws -> Any? { let params = ["urtext": urtext, "requestid": requestId].compactMapValues { $0 } return try await post(url: "api/v1/users/{username}/goals/\(slug)/datapoints.json", parameters: params) @@ -128,3 +98,33 @@ extension HTTPHeaders { return HTTPHeaders(allHeaders) } } + +extension RequestManager: SignedRequestManaging { + public func signedGET(url: String, parameters: [String: Any]?) async throws -> Any? { + let params = signedParameters(parameters) + return try await rawRequest(url: url, method: .get, parameters: params, headers: authenticationHeaders()) + } + public func signedPOST(url: String, parameters: [String: Any]?) async throws -> Any? { + let params = signedParameters(parameters) + return try await rawRequest(url: url, method: .post, parameters: params, headers: authenticationHeaders()) + } + fileprivate func signedParameters(_ params: [String: Any]?) -> [String: Any]? { + if params == nil { return params } + var signed = params + var base = "" + var keys = Array(params!.keys) + keys.sort(by: { $0 < $1 }) + for key in keys { + let value: AnyObject? = params![key] as AnyObject? + if !(value is String) { return params! } + let allowedCharacterSet = (CharacterSet(charactersIn: "@/").inverted) + let escapedKey = key.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) + let escapedValue = (params![key] as AnyObject).addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) + if base.count > 0 { base += "&" } + base += "\(escapedKey!)=\(escapedValue!)" + } + let token = base.hmac(algorithm: HMACAlgorithm.SHA1, key: Config().requestSigningKey) + signed?["beemios_token"] = token + return signed! as [String: Any] + } +} diff --git a/BeeKit/Managers/RequestManaging.swift b/BeeKit/Managers/RequestManaging.swift new file mode 100644 index 000000000..abefc8519 --- /dev/null +++ b/BeeKit/Managers/RequestManaging.swift @@ -0,0 +1,27 @@ +// Part of BeeSwift. Copyright Beeminder + +public protocol RequestManaging { + func get(url: String, parameters: [String: Any]?) async throws -> Any? + func put(url: String, parameters: [String: Any]?) async throws -> Any? + func post(url: String, parameters: [String: Any]?) async throws -> Any? + func delete(url: String, parameters: [String: Any]?) async throws -> Any? + func addDatapoint(urtext: String, slug: String, requestId: String?) async throws -> Any? +} + +extension RequestManaging { + public func get(url: String, parameters: [String: Any]? = nil) async throws -> Any? { + try await get(url: url, parameters: parameters) + } + public func put(url: String, parameters: [String: Any]? = nil) async throws -> Any? { + try await put(url: url, parameters: parameters) + } + public func post(url: String, parameters: [String: Any]? = nil) async throws -> Any? { + try await post(url: url, parameters: parameters) + } + public func delete(url: String, parameters: [String: Any]? = nil) async throws -> Any? { + try await delete(url: url, parameters: parameters) + } + public func addDatapoint(urtext: String, slug: String, requestId: String? = nil) async throws -> Any? { + try await addDatapoint(urtext: urtext, slug: slug, requestId: requestId) + } +} diff --git a/BeeKit/Managers/ServerError.swift b/BeeKit/Managers/ServerError.swift new file mode 100644 index 000000000..e22390e7d --- /dev/null +++ b/BeeKit/Managers/ServerError.swift @@ -0,0 +1,26 @@ +// Part of BeeSwift. Copyright Beeminder + +import Foundation + +public enum ServerError: LocalizedError { + case notFound + case unauthorized + case forbidden + case serverError(Int) + case custom(String, requestError: Error?) + public var errorDescription: String? { + switch self { + case .notFound: return "Not found" + case .unauthorized: return "Unauthorized" + case .forbidden: return "Permission denied" + case .serverError(let code): return "Server error (\(code)). Please try again later" + case .custom(let message, _): return message + } + } + var requestError: Error? { + switch self { + case .custom(_, let error): return error + default: return nil + } + } +} diff --git a/BeeKit/Managers/SignedRequestManager.swift b/BeeKit/Managers/SignedRequestManager.swift deleted file mode 100644 index 3cbac6ce5..000000000 --- a/BeeKit/Managers/SignedRequestManager.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// SignedRequestManager.swift -// BeeSwift -// -// Created by Andy Brett on 11/30/17. -// Copyright 2017 APB. All rights reserved. -// - -import Alamofire -import Foundation - -public class SignedRequestManager { - private let requestManager: RequestManager - - init(requestManager: RequestManager) { self.requestManager = requestManager } - - public func signedGET(url: String, parameters: [String: Any]?) async throws -> Any? { - let params = signedParameters(parameters) - return try await requestManager.rawRequest( - url: url, - method: .get, - parameters: params, - headers: requestManager.authenticationHeaders() - ) - } - public func signedPOST(url: String, parameters: [String: Any]?) async throws -> Any? { - let params = signedParameters(parameters) - return try await requestManager.rawRequest( - url: url, - method: .post, - parameters: params, - headers: requestManager.authenticationHeaders() - ) - } - fileprivate func signedParameters(_ params: [String: Any]?) -> [String: Any]? { - if params == nil { return params } - var signed = params - var base = "" - var keys = Array(params!.keys) - keys.sort(by: { $0 < $1 }) - for key in keys { - let value: AnyObject? = params![key] as AnyObject? - if !(value is String) { return params! } - let allowedCharacterSet = (CharacterSet(charactersIn: "@/").inverted) - let escapedKey = key.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) - let escapedValue = (params![key] as AnyObject).addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) - if base.count > 0 { base += "&" } - base += "\(escapedKey!)=\(escapedValue!)" - } - let token = base.hmac(algorithm: HMACAlgorithm.SHA1, key: Config().requestSigningKey) - signed?["beemios_token"] = token - return signed! as [String: Any] - } -} diff --git a/BeeKit/Managers/SignedRequestManaging.swift b/BeeKit/Managers/SignedRequestManaging.swift new file mode 100644 index 000000000..be1ab1519 --- /dev/null +++ b/BeeKit/Managers/SignedRequestManaging.swift @@ -0,0 +1,15 @@ +// Part of BeeSwift. Copyright Beeminder + +public protocol SignedRequestManaging { + func signedGET(url: String, parameters: [String: Any]?) async throws -> Any? + func signedPOST(url: String, parameters: [String: Any]?) async throws -> Any? +} + +extension SignedRequestManaging { + public func signedGET(url: String, parameters: [String: Any]? = nil) async throws -> Any? { + try await signedGET(url: url, parameters: parameters) + } + public func signedPOST(url: String, parameters: [String: Any]? = nil) async throws -> Any? { + try await signedPOST(url: url, parameters: parameters) + } +} diff --git a/BeeKit/Managers/VersionManager.swift b/BeeKit/Managers/VersionManager.swift index 9a48371f8..0bb59cce0 100644 --- a/BeeKit/Managers/VersionManager.swift +++ b/BeeKit/Managers/VersionManager.swift @@ -23,9 +23,9 @@ private let ageOfReleaseToWarn: TimeInterval = 10.0 * dayInSeconds public class VersionManager { private var minRequiredVersion: String = "1.0" private var updateState = UpdateState.UpToDate - private let requestManager: RequestManager + private let requestManager: RequestManaging - init(requestManager: RequestManager) { self.requestManager = requestManager } + init(requestManager: RequestManaging) { self.requestManager = requestManager } public func lastChckedUpdateState() -> UpdateState { return updateState } diff --git a/BeeKit/ServiceLocator.swift b/BeeKit/ServiceLocator.swift index c1c6dcdf3..9da69cbab 100644 --- a/BeeKit/ServiceLocator.swift +++ b/BeeKit/ServiceLocator.swift @@ -6,16 +6,20 @@ // Copyright 2023 APB. All rights reserved. // +import AlamofireImage import Foundation import OSLog public class ServiceLocator { private static let logger = Logger(subsystem: "com.beeminder.beeminder", category: "ServiceLocator") + private static let sharedRequestManager = RequestManager() + + public static let requestManager: RequestManaging = sharedRequestManager + public static let signedRequestManager: SignedRequestManaging = sharedRequestManager + public static let persistentContainer = BeeminderPersistentContainer.create() - public static let requestManager = RequestManager() - public static let signedRequestManager = SignedRequestManager(requestManager: requestManager) public static let currentUserManager = CurrentUserManager( requestManager: requestManager, container: persistentContainer @@ -33,4 +37,6 @@ public class ServiceLocator { goalManager: goalManager, container: persistentContainer ) + + public static let imageDownloader = ImageDownloadService.shared.downloader } diff --git a/BeeKitTests/DataPointManagerTests.swift b/BeeKitTests/DataPointManagerTests.swift index 06bf76a88..b03b0474e 100644 --- a/BeeKitTests/DataPointManagerTests.swift +++ b/BeeKitTests/DataPointManagerTests.swift @@ -17,7 +17,8 @@ class MockHealthKitDataPoint: BeeDataPoint { } } -class MockRequestManagerForDataPoint: RequestManager { +class MockRequestManagerForDataPoint: RequestManaging { + func post(url: String, parameters: [String: Any]?) async throws -> Any? { nil } private let queue = DispatchQueue(label: "com.beeminder.MockRequestManagerForDataPoint") private var _responses: [String: Any] = [:] private var _putCalls: [(url: String, parameters: [String: Any])] = [] @@ -32,7 +33,7 @@ class MockRequestManagerForDataPoint: RequestManager { var deleteCalls: [String] { queue.sync { _deleteCalls } } var addDatapointCalls: [(urtext: String, slug: String, requestId: String)] { queue.sync { _addDatapointCalls } } - override func get(url: String, parameters: [String: Any]? = nil) async throws -> Any? { + func get(url: String, parameters: [String: Any]? = nil) async throws -> Any? { let response = queue.sync { () -> Any? in if let response = _responses[url] { _responses.removeValue(forKey: url) @@ -42,15 +43,15 @@ class MockRequestManagerForDataPoint: RequestManager { } return response ?? [] } - override func put(url: String, parameters: [String: Any]? = nil) async throws -> Any? { + func put(url: String, parameters: [String: Any]? = nil) async throws -> Any? { queue.sync { _putCalls.append((url: url, parameters: parameters ?? [:])) } return [:] } - override func delete(url: String, parameters: [String: Any]? = nil) async throws -> Any? { + func delete(url: String, parameters: [String: Any]? = nil) async throws -> Any? { queue.sync { _deleteCalls.append(url) } return [:] } - override func addDatapoint(urtext: String, slug: String, requestId: String? = nil) async throws -> Any? { + func addDatapoint(urtext: String, slug: String, requestId: String? = nil) async throws -> Any? { queue.sync { _addDatapointCalls.append((urtext: urtext, slug: slug, requestId: requestId ?? "")) } return [:] } diff --git a/BeeKitTests/GoalManagerTests.swift b/BeeKitTests/GoalManagerTests.swift index 361d19951..beaff3e69 100644 --- a/BeeKitTests/GoalManagerTests.swift +++ b/BeeKitTests/GoalManagerTests.swift @@ -5,9 +5,13 @@ import XCTest @testable import BeeKit -class MockRequestManager: RequestManager { +class MockRequestManager: RequestManaging { + func addDatapoint(urtext: String, slug: String, requestId: String?) async throws -> Any? { nil } + func delete(url: String, parameters: [String: Any]?) async throws -> Any? { nil } + func post(url: String, parameters: [String: Any]?) async throws -> Any? { nil } + func put(url: String, parameters: [String: Any]?) async throws -> Any? { nil } var responses: [String: Any] = [:] - override func get(url: String, parameters: [String: Any]? = nil) async throws -> Any? { + func get(url: String, parameters: [String: Any]? = nil) async throws -> Any? { if let response = responses[url] { return response } XCTFail("Unexpected URL requested: \(url)") return nil diff --git a/BeeSwift.xcodeproj/project.pbxproj b/BeeSwift.xcodeproj/project.pbxproj index 0a38105de..68e8f822d 100644 --- a/BeeSwift.xcodeproj/project.pbxproj +++ b/BeeSwift.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 9BA7F9052F40723300CF84BE /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = 9BA7F9042F40723300CF84BE /* AlamofireImage */; }; A12BA94E1AFF202200AFEF32 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A12BA94D1AFF202200AFEF32 /* SystemConfiguration.framework */; }; A158DDB61E46EEA10031BD4F /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A158DDB51E46EEA10031BD4F /* HealthKit.framework */; }; A17E930C1B09032F0098FCA0 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A17E930B1B09032F0098FCA0 /* QuartzCore.framework */; }; @@ -208,6 +209,7 @@ E458C8292AD12057000DCA5C /* OrderedCollections in Frameworks */, E4015DA22D1E7B2D00F58D94 /* CoreDataEvolution in Frameworks */, E458C8222AD11D40000DCA5C /* Alamofire in Frameworks */, + 9BA7F9052F40723300CF84BE /* AlamofireImage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -369,6 +371,7 @@ E458C8232AD11D7F000DCA5C /* KeychainSwift */, E458C8282AD12057000DCA5C /* OrderedCollections */, E4015DA12D1E7B2D00F58D94 /* CoreDataEvolution */, + 9BA7F9042F40723300CF84BE /* AlamofireImage */, ); productName = BeeKit; productReference = E57BE6E02655EBD900BA540B /* BeeKit.framework */; @@ -1155,6 +1158,11 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 9BA7F9042F40723300CF84BE /* AlamofireImage */ = { + isa = XCSwiftPackageProductDependency; + package = E462BA3729AC450000E80EF0 /* XCRemoteSwiftPackageReference "AlamofireImage" */; + productName = AlamofireImage; + }; E4015DA12D1E7B2D00F58D94 /* CoreDataEvolution */ = { isa = XCSwiftPackageProductDependency; package = E4015DA02D1E7B2D00F58D94 /* XCRemoteSwiftPackageReference "CoreDataEvolution" */; diff --git a/BeeSwift/Components/GoalImageView.swift b/BeeSwift/Components/GoalImageView.swift index 30106b4a5..3d06adbc9 100644 --- a/BeeSwift/Components/GoalImageView.swift +++ b/BeeSwift/Components/GoalImageView.swift @@ -7,7 +7,7 @@ import OSLog /// Shows the current graph for a goal /// Handles placeholders for loading and queued states, and automatically updates when the goal changes class GoalImageView: UIView { - private static let downloader = ImageDownloader(imageCache: AutoPurgingImageCache()) + private static let downloader = ServiceLocator.imageDownloader private let logger = Logger(subsystem: "com.beeminder.beeminder", category: "GoalImageView") private let imageView = UIImageView() diff --git a/BeeSwift/Gallery/GalleryViewController.swift b/BeeSwift/Gallery/GalleryViewController.swift index 030a318b9..5ff1f0b5e 100644 --- a/BeeSwift/Gallery/GalleryViewController.swift +++ b/BeeSwift/Gallery/GalleryViewController.swift @@ -6,6 +6,7 @@ // Copyright 2015 APB. All rights reserved. // +import AlamofireImage import BeeKit import CoreData import HealthKit @@ -29,7 +30,7 @@ class GalleryViewController: UIViewController { private let versionManager: VersionManager private let goalManager: GoalManager private let healthStoreManager: HealthStoreManager - private let requestManager: RequestManager + private let requestManager: RequestManaging private lazy var stackView: UIStackView = { let stackView = UIStackView() stackView.axis = .vertical @@ -106,7 +107,7 @@ class GalleryViewController: UIViewController { versionManager: VersionManager, goalManager: GoalManager, healthStoreManager: HealthStoreManager, - requestManager: RequestManager, + requestManager: RequestManaging, coordinator: MainCoordinator ) { self.currentUserManager = currentUserManager @@ -502,6 +503,17 @@ extension GalleryViewController: UICollectionViewDelegate { } } +extension GalleryViewController: UICollectionViewDataSourcePrefetching { + func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { + let urls = indexPaths.compactMap { indexPath -> URL? in + let goal = fetchedResultsController.object(at: indexPath) + return try? goal.thumbUrl.asURL() + } + let downloader = ServiceLocator.imageDownloader + urls.forEach { downloader.download(URLRequest(url: $0), completion: { _ in }) } + } +} + extension GalleryViewController { static private var preferredSort: [NSSortDescriptor] { let selectedGoalSort = diff --git a/BeeSwift/GoalView/EditDatapointViewController.swift b/BeeSwift/GoalView/EditDatapointViewController.swift index a9a36e3c5..12faabf9a 100644 --- a/BeeSwift/GoalView/EditDatapointViewController.swift +++ b/BeeSwift/GoalView/EditDatapointViewController.swift @@ -21,10 +21,10 @@ class EditDatapointViewController: UIViewController, UITextFieldDelegate { fileprivate var datePicker = InlineDatePicker() fileprivate var valueField = UITextField() fileprivate var commentField = UITextField() - private let requestManager: RequestManager + private let requestManager: RequestManaging private let goalManager: GoalManager - init(goal: Goal, datapoint: DataPoint, requestManager: RequestManager, goalManager: GoalManager) { + init(goal: Goal, datapoint: DataPoint, requestManager: RequestManaging, goalManager: GoalManager) { self.goal = goal self.datapoint = datapoint self.requestManager = requestManager diff --git a/BeeSwift/GoalView/GoalSettingsViewController.swift b/BeeSwift/GoalView/GoalSettingsViewController.swift index e8acf3666..864158808 100644 --- a/BeeSwift/GoalView/GoalSettingsViewController.swift +++ b/BeeSwift/GoalView/GoalSettingsViewController.swift @@ -11,7 +11,7 @@ class GoalSettingsViewController: UIViewController { fileprivate let cellReuseIdentifier = "goalSettingsTableViewCell" let goal: Goal private let currentUserManager: CurrentUserManager - private let requestManager: RequestManager + private let requestManager: RequestManaging private let goalManager: GoalManager private weak var coordinator: MainCoordinator? @@ -27,7 +27,7 @@ class GoalSettingsViewController: UIViewController { init( goal: Goal, currentUserManager: CurrentUserManager, - requestManager: RequestManager, + requestManager: RequestManaging, goalManager: GoalManager, coordinator: MainCoordinator ) { diff --git a/BeeSwift/GoalView/GoalViewController.swift b/BeeSwift/GoalView/GoalViewController.swift index b722b29b6..331bffbe4 100644 --- a/BeeSwift/GoalView/GoalViewController.swift +++ b/BeeSwift/GoalView/GoalViewController.swift @@ -28,7 +28,7 @@ class GoalViewController: UIViewController, UIScrollViewDelegate, DatapointTable let goal: Goal private let healthStoreManager: HealthStoreManager private let goalManager: GoalManager - private let requestManager: RequestManager + private let requestManager: RequestManaging private let currentUserManager: CurrentUserManager private let viewContext: NSManagedObjectContext private weak var coordinator: MainCoordinator? @@ -61,7 +61,7 @@ class GoalViewController: UIViewController, UIScrollViewDelegate, DatapointTable goal: Goal, healthStoreManager: HealthStoreManager, goalManager: GoalManager, - requestManager: RequestManager, + requestManager: RequestManaging, currentUserManager: CurrentUserManager, viewContext: NSManagedObjectContext, coordinator: MainCoordinator diff --git a/BeeSwift/GoalView/TimerViewController.swift b/BeeSwift/GoalView/TimerViewController.swift index d0718a2f7..6ebe3e300 100644 --- a/BeeSwift/GoalView/TimerViewController.swift +++ b/BeeSwift/GoalView/TimerViewController.swift @@ -20,10 +20,10 @@ class TimerViewController: UIViewController { var timingSince: Date? var timer: Timer? private let units: TimerUnit - private let requestManager: RequestManager + private let requestManager: RequestManaging var accumulatedSeconds = 0 - init(goal: Goal, requestManager: RequestManager) { + init(goal: Goal, requestManager: RequestManaging) { self.goal = goal self.requestManager = requestManager self.units = Self.timerUnit(goal: goal) ?? .hours diff --git a/BeeSwift/MainCoordinator.swift b/BeeSwift/MainCoordinator.swift index 39ee7e9f7..403372c7f 100644 --- a/BeeSwift/MainCoordinator.swift +++ b/BeeSwift/MainCoordinator.swift @@ -9,7 +9,7 @@ class MainCoordinator { private let versionManager: VersionManager private let goalManager: GoalManager private let healthStoreManager: HealthStoreManager - private let requestManager: RequestManager + private let requestManager: RequestManaging init( navigationController: UINavigationController, currentUserManager: CurrentUserManager, @@ -17,7 +17,7 @@ class MainCoordinator { versionManager: VersionManager, goalManager: GoalManager, healthStoreManager: HealthStoreManager, - requestManager: RequestManager + requestManager: RequestManaging ) { self.navigationController = navigationController self.currentUserManager = currentUserManager diff --git a/BeeSwift/Settings/ChooseHKMetricViewController.swift b/BeeSwift/Settings/ChooseHKMetricViewController.swift index 3cafd9aaf..578291672 100644 --- a/BeeSwift/Settings/ChooseHKMetricViewController.swift +++ b/BeeSwift/Settings/ChooseHKMetricViewController.swift @@ -17,10 +17,14 @@ class ChooseHKMetricViewController: UIViewController { fileprivate var tableView = UITableView() let goal: Goal private let healthStoreManager: HealthStoreManager - private let requestManager: RequestManager + private let requestManager: RequestManaging private weak var coordinator: MainCoordinator? - init(goal: Goal, healthStoreManager: HealthStoreManager, requestManager: RequestManager, coordinator: MainCoordinator) - { + init( + goal: Goal, + healthStoreManager: HealthStoreManager, + requestManager: RequestManaging, + coordinator: MainCoordinator + ) { self.goal = goal self.healthStoreManager = healthStoreManager self.requestManager = requestManager diff --git a/BeeSwift/Settings/ConfigureHKMetricViewController.swift b/BeeSwift/Settings/ConfigureHKMetricViewController.swift index bad4a2658..e615ff517 100644 --- a/BeeSwift/Settings/ConfigureHKMetricViewController.swift +++ b/BeeSwift/Settings/ConfigureHKMetricViewController.swift @@ -25,7 +25,7 @@ class ConfigureHKMetricViewController: UIViewController { private let goal: Goal private let metric: HealthKitMetric private let healthStoreManager: HealthStoreManager - private let requestManager: RequestManager + private let requestManager: RequestManaging private let goalManager: GoalManager? private var isRequestInFlight = false @@ -47,7 +47,7 @@ class ConfigureHKMetricViewController: UIViewController { goal: Goal, metric: HealthKitMetric, healthStoreManager: HealthStoreManager, - requestManager: RequestManager, + requestManager: RequestManaging, goalManager: GoalManager? = nil ) { self.goal = goal diff --git a/BeeSwift/Settings/ConfigureNotificationsViewController.swift b/BeeSwift/Settings/ConfigureNotificationsViewController.swift index 84275e354..d8e6403e0 100644 --- a/BeeSwift/Settings/ConfigureNotificationsViewController.swift +++ b/BeeSwift/Settings/ConfigureNotificationsViewController.swift @@ -20,7 +20,7 @@ class ConfigureNotificationsViewController: UIViewController { private let goalManager: GoalManager private let viewContext: NSManagedObjectContext private let currentUserManager: CurrentUserManager - private let requestManager: RequestManager + private let requestManager: RequestManaging private weak var coordinator: MainCoordinator? private lazy var dataSource: NotificationsTableViewDiffibleDataSource = { NotificationsTableViewDiffibleDataSource(goals: [], tableView: tableView) @@ -29,7 +29,7 @@ class ConfigureNotificationsViewController: UIViewController { goalManager: GoalManager, viewContext: NSManagedObjectContext, currentUserManager: CurrentUserManager, - requestManager: RequestManager, + requestManager: RequestManaging, coordinator: MainCoordinator ) { self.goalManager = goalManager diff --git a/BeeSwift/Settings/EditDefaultNotificationsViewController.swift b/BeeSwift/Settings/EditDefaultNotificationsViewController.swift index fe08370a2..f883ea6f6 100644 --- a/BeeSwift/Settings/EditDefaultNotificationsViewController.swift +++ b/BeeSwift/Settings/EditDefaultNotificationsViewController.swift @@ -15,12 +15,12 @@ class EditDefaultNotificationsViewController: EditNotificationsViewController { private let logger = Logger(subsystem: "com.beeminder.beeminder", category: "EditDefaultNotificationsViewController") private let user: User private let currentUserManager: CurrentUserManager - private let requestManager: RequestManager + private let requestManager: RequestManaging private let goalManager: GoalManager private let viewContext: NSManagedObjectContext init( currentUserManager: CurrentUserManager, - requestManager: RequestManager, + requestManager: RequestManaging, goalManager: GoalManager, viewContext: NSManagedObjectContext ) { diff --git a/BeeSwift/Settings/EditGoalNotificationsViewController.swift b/BeeSwift/Settings/EditGoalNotificationsViewController.swift index b42105af4..c7ba73177 100644 --- a/BeeSwift/Settings/EditGoalNotificationsViewController.swift +++ b/BeeSwift/Settings/EditGoalNotificationsViewController.swift @@ -19,13 +19,13 @@ class EditGoalNotificationsViewController: EditNotificationsViewController { let goal: Goal fileprivate var useDefaultsSwitch = UISwitch() private let currentUserManager: CurrentUserManager - private let requestManager: RequestManager + private let requestManager: RequestManaging private let goalManager: GoalManager private let viewContext: NSManagedObjectContext init( goal: Goal, currentUserManager: CurrentUserManager, - requestManager: RequestManager, + requestManager: RequestManaging, goalManager: GoalManager, viewContext: NSManagedObjectContext ) { diff --git a/BeeSwift/Settings/HealthKitConfigViewController.swift b/BeeSwift/Settings/HealthKitConfigViewController.swift index dec31531b..b9409ce53 100644 --- a/BeeSwift/Settings/HealthKitConfigViewController.swift +++ b/BeeSwift/Settings/HealthKitConfigViewController.swift @@ -23,13 +23,13 @@ class HealthKitConfigViewController: UIViewController { private let goalManager: GoalManager private let viewContext: NSManagedObjectContext private let healthStoreManager: HealthStoreManager - private let requestManager: RequestManager + private let requestManager: RequestManaging private weak var coordinator: MainCoordinator? init( goalManager: GoalManager, viewContext: NSManagedObjectContext, healthStoreManager: HealthStoreManager, - requestManager: RequestManager, + requestManager: RequestManaging, coordinator: MainCoordinator ) { self.goalManager = goalManager diff --git a/BeeSwift/Settings/SettingsViewController.swift b/BeeSwift/Settings/SettingsViewController.swift index 422aa7263..b66118f6f 100644 --- a/BeeSwift/Settings/SettingsViewController.swift +++ b/BeeSwift/Settings/SettingsViewController.swift @@ -18,13 +18,13 @@ class SettingsViewController: UIViewController { private let currentUserManager: CurrentUserManager private let viewContext: NSManagedObjectContext private let goalManager: GoalManager - private let requestManager: RequestManager + private let requestManager: RequestManaging private weak var coordinator: MainCoordinator? init( currentUserManager: CurrentUserManager, viewContext: NSManagedObjectContext, goalManager: GoalManager, - requestManager: RequestManager, + requestManager: RequestManaging, coordinator: MainCoordinator ) { self.currentUserManager = currentUserManager