diff --git a/Passepartout-iOS/Scenes/ServiceViewController.swift b/Passepartout-iOS/Scenes/ServiceViewController.swift index a5ea97ab..369622c6 100644 --- a/Passepartout-iOS/Scenes/ServiceViewController.swift +++ b/Passepartout-iOS/Scenes/ServiceViewController.swift @@ -247,7 +247,14 @@ class ServiceViewController: UIViewController, TableModelHost { } vpn.reconnect { (error) in guard error == nil else { - cell.setOn(false, animated: true) + + // XXX: delay to avoid weird toggle state + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) { + cell.setOn(false, animated: true) + if error as? ApplicationError == .externalResources { + self.requireDownload() + } + } return } self.reloadModel() @@ -439,6 +446,27 @@ class ServiceViewController: UIViewController, TableModelHost { IssueReporter.shared.present(in: self, withAttachments: attach) } + private func requireDownload() { + guard let providerProfile = profile as? ProviderConnectionProfile else { + return + } + guard let downloadURL = AppConstants.URLs.externalResources[providerProfile.name] else { + return + } + + let alert = Macros.alert( + L10n.Service.Alerts.Download.title, + L10n.Service.Alerts.Download.message(providerProfile.name.rawValue) + ) + alert.addCancelAction(L10n.Global.cancel) + alert.addDefaultAction(L10n.Global.ok) { + + // FIXME: start external download + print(downloadURL) + } + present(alert, animated: true, completion: nil) + } + // MARK: Notifications @objc private func vpnDidUpdate() { diff --git a/Passepartout/Resources/en.lproj/Localizable.strings b/Passepartout/Resources/en.lproj/Localizable.strings index 1b952a3f..2ffbb8a3 100644 --- a/Passepartout/Resources/en.lproj/Localizable.strings +++ b/Passepartout/Resources/en.lproj/Localizable.strings @@ -132,6 +132,8 @@ "service.alerts.data_count.messages.not_available" = "Information not available, are you connected?"; "service.alerts.masks_private_data.messages.must_reconnect" = "In order to safely reset the current debug log and apply the new masking preference, you must reconnect to the VPN now."; "service.alerts.buttons.reconnect" = "Reconnect"; +"service.alerts.download.title" = "Download required"; +"service.alerts.download.message" = "%@ requires the download of additional configuration files.\n\nConfirm to start the download."; "account.sections.credentials.header" = "Credentials"; "account.sections.guidance.footer.infrastructure.mullvad" = "Use your %@ website account number and password \"m\"."; diff --git a/Passepartout/Sources/AppConstants.swift b/Passepartout/Sources/AppConstants.swift index bb2b952e..3cb0848b 100644 --- a/Passepartout/Sources/AppConstants.swift +++ b/Passepartout/Sources/AppConstants.swift @@ -202,6 +202,8 @@ public class AppConstants { .tunnelBear: "https://click.tunnelbear.com/aff_c?offer_id=2&aff_id=7464", .windscribe: "https://secure.link/kCsD0prd" ] + + public static let externalResources: [Infrastructure.Name: String] = [:] } public class Repos { diff --git a/Passepartout/Sources/ApplicationError.swift b/Passepartout/Sources/ApplicationError.swift index c34f15bc..93b33889 100644 --- a/Passepartout/Sources/ApplicationError.swift +++ b/Passepartout/Sources/ApplicationError.swift @@ -33,4 +33,6 @@ public enum ApplicationError: String, Error { case migration case inactiveProfile + + case externalResources } diff --git a/Passepartout/Sources/Model/Profiles/ProviderConnectionProfile.swift b/Passepartout/Sources/Model/Profiles/ProviderConnectionProfile.swift index b83b3641..c9fa49a4 100644 --- a/Passepartout/Sources/Model/Profiles/ProviderConnectionProfile.swift +++ b/Passepartout/Sources/Model/Profiles/ProviderConnectionProfile.swift @@ -124,7 +124,7 @@ public class ProviderConnectionProfile: ConnectionProfile, Codable, Equatable { do { try preset.injectExternalConfiguration(&builder, with: name, pool: pool) } catch { - fatalError("Could not find external preset resources") + throw ApplicationError.externalResources } if let address = manualAddress { diff --git a/Passepartout/Sources/SwiftGen+Strings.swift b/Passepartout/Sources/SwiftGen+Strings.swift index b2bd6a0a..c4f1c77f 100644 --- a/Passepartout/Sources/SwiftGen+Strings.swift +++ b/Passepartout/Sources/SwiftGen+Strings.swift @@ -593,6 +593,14 @@ public enum L10n { public static let notAvailable = L10n.tr("Localizable", "service.alerts.data_count.messages.not_available") } } + public enum Download { + /// %@ requires the download of additional configuration files.\n\nConfirm to start the download. + public static func message(_ p1: String) -> String { + return L10n.tr("Localizable", "service.alerts.download.message", p1) + } + /// Download required + public static let title = L10n.tr("Localizable", "service.alerts.download.title") + } public enum MasksPrivateData { public enum Messages { /// In order to safely reset the current debug log and apply the new masking preference, you must reconnect to the VPN now. diff --git a/Passepartout/Sources/VPN/GracefulVPN.swift b/Passepartout/Sources/VPN/GracefulVPN.swift index 863b28f5..e8dc8258 100644 --- a/Passepartout/Sources/VPN/GracefulVPN.swift +++ b/Passepartout/Sources/VPN/GracefulVPN.swift @@ -75,6 +75,10 @@ public class GracefulVPN { log.info("Reconnecting...") try vpn.reconnect(configuration: service.vpnConfiguration(), completionHandler: completionHandler) } catch let e { + guard e as? ApplicationError != .externalResources else { + completionHandler?(e) + return + } log.error("Could not reconnect: \(e)") } } @@ -89,6 +93,10 @@ public class GracefulVPN { log.info("Reinstalling...") try vpn.install(configuration: service.vpnConfiguration(), completionHandler: completionHandler) } catch let e { + guard e as? ApplicationError != .externalResources else { + completionHandler?(e) + return + } log.error("Could not reinstall: \(e)") } }