From ac896dda52fbbcd5fffe208f0098db624db96a0c Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 4 Sep 2018 09:35:03 +0200 Subject: [PATCH 1/5] Delegate launchgate --- Source/DialogManager.swift | 147 +++++++++++++------------ Source/LaunchGate.swift | 189 ++++++++++++++++++--------------- Source/RemoteFileManager.swift | 3 +- StringHandler.swift | 25 +++++ 4 files changed, 206 insertions(+), 158 deletions(-) create mode 100644 StringHandler.swift diff --git a/Source/DialogManager.swift b/Source/DialogManager.swift index ea68953..6e2be84 100755 --- a/Source/DialogManager.swift +++ b/Source/DialogManager.swift @@ -8,100 +8,111 @@ import Foundation +// Localization of strings + protocol Dialogable { - var message: String { get } + var message: String { get } } class DialogManager { - typealias RememberableDialogSubject = Dialogable & Rememberable + typealias RememberableDialogSubject = Dialogable & Rememberable - enum DialogType { - case alert(blocking: Bool) - case optionalUpdate(updateURL: URL) - case requiredUpdate(updateURL: URL) - } + enum DialogType { + case alert(blocking: Bool) + case optionalUpdate(updateURL: URL) + case requiredUpdate(updateURL: URL) + } - func displayAlertDialog(_ alertConfig: RememberableDialogSubject, blocking: Bool) { - let dialog = createAlertController(.alert(blocking: blocking), message: alertConfig.message) + var stringHandler: StringHandler? - displayAlertController(dialog) { () -> Void in - if !blocking { - Memory.remember(alertConfig) - } + init(withStringHandler stringHandler: StringHandler? = nil) { + self.stringHandler = stringHandler } - } - func displayRequiredUpdateDialog(_ updateConfig: Dialogable, updateURL: URL) { - let dialog = createAlertController(.requiredUpdate(updateURL: updateURL), message: updateConfig.message) + func displayAlertDialog(_ alertConfig: RememberableDialogSubject, blocking: Bool) { + let dialog = createAlertController(.alert(blocking: blocking), + message: stringHandler?.alertMessage ?? alertConfig.message) - displayAlertController(dialog, completion: nil) - } + displayAlertController(dialog) { () -> Void in + if !blocking { + Memory.remember(alertConfig) + } + } + } - func displayOptionalUpdateDialog(_ updateConfig: RememberableDialogSubject, updateURL: URL) { - let dialog = createAlertController(.optionalUpdate(updateURL: updateURL), message: updateConfig.message) + func displayRequiredUpdateDialog(_ updateConfig: Dialogable, updateURL: URL) { + let dialog = createAlertController(.requiredUpdate(updateURL: updateURL), + message: stringHandler?.requiredUpdateMessage ?? updateConfig.message) - displayAlertController(dialog) { () -> Void in - Memory.remember(updateConfig) + displayAlertController(dialog, completion: nil) } - } - // MARK: Custom Alert Controllers + func displayOptionalUpdateDialog(_ updateConfig: RememberableDialogSubject, updateURL: URL) { + let dialog = createAlertController(.optionalUpdate(updateURL: updateURL), + message: stringHandler?.optionalUpdateMessage ?? updateConfig.message) - func createAlertController(_ type: DialogType, message: String) -> UIAlertController { - let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert) + displayAlertController(dialog) { () -> Void in + Memory.remember(updateConfig) + } + } + + // MARK: Custom Alert Controllers + + func createAlertController(_ type: DialogType, message: String) -> UIAlertController { + let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert) + + switch type { + case let .alert(blocking): + if !blocking { + alertController.addAction(dismissActon()) + } - switch type { - case let .alert(blocking): - if !blocking { + case let .optionalUpdate(updateURL): alertController.addAction(dismissActon()) + alertController.addAction(updateAction(updateURL)) + + case let .requiredUpdate(updateURL): + alertController.addAction(updateAction(updateURL)) } - case let .optionalUpdate(updateURL): - alertController.addAction(dismissActon()) - alertController.addAction(updateAction(updateURL)) + return alertController + } - case let .requiredUpdate(updateURL): - alertController.addAction(updateAction(updateURL)) + func displayAlertController(_ alert: UIAlertController, completion: (() -> Void)?) { + DispatchQueue.main.async { [] in + if let topViewController = self.topViewController() { + topViewController.present(alert, animated: true) { + if let completion = completion { + completion() + } + } + } + } } - return alertController - } + func topViewController() -> UIViewController? { + return UIApplication.shared.keyWindow?.rootViewController + } - func displayAlertController(_ alert: UIAlertController, completion: (() -> Void)?) { - DispatchQueue.main.async { [] in - if let topViewController = self.topViewController() { - topViewController.present(alert, animated: true) { - if let completion = completion { - completion() - } - } - } + // MARK: Custom Alert Actions + + private func dismissActon() -> UIAlertAction { + return UIAlertAction( + title: NSLocalizedString(stringHandler?.dismissTitle ?? "Dismiss", comment: "Button title for dismissing the update AlertView"), + style: .default) { _ in } } - } - - func topViewController() -> UIViewController? { - return UIApplication.shared.keyWindow?.rootViewController - } - - // MARK: Custom Alert Actions - - private func dismissActon() -> UIAlertAction { - return UIAlertAction( - title: NSLocalizedString("Dismiss", comment: "Button title for dismissing the update AlertView"), - style: .default) { _ in } - } - - private func updateAction(_ updateURL: URL) -> UIAlertAction { - return UIAlertAction( - title: NSLocalizedString("Update", comment: "Button title for accepting the update AlertView"), - style: .default) { (_) -> Void in - if UIApplication.shared.canOpenURL(updateURL) { - DispatchQueue.main.async { [] in - UIApplication.shared.openURL(updateURL) + + private func updateAction(_ updateURL: URL) -> UIAlertAction { + return UIAlertAction( + title: NSLocalizedString(stringHandler?.downloadTitle ?? "Update", comment: "Button title for accepting the update AlertView"), + style: .default) { (_) -> Void in + if UIApplication.shared.canOpenURL(updateURL) { + DispatchQueue.main.async { [] in + UIApplication.shared.openURL(updateURL) + } } - } } - } + } } diff --git a/Source/LaunchGate.swift b/Source/LaunchGate.swift index e2337a6..2d8b42d 100755 --- a/Source/LaunchGate.swift +++ b/Source/LaunchGate.swift @@ -8,127 +8,140 @@ import Foundation +/// Protocol for LaunchGate +public protocol LaunchGateDelegate: class { + func noUpdateHandler() +} + /// Custom internal error type typealias LaunchGateError = Error & CustomStringConvertible public class LaunchGate { - /// Parser to use when parsing the configuration file - public var parser: LaunchGateParser! + /// Parser to use when parsing the configuration file + public var parser: LaunchGateParser! + + /// URI for the configuration file + var configurationFileURL: URL! - /// URI for the configuration file - var configurationFileURL: URL! + /// App Store URI ("itms-apps://itunes.apple.com/...") for the current app + var updateURL: URL! - /// App Store URI ("itms-apps://itunes.apple.com/...") for the current app - var updateURL: URL! + /// Manager object for the various alert dialogs + var dialogManager: DialogManager! - /// Manager object for the various alert dialogs - var dialogManager: DialogManager! + // MARK: - Public API - // MARK: - Public API + weak var delegate: LaunchGateDelegate? - /** - Failable initializer. If either the `configURI` or `appStoreURI` are unable to be - converted into an `URL` (i.e. containing illegal URL characters) this initializer - will return `nil`. + /** + Failable initializer. If either the `configURI` or `appStoreURI` are unable to be + converted into an `URL` (i.e. containing illegal URL characters) this initializer + will return `nil`. - - Parameters: - - configURI: URI for the configuration file - - appStoreURI: App Store URI ("itms-apps://itunes.apple.com/...") for the current app + - Parameters: + - configURI: URI for the configuration file + - appStoreURI: App Store URI ("itms-apps://itunes.apple.com/...") for the current app - - Returns: A `LaunchGate` instance or `nil` - */ - public init?(configURI: String, appStoreURI: String) { - guard let configURL = URL(string: configURI) else { return nil } - guard let appStoreURL = URL(string: appStoreURI) else { return nil } + - Returns: A `LaunchGate` instance or `nil` + */ + public init?(configURI: String, appStoreURI: String, stringHandler: StringHandler? = nil) { + guard let configURL = URL(string: configURI) else { return nil } + guard let appStoreURL = URL(string: appStoreURI) else { return nil } - configurationFileURL = configURL - updateURL = appStoreURL - parser = DefaultParser() - dialogManager = DialogManager() - } + configurationFileURL = configURL + updateURL = appStoreURL + parser = DefaultParser() + dialogManager = DialogManager(withStringHandler: stringHandler) + } - /// Check the configuration file and perform any appropriate action. - public func check() { - performCheck(RemoteFileManager(remoteFileURL: (configurationFileURL as URL))) - } + public func setDelegate(_ delegate: LaunchGateDelegate?) { + self.delegate = delegate + } - // MARK: - Internal API + /// Check the configuration file and perform any appropriate action. + public func check() { + performCheck(RemoteFileManager(remoteFileURL: (configurationFileURL as URL))) + } - /** - Check the configuration file and perform any appropriate action, using - the provided `RemoteFileManager`. + // MARK: - Internal API - - Parameter remoteFileManager: The `RemoteFileManager` to use to fetch the configuration file. - */ - func performCheck(_ remoteFileManager: RemoteFileManager) { - remoteFileManager.fetchRemoteFile { (jsonData) -> Void in - if let config = self.parser.parse(jsonData) { - self.displayDialogIfNecessary(config, dialogManager: self.dialogManager) - } + /** + Check the configuration file and perform any appropriate action, using + the provided `RemoteFileManager`. + + - Parameter remoteFileManager: The `RemoteFileManager` to use to fetch the configuration file. + */ + func performCheck(_ remoteFileManager: RemoteFileManager) { + remoteFileManager.fetchRemoteFile { (jsonData) -> Void in + if let config = self.parser.parse(jsonData) { + self.displayDialogIfNecessary(config, dialogManager: self.dialogManager) + } + } } - } - /** - Determine which dialog, if any, to display based on the parsed configuration. + /** + Determine which dialog, if any, to display based on the parsed configuration. - - Parameters: + - Parameters: - config: Configuration parsed from remote configuration file. - dialogManager: Manager object for the various alert dialogs - */ - func displayDialogIfNecessary(_ config: LaunchGateConfiguration, dialogManager: DialogManager) { - if let reqUpdate = config.requiredUpdate, let appVersion = currentAppVersion() { - if shouldShowRequiredUpdateDialog(reqUpdate, appVersion: appVersion) { - dialogManager.displayRequiredUpdateDialog(reqUpdate, updateURL: updateURL) - } - } else if let optUpdate = config.optionalUpdate, let appVersion = currentAppVersion() { - if shouldShowOptionalUpdateDialog(optUpdate, appVersion: appVersion) { - dialogManager.displayOptionalUpdateDialog(optUpdate, updateURL: updateURL) - } - } else if let alert = config.alert { - if shouldShowAlertDialog(alert) { - dialogManager.displayAlertDialog(alert, blocking: alert.blocking) - } + */ + func displayDialogIfNecessary(_ config: LaunchGateConfiguration, dialogManager: DialogManager) { + if let reqUpdate = config.requiredUpdate, let appVersion = currentAppVersion() { + if shouldShowRequiredUpdateDialog(reqUpdate, appVersion: appVersion) { + dialogManager.displayRequiredUpdateDialog(reqUpdate, updateURL: updateURL) + } + } else if let optUpdate = config.optionalUpdate, let appVersion = currentAppVersion() { + if shouldShowOptionalUpdateDialog(optUpdate, appVersion: appVersion) { + dialogManager.displayOptionalUpdateDialog(optUpdate, updateURL: updateURL) + } + } else if let alert = config.alert { + if shouldShowAlertDialog(alert) { + dialogManager.displayAlertDialog(alert, blocking: alert.blocking) + } + } else { + delegate?.noUpdateHandler() + } } - } - /** - Determine if an alert dialog should be displayed, based on the configuration. + /** + Determine if an alert dialog should be displayed, based on the configuration. - - Parameter alertConfig: An `AlertConfiguration`, parsed from the configuration file. + - Parameter alertConfig: An `AlertConfiguration`, parsed from the configuration file. - - Returns: `true`, if an alert dialog should be displayed; `false`, if not. - */ - func shouldShowAlertDialog(_ alertConfig: AlertConfiguration) -> Bool { - return alertConfig.blocking || alertConfig.isNotRemembered() - } + - Returns: `true`, if an alert dialog should be displayed; `false`, if not. + */ + func shouldShowAlertDialog(_ alertConfig: AlertConfiguration) -> Bool { + return alertConfig.blocking || alertConfig.isNotRemembered() + } - /** - Determine if an optional update dialog should be displayed, based on the configuration. + /** + Determine if an optional update dialog should be displayed, based on the configuration. - - Parameter updateConfig: An `UpdateConfiguration`, parsed from the configuration file. + - Parameter updateConfig: An `UpdateConfiguration`, parsed from the configuration file. - - Returns: `true`, if an optional update should be displayed; `false`, if not. - */ - func shouldShowOptionalUpdateDialog(_ updateConfig: UpdateConfiguration, appVersion: String) -> Bool { - guard updateConfig.isNotRemembered() else { return false } + - Returns: `true`, if an optional update should be displayed; `false`, if not. + */ + func shouldShowOptionalUpdateDialog(_ updateConfig: UpdateConfiguration, appVersion: String) -> Bool { + guard updateConfig.isNotRemembered() else { return false } - return appVersion < updateConfig.version - } + return appVersion < updateConfig.version + } - /** - Determine if a required update dialog should be displayed, based on the configuration. + /** + Determine if a required update dialog should be displayed, based on the configuration. - - Parameter updateConfig: An `UpdateConfiguration`, parsed from the configuration file. + - Parameter updateConfig: An `UpdateConfiguration`, parsed from the configuration file. - - Returns: `true`, if a required update dialog should be displayed; `false`, if not. - */ - func shouldShowRequiredUpdateDialog(_ updateConfig: UpdateConfiguration, appVersion: String) -> Bool { - return appVersion < updateConfig.version - } + - Returns: `true`, if a required update dialog should be displayed; `false`, if not. + */ + func shouldShowRequiredUpdateDialog(_ updateConfig: UpdateConfiguration, appVersion: String) -> Bool { + return appVersion < updateConfig.version + } - func currentAppVersion() -> String? { - return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String - } + func currentAppVersion() -> String? { + return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + } } diff --git a/Source/RemoteFileManager.swift b/Source/RemoteFileManager.swift index 0e4f797..0dc3215 100755 --- a/Source/RemoteFileManager.swift +++ b/Source/RemoteFileManager.swift @@ -13,6 +13,7 @@ class RemoteFileManager { let remoteFileURL: URL init(remoteFileURL: URL) { + print("✡️ [RemoteFileManager] remoteFileURL : \(remoteFileURL) ") self.remoteFileURL = remoteFileURL } @@ -33,10 +34,8 @@ class RemoteFileManager { print("LaunchGate — Error: Remote configuration file response was empty.") return } - responseHandler(data) } - task.resume() } diff --git a/StringHandler.swift b/StringHandler.swift new file mode 100644 index 0000000..a5caf94 --- /dev/null +++ b/StringHandler.swift @@ -0,0 +1,25 @@ +// +// StringHandler.swift +// LaunchGate +// +// Created by Nicolas CHARVOZ on 03/09/2018. +// + +import Foundation + +open class StringHandler { + var requiredUpdateMessage: String? + var optionalUpdateMessage: String? + var alertMessage: String? + var dismissTitle: String? + var downloadTitle: String? + + public init(requiredUpdateMessage: String?, optionalUpdateMessage: String?, + alertMessage: String?, dismissTitle: String?, downloadTitle: String?) { + self.requiredUpdateMessage = requiredUpdateMessage + self.optionalUpdateMessage = optionalUpdateMessage + self.alertMessage = alertMessage + self.dismissTitle = dismissTitle + self.downloadTitle = downloadTitle + } +} From e3bb16f7d5d2b1f6ea8bdbf36266fc5bfa81b579 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 4 Sep 2018 09:45:28 +0200 Subject: [PATCH 2/5] Source added --- Source/StringHandler.swift | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Source/StringHandler.swift diff --git a/Source/StringHandler.swift b/Source/StringHandler.swift new file mode 100644 index 0000000..a5caf94 --- /dev/null +++ b/Source/StringHandler.swift @@ -0,0 +1,25 @@ +// +// StringHandler.swift +// LaunchGate +// +// Created by Nicolas CHARVOZ on 03/09/2018. +// + +import Foundation + +open class StringHandler { + var requiredUpdateMessage: String? + var optionalUpdateMessage: String? + var alertMessage: String? + var dismissTitle: String? + var downloadTitle: String? + + public init(requiredUpdateMessage: String?, optionalUpdateMessage: String?, + alertMessage: String?, dismissTitle: String?, downloadTitle: String?) { + self.requiredUpdateMessage = requiredUpdateMessage + self.optionalUpdateMessage = optionalUpdateMessage + self.alertMessage = alertMessage + self.dismissTitle = dismissTitle + self.downloadTitle = downloadTitle + } +} From 73aff77af56d820750d1480ac782ec90f5d0ea20 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 4 Sep 2018 10:19:37 +0200 Subject: [PATCH 3/5] podscpec update --- LaunchGate.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LaunchGate.podspec b/LaunchGate.podspec index 657ddfd..28fff05 100755 --- a/LaunchGate.podspec +++ b/LaunchGate.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LaunchGate" - s.version = "1.1.1" + s.version = "1.1.2" s.summary = <<-SUMMARY LaunchGate makes it easy to let users know when an update to your app is available. SUMMARY From 84183c51267c2b7f6358e7c09fd49a94dfdf8f5f Mon Sep 17 00:00:00 2001 From: Nicolas Date: Tue, 4 Sep 2018 13:50:39 +0200 Subject: [PATCH 4/5] Delegate for DialogManager --- LaunchGate.podspec | 2 +- Source/DefaultParser.swift | 138 +++++++++++++++++---------------- Source/DialogManager.swift | 12 ++- Source/LaunchGate.swift | 25 +++++- Source/RemoteFileManager.swift | 64 ++++++++------- 5 files changed, 139 insertions(+), 102 deletions(-) diff --git a/LaunchGate.podspec b/LaunchGate.podspec index 28fff05..84f9ee7 100755 --- a/LaunchGate.podspec +++ b/LaunchGate.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LaunchGate" - s.version = "1.1.2" + s.version = "1.1.3" s.summary = <<-SUMMARY LaunchGate makes it easy to let users know when an update to your app is available. SUMMARY diff --git a/Source/DefaultParser.swift b/Source/DefaultParser.swift index 610071b..90a8ceb 100755 --- a/Source/DefaultParser.swift +++ b/Source/DefaultParser.swift @@ -10,83 +10,85 @@ import Foundation class DefaultParser: LaunchGateParser { - typealias JSON = [String: AnyObject] - - enum Error: LaunchGateError { - case unableToParseConfigurationObject - case unableToParseAlert - case unableToParseOptionalUpdate - case unableToParseRequiredUpdate - - var description: String { - switch self { - case .unableToParseConfigurationObject: - return "Unable to parse the configuration object (\"ios\") from JSON file." - case .unableToParseAlert: - return "Unable to parse the alert configuration from JSON file." - case .unableToParseOptionalUpdate: - return "Unable to parse the optional update configuration from JSON file." - case .unableToParseRequiredUpdate: - return "Unable to parse the required update configuration from JSON file." + typealias JSON = [String: AnyObject] + + enum Error: LaunchGateError { + case unableToParseConfigurationObject + case unableToParseAlert + case unableToParseOptionalUpdate + case unableToParseRequiredUpdate + + var description: String { + switch self { + case .unableToParseConfigurationObject: + return "Unable to parse the configuration object (\"ios\") from JSON file." + case .unableToParseAlert: + return "Unable to parse the alert configuration from JSON file." + case .unableToParseOptionalUpdate: + return "Unable to parse the optional update configuration from JSON file." + case .unableToParseRequiredUpdate: + return "Unable to parse the required update configuration from JSON file." + } } } - } - func parse(_ jsonData: Data) -> LaunchGateConfiguration? { - do { - let jsonData = try JSONSerialization.jsonObject(with: jsonData, options: []) - guard let json = jsonData as? JSON else { throw Error.unableToParseConfigurationObject } - guard let config = json["ios"] else { throw Error.unableToParseConfigurationObject } - - var alert: AlertConfiguration? - var optionalUpdate: UpdateConfiguration? - var requiredUpdate: UpdateConfiguration? - - if let alertJSON = config["alert"] as? JSON { - alert = try DefaultParser.parseAlert(alertJSON) - } - - if let optionalUpdateJSON = config["optionalUpdate"] as? JSON { - optionalUpdate = try DefaultParser.parseOptionalUpdate(optionalUpdateJSON) - } - - if let requiredUpdateJSON = config["requiredUpdate"] as? JSON { - requiredUpdate = try DefaultParser.parseRequiredUpdate(requiredUpdateJSON) - } - - return LaunchGateConfiguration(alert: alert, optionalUpdate: optionalUpdate, requiredUpdate: requiredUpdate) - } catch let error as DefaultParser.Error { - print("LaunchGate — Error: \(error)") - } catch let error as NSError { - print("LaunchGate — Error: \(error.localizedDescription)") - - if let recoverySuggestion = error.localizedRecoverySuggestion { - print(recoverySuggestion) - } - } + func parse(_ jsonData: Data) -> LaunchGateConfiguration? { + do { + let jsonData = try JSONSerialization.jsonObject(with: jsonData, options: []) + guard let json = jsonData as? JSON else { throw Error.unableToParseConfigurationObject } + guard let config = json["ios"] else { throw Error.unableToParseConfigurationObject } + + var alert: AlertConfiguration? + var optionalUpdate: UpdateConfiguration? + var requiredUpdate: UpdateConfiguration? + + if let alertJSON = config["alert"] as? JSON { + alert = try DefaultParser.parseAlert(alertJSON) + } + + if let optionalUpdateJSON = config["optionalUpdate"] as? JSON { + optionalUpdate = try DefaultParser.parseOptionalUpdate(optionalUpdateJSON) + } + + if let requiredUpdateJSON = config["requiredUpdate"] as? JSON { + requiredUpdate = try DefaultParser.parseRequiredUpdate(requiredUpdateJSON) + } + + return LaunchGateConfiguration(alert: alert, optionalUpdate: optionalUpdate, requiredUpdate: requiredUpdate) + } catch let error as DefaultParser.Error { + print("LaunchGate — Error: \(error)") + return nil + } catch let error as NSError { + print("LaunchGate — Error: \(error.localizedDescription)") + + if let recoverySuggestion = error.localizedRecoverySuggestion { + print(recoverySuggestion) + } + return nil + } - return nil - } + return nil + } - private static func parseAlert(_ json: JSON) throws -> AlertConfiguration? { - guard let message = json["message"] as? String else { throw Error.unableToParseAlert } - guard let blocking = json["blocking"] as? Bool else { throw Error.unableToParseAlert } + private static func parseAlert(_ json: JSON) throws -> AlertConfiguration? { + guard let message = json["message"] as? String else { throw Error.unableToParseAlert } + guard let blocking = json["blocking"] as? Bool else { throw Error.unableToParseAlert } - return AlertConfiguration(message: message, blocking: blocking) - } + return AlertConfiguration(message: message, blocking: blocking) + } - private static func parseOptionalUpdate(_ json: JSON) throws -> UpdateConfiguration? { - guard let version = json["optionalVersion"] as? String else { throw Error.unableToParseOptionalUpdate } - guard let message = json["message"] as? String else { throw Error.unableToParseOptionalUpdate } + private static func parseOptionalUpdate(_ json: JSON) throws -> UpdateConfiguration? { + guard let version = json["optionalVersion"] as? String else { throw Error.unableToParseOptionalUpdate } + guard let message = json["message"] as? String else { throw Error.unableToParseOptionalUpdate } - return UpdateConfiguration(version: version, message: message) - } + return UpdateConfiguration(version: version, message: message) + } - private static func parseRequiredUpdate(_ json: JSON) throws -> UpdateConfiguration? { - guard let version = json["minimumVersion"] as? String else { throw Error.unableToParseRequiredUpdate } - guard let message = json["message"] as? String else { throw Error.unableToParseRequiredUpdate } + private static func parseRequiredUpdate(_ json: JSON) throws -> UpdateConfiguration? { + guard let version = json["minimumVersion"] as? String else { throw Error.unableToParseRequiredUpdate } + guard let message = json["message"] as? String else { throw Error.unableToParseRequiredUpdate } - return UpdateConfiguration(version: version, message: message) - } + return UpdateConfiguration(version: version, message: message) + } } diff --git a/Source/DialogManager.swift b/Source/DialogManager.swift index 6e2be84..4dd841f 100755 --- a/Source/DialogManager.swift +++ b/Source/DialogManager.swift @@ -8,6 +8,10 @@ import Foundation +public protocol DialogManagerDelegate: class { + func didDismissAlertView() +} + // Localization of strings protocol Dialogable { @@ -25,9 +29,11 @@ class DialogManager { } var stringHandler: StringHandler? + weak var delegate: DialogManagerDelegate? - init(withStringHandler stringHandler: StringHandler? = nil) { + init(withStringHandler stringHandler: StringHandler? = nil, andDelegate delegate: DialogManagerDelegate? = nil) { self.stringHandler = stringHandler + self.delegate = delegate } func displayAlertDialog(_ alertConfig: RememberableDialogSubject, blocking: Bool) { @@ -100,7 +106,9 @@ class DialogManager { private func dismissActon() -> UIAlertAction { return UIAlertAction( title: NSLocalizedString(stringHandler?.dismissTitle ?? "Dismiss", comment: "Button title for dismissing the update AlertView"), - style: .default) { _ in } + style: .default) { _ in + self.delegate?.didDismissAlertView() + } } private func updateAction(_ updateURL: URL) -> UIAlertAction { diff --git a/Source/LaunchGate.swift b/Source/LaunchGate.swift index 2d8b42d..bb1b221 100755 --- a/Source/LaunchGate.swift +++ b/Source/LaunchGate.swift @@ -45,14 +45,14 @@ public class LaunchGate { - Returns: A `LaunchGate` instance or `nil` */ - public init?(configURI: String, appStoreURI: String, stringHandler: StringHandler? = nil) { + public init?(configURI: String, appStoreURI: String, stringHandler: StringHandler? = nil, dialogDelegate: DialogManagerDelegate? = nil) { guard let configURL = URL(string: configURI) else { return nil } guard let appStoreURL = URL(string: appStoreURI) else { return nil } configurationFileURL = configURL updateURL = appStoreURL parser = DefaultParser() - dialogManager = DialogManager(withStringHandler: stringHandler) + dialogManager = DialogManager(withStringHandler: stringHandler, andDelegate: dialogDelegate) } public func setDelegate(_ delegate: LaunchGateDelegate?) { @@ -61,7 +61,7 @@ public class LaunchGate { /// Check the configuration file and perform any appropriate action. public func check() { - performCheck(RemoteFileManager(remoteFileURL: (configurationFileURL as URL))) + performCheck(RemoteFileManager(remoteFileURL: (configurationFileURL as URL), andDelegate: self)) } // MARK: - Internal API @@ -75,7 +75,11 @@ public class LaunchGate { func performCheck(_ remoteFileManager: RemoteFileManager) { remoteFileManager.fetchRemoteFile { (jsonData) -> Void in if let config = self.parser.parse(jsonData) { + print("⚪️ [LaunchGate] Config available for remote file : \(config)") self.displayDialogIfNecessary(config, dialogManager: self.dialogManager) + } else { + print("⚪️ [LaunchGate] No config available for remote file") + self.delegate?.noUpdateHandler() } } } @@ -91,17 +95,23 @@ public class LaunchGate { if let reqUpdate = config.requiredUpdate, let appVersion = currentAppVersion() { if shouldShowRequiredUpdateDialog(reqUpdate, appVersion: appVersion) { dialogManager.displayRequiredUpdateDialog(reqUpdate, updateURL: updateURL) + } else { + delegate?.noUpdateHandler() } } else if let optUpdate = config.optionalUpdate, let appVersion = currentAppVersion() { if shouldShowOptionalUpdateDialog(optUpdate, appVersion: appVersion) { dialogManager.displayOptionalUpdateDialog(optUpdate, updateURL: updateURL) + } else { + self.delegate?.noUpdateHandler() } } else if let alert = config.alert { if shouldShowAlertDialog(alert) { dialogManager.displayAlertDialog(alert, blocking: alert.blocking) + } else { + self.delegate?.noUpdateHandler() } } else { - delegate?.noUpdateHandler() + self.delegate?.noUpdateHandler() } } @@ -145,3 +155,10 @@ public class LaunchGate { } } + +extension LaunchGate: RemoteFileManagerDelegate { + func errorWithRemoteFileHandler() { + print("🔴 errorWithRemoteFileHandler()") + delegate?.noUpdateHandler() + } +} diff --git a/Source/RemoteFileManager.swift b/Source/RemoteFileManager.swift index 0dc3215..5b6b7d4 100755 --- a/Source/RemoteFileManager.swift +++ b/Source/RemoteFileManager.swift @@ -8,35 +8,45 @@ import Foundation +protocol RemoteFileManagerDelegate: class { + func errorWithRemoteFileHandler() +} + class RemoteFileManager { - let remoteFileURL: URL - - init(remoteFileURL: URL) { - print("✡️ [RemoteFileManager] remoteFileURL : \(remoteFileURL) ") - self.remoteFileURL = remoteFileURL - } - - func fetchRemoteFile(_ callback: @escaping (Data) -> Void) { - performRemoteFileRequest(URLSession.shared, url: remoteFileURL, responseHandler: callback) - } - - func performRemoteFileRequest(_ session: URLSession, url: URL, responseHandler: @escaping (_ data: Data) -> Void) { - let task = session.dataTask(with: url) { data, response, error in - if let error = error { - print("LaunchGate — Error: \(error.localizedDescription)") - } - guard response != nil else { - print("LaunchGate - Error because there is no response") - return - } - guard let data = data else { - print("LaunchGate — Error: Remote configuration file response was empty.") - return - } - responseHandler(data) + let remoteFileURL: URL + + weak var delegate: RemoteFileManagerDelegate? + + init(remoteFileURL: URL, andDelegate delegate: RemoteFileManagerDelegate? = nil) { + print("✡️ [RemoteFileManager] remoteFileURL : \(remoteFileURL) ") + self.remoteFileURL = remoteFileURL + self.delegate = delegate + } + + func fetchRemoteFile(_ callback: @escaping (Data) -> Void) { + performRemoteFileRequest(URLSession.shared, url: remoteFileURL, responseHandler: callback) + } + + func performRemoteFileRequest(_ session: URLSession, url: URL, responseHandler: @escaping (_ data: Data) -> Void) { + let task = session.dataTask(with: url) { data, response, error in + if let error = error { + print("🔽 LaunchGate — Error: \(error.localizedDescription)") + self.delegate?.errorWithRemoteFileHandler() + } + guard response != nil else { + print("🔽 LaunchGate - Error because there is no response") + self.delegate?.errorWithRemoteFileHandler() + return + } + guard let data = data else { + print("🔽 LaunchGate — Error: Remote configuration file response was empty.") + self.delegate?.errorWithRemoteFileHandler() + return + } + responseHandler(data) + } + task.resume() } - task.resume() - } } From d9bd89e806476acf2b0d8609840be41417af99ad Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 5 Sep 2018 16:32:10 +0200 Subject: [PATCH 5/5] Renamed StringHandler >> WordingHandler || line rules > 120 fixed --- LaunchGate.podspec | 2 +- Source/DialogManager.swift | 7 ++++--- Source/LaunchGate.swift | 21 ++++++++++++------- ...ringHandler.swift => WordingHandler.swift} | 11 ++++++---- 4 files changed, 26 insertions(+), 15 deletions(-) rename Source/{StringHandler.swift => WordingHandler.swift} (65%) diff --git a/LaunchGate.podspec b/LaunchGate.podspec index 84f9ee7..40829bb 100755 --- a/LaunchGate.podspec +++ b/LaunchGate.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LaunchGate" - s.version = "1.1.3" + s.version = "1.1.4" s.summary = <<-SUMMARY LaunchGate makes it easy to let users know when an update to your app is available. SUMMARY diff --git a/Source/DialogManager.swift b/Source/DialogManager.swift index 4dd841f..ae88b86 100755 --- a/Source/DialogManager.swift +++ b/Source/DialogManager.swift @@ -28,11 +28,12 @@ class DialogManager { case requiredUpdate(updateURL: URL) } - var stringHandler: StringHandler? + var stringHandler: WordingHandler? weak var delegate: DialogManagerDelegate? - init(withStringHandler stringHandler: StringHandler? = nil, andDelegate delegate: DialogManagerDelegate? = nil) { - self.stringHandler = stringHandler + init(withWordingHandler wordingHandler: WordingHandler? = nil, + andDelegate delegate: DialogManagerDelegate? = nil) { + self.stringHandler = wordingHandler self.delegate = delegate } diff --git a/Source/LaunchGate.swift b/Source/LaunchGate.swift index bb1b221..6002111 100755 --- a/Source/LaunchGate.swift +++ b/Source/LaunchGate.swift @@ -32,7 +32,7 @@ public class LaunchGate { // MARK: - Public API - weak var delegate: LaunchGateDelegate? + open weak var delegate: LaunchGateDelegate? /** Failable initializer. If either the `configURI` or `appStoreURI` are unable to be @@ -45,18 +45,25 @@ public class LaunchGate { - Returns: A `LaunchGate` instance or `nil` */ - public init?(configURI: String, appStoreURI: String, stringHandler: StringHandler? = nil, dialogDelegate: DialogManagerDelegate? = nil) { + public init?(configURI: String, appStoreURI: String, + wordingHandler: WordingHandler? = nil, + dialogDelegate: DialogManagerDelegate? = nil) { guard let configURL = URL(string: configURI) else { return nil } guard let appStoreURL = URL(string: appStoreURI) else { return nil } configurationFileURL = configURL updateURL = appStoreURL parser = DefaultParser() - dialogManager = DialogManager(withStringHandler: stringHandler, andDelegate: dialogDelegate) - } - - public func setDelegate(_ delegate: LaunchGateDelegate?) { - self.delegate = delegate + dialogManager = DialogManager(withWordingHandler: wordingHandler, andDelegate: dialogDelegate) + + print( + """ + 🍒 [LaunchGate] configurationFileUrl = \(configurationFileURL) + updateUrl = \(updateURL) + parser = \(parser) + dialogManager = \(dialogManager) + """ + ) } /// Check the configuration file and perform any appropriate action. diff --git a/Source/StringHandler.swift b/Source/WordingHandler.swift similarity index 65% rename from Source/StringHandler.swift rename to Source/WordingHandler.swift index a5caf94..240a202 100644 --- a/Source/StringHandler.swift +++ b/Source/WordingHandler.swift @@ -1,5 +1,5 @@ // -// StringHandler.swift +// WordingHandler.swift // LaunchGate // // Created by Nicolas CHARVOZ on 03/09/2018. @@ -7,15 +7,18 @@ import Foundation -open class StringHandler { +open class WordingHandler { var requiredUpdateMessage: String? var optionalUpdateMessage: String? var alertMessage: String? var dismissTitle: String? var downloadTitle: String? - public init(requiredUpdateMessage: String?, optionalUpdateMessage: String?, - alertMessage: String?, dismissTitle: String?, downloadTitle: String?) { + public init(requiredUpdateMessage: String?, + optionalUpdateMessage: String?, + alertMessage: String?, + dismissTitle: String?, + downloadTitle: String?) { self.requiredUpdateMessage = requiredUpdateMessage self.optionalUpdateMessage = optionalUpdateMessage self.alertMessage = alertMessage