Merge branch 'restrict-macos-features'
This commit is contained in:
commit
95449149d3
|
@ -175,9 +175,12 @@
|
|||
0E57F64620C83FC7008323CF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E57F64420C83FC7008323CF /* LaunchScreen.storyboard */; };
|
||||
0E6268942369AD0600355F75 /* PurchaseTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6268932369AD0600355F75 /* PurchaseTableViewCell.swift */; };
|
||||
0E66A270225FE25800F9C779 /* PoolCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E66A26F225FE25800F9C779 /* PoolCategory.swift */; };
|
||||
0E6BA54B25C9EE3A000CDFAC /* Purchase.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E6BA54D25C9EE3A000CDFAC /* Purchase.storyboard */; };
|
||||
0E6BE13F20CFBAB300A6DD36 /* DebugLogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E6BE13E20CFBAB300A6DD36 /* DebugLogViewController.swift */; };
|
||||
0E773BF8224BF37600CDDC8E /* ShortcutsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E773BF7224BF37600CDDC8E /* ShortcutsViewController.swift */; };
|
||||
0E776642229D0DAE0023FA76 /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 0E3CAFAD229AAE760008E5C8 /* Intents.intentdefinition */; settings = {ATTRIBUTES = (no_codegen, ); }; };
|
||||
0E79D2C825C9F1B300D12964 /* PurchaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E79D2C725C9F1B300D12964 /* PurchaseViewController.swift */; };
|
||||
0E79D31E25CC0CF600D12964 /* PurchaseProductView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E79D31D25CC0CF600D12964 /* PurchaseProductView.swift */; };
|
||||
0E89DFCE213EEDFA00741BA1 /* WizardProviderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E89DFCD213EEDFA00741BA1 /* WizardProviderViewController.swift */; };
|
||||
0E9AA978259F756A003FAFF1 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9AA977259F756A003FAFF1 /* PacketTunnelProvider.swift */; };
|
||||
0E9AA979259F756A003FAFF1 /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E9AA977259F756A003FAFF1 /* PacketTunnelProvider.swift */; };
|
||||
|
@ -485,6 +488,7 @@
|
|||
0E66A26F225FE25800F9C779 /* PoolCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PoolCategory.swift; path = ../Model/Profiles/PoolCategory.swift; sourceTree = "<group>"; };
|
||||
0E6ACB7722B1A57C001B3C99 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Intents.strings; sourceTree = "<group>"; };
|
||||
0E6ACB7822B1A5BB001B3C99 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Core.strings; sourceTree = "<group>"; };
|
||||
0E6BA54C25C9EE3A000CDFAC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Purchase.storyboard; sourceTree = "<group>"; };
|
||||
0E6BE13920CFB76800A6DD36 /* ApplicationError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationError.swift; sourceTree = "<group>"; };
|
||||
0E6BE13E20CFBAB300A6DD36 /* DebugLogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugLogViewController.swift; sourceTree = "<group>"; };
|
||||
0E773BF7224BF37600CDDC8E /* ShortcutsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -500,6 +504,8 @@
|
|||
0E776640229D0DA80023FA76 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Intents.strings; sourceTree = "<group>"; };
|
||||
0E79D13E21919EC900BB5FB2 /* PlaceholderConnectionProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderConnectionProfile.swift; sourceTree = "<group>"; };
|
||||
0E79D14021919F5600BB5FB2 /* ProfileKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileKey.swift; sourceTree = "<group>"; };
|
||||
0E79D2C725C9F1B300D12964 /* PurchaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseViewController.swift; sourceTree = "<group>"; };
|
||||
0E79D31D25CC0CF600D12964 /* PurchaseProductView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PurchaseProductView.swift; sourceTree = "<group>"; };
|
||||
0E89DFC4213DF7AE00741BA1 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||
0E89DFC7213E8FC500741BA1 /* SessionProxy+Communication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+Communication.swift"; sourceTree = "<group>"; };
|
||||
0E89DFCD213EEDFA00741BA1 /* WizardProviderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardProviderViewController.swift; sourceTree = "<group>"; };
|
||||
|
@ -771,6 +777,7 @@
|
|||
0E569F7D259F41690022DFB8 /* Providers.xcassets */,
|
||||
0E569F85259F41690022DFB8 /* Main.storyboard */,
|
||||
0E569F81259F41690022DFB8 /* Preferences.storyboard */,
|
||||
0E6BA54D25C9EE3A000CDFAC /* Purchase.storyboard */,
|
||||
0E569F83259F41690022DFB8 /* Service.storyboard */,
|
||||
);
|
||||
path = macOS;
|
||||
|
@ -801,6 +808,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
0E569F65259F41690022DFB8 /* Preferences */,
|
||||
0E6BA54125C9ED91000CDFAC /* Purchase */,
|
||||
0E569F6C259F41690022DFB8 /* Service */,
|
||||
0E569F69259F41690022DFB8 /* OrganizerProfileTableView.swift */,
|
||||
0E569F6A259F41690022DFB8 /* OrganizerViewController.swift */,
|
||||
|
@ -932,6 +940,15 @@
|
|||
path = iOS;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0E6BA54125C9ED91000CDFAC /* Purchase */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0E79D31D25CC0CF600D12964 /* PurchaseProductView.swift */,
|
||||
0E79D2C725C9F1B300D12964 /* PurchaseViewController.swift */,
|
||||
);
|
||||
path = Purchase;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0E89DFCC213EEDE700741BA1 /* Organizer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1486,6 +1503,7 @@
|
|||
0E52031D259F58BF00CBAB56 /* Providers.xcassets in Resources */,
|
||||
0E52047D259F642600CBAB56 /* Preferences.storyboard in Resources */,
|
||||
0E52038F259F593F00CBAB56 /* App.strings in Resources */,
|
||||
0E6BA54B25C9EE3A000CDFAC /* Purchase.storyboard in Resources */,
|
||||
0E520385259F593B00CBAB56 /* Credits.html in Resources */,
|
||||
0E52047C259F642600CBAB56 /* Service.storyboard in Resources */,
|
||||
0E52032B259F58DD00CBAB56 /* TextTableView.xib in Resources */,
|
||||
|
@ -1882,6 +1900,7 @@
|
|||
0E294AA225AE2B0B00CB4908 /* Descriptible.swift in Sources */,
|
||||
0E520356259F590600CBAB56 /* PreferencesGeneralViewController.swift in Sources */,
|
||||
0E520348259F58FE00CBAB56 /* MTUViewController.swift in Sources */,
|
||||
0E79D31E25CC0CF600D12964 /* PurchaseProductView.swift in Sources */,
|
||||
0E52037E259F593B00CBAB56 /* SwiftGen+Segues.swift in Sources */,
|
||||
0E52037C259F593B00CBAB56 /* Theme.swift in Sources */,
|
||||
0E52035E259F591300CBAB56 /* StatusMenu.swift in Sources */,
|
||||
|
@ -1895,6 +1914,7 @@
|
|||
0E520354259F590600CBAB56 /* PreferencesViewController.swift in Sources */,
|
||||
0E520343259F58FE00CBAB56 /* ProfileCustomizationViewController.swift in Sources */,
|
||||
0E520346259F58FE00CBAB56 /* TrustedNetworksViewController.swift in Sources */,
|
||||
0E79D2C825C9F1B300D12964 /* PurchaseViewController.swift in Sources */,
|
||||
0E520338259F58F500CBAB56 /* ProviderServiceView.swift in Sources */,
|
||||
0E520347259F58FE00CBAB56 /* ProxyViewController.swift in Sources */,
|
||||
0E52037B259F593B00CBAB56 /* IssueReporter.swift in Sources */,
|
||||
|
@ -2247,6 +2267,14 @@
|
|||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0E6BA54D25C9EE3A000CDFAC /* Purchase.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
0E6BA54C25C9EE3A000CDFAC /* Base */,
|
||||
);
|
||||
name = Purchase.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0ED38ADC213F44D00004D387 /* Organizer.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
|
|
|
@ -95,30 +95,6 @@ internal enum L10n {
|
|||
}
|
||||
}
|
||||
}
|
||||
internal enum Purchase {
|
||||
/// Purchase
|
||||
internal static let title = L10n.tr("App", "purchase.title")
|
||||
internal enum Cells {
|
||||
internal enum FullVersion {
|
||||
/// - All providers (including future ones)\n%@
|
||||
internal static func extraDescription(_ p1: Any) -> String {
|
||||
return L10n.tr("App", "purchase.cells.full_version.extra_description", String(describing: p1))
|
||||
}
|
||||
}
|
||||
internal enum Restore {
|
||||
/// If you bought this app or feature in the past, you can restore your purchases and this screen won't show again.
|
||||
internal static let description = L10n.tr("App", "purchase.cells.restore.description")
|
||||
/// Restore purchases
|
||||
internal static let title = L10n.tr("App", "purchase.cells.restore.title")
|
||||
}
|
||||
}
|
||||
internal enum Sections {
|
||||
internal enum Products {
|
||||
/// Every product is a one-time purchase. Provider purchases do not include a VPN subscription.
|
||||
internal static let footer = L10n.tr("App", "purchase.sections.products.footer")
|
||||
}
|
||||
}
|
||||
}
|
||||
internal enum Service {
|
||||
internal enum Alerts {
|
||||
internal enum Location {
|
||||
|
@ -765,6 +741,30 @@ internal enum L10n {
|
|||
}
|
||||
}
|
||||
}
|
||||
internal enum Purchase {
|
||||
/// Purchase
|
||||
internal static let title = L10n.tr("Core", "purchase.title")
|
||||
internal enum Cells {
|
||||
internal enum FullVersion {
|
||||
/// - All providers (including future ones)\n%@
|
||||
internal static func extraDescription(_ p1: Any) -> String {
|
||||
return L10n.tr("Core", "purchase.cells.full_version.extra_description", String(describing: p1))
|
||||
}
|
||||
}
|
||||
internal enum Restore {
|
||||
/// If you bought this app or feature in the past, you can restore your purchases and this screen won't show again.
|
||||
internal static let description = L10n.tr("Core", "purchase.cells.restore.description")
|
||||
/// Restore purchases
|
||||
internal static let title = L10n.tr("Core", "purchase.cells.restore.title")
|
||||
}
|
||||
}
|
||||
internal enum Sections {
|
||||
internal enum Products {
|
||||
/// Every product is a one-time purchase. Provider purchases do not include a VPN subscription.
|
||||
internal static let footer = L10n.tr("Core", "purchase.sections.products.footer")
|
||||
}
|
||||
}
|
||||
}
|
||||
internal enum Reddit {
|
||||
/// Did you know that Passepartout has a subreddit? Subscribe for updates or to discuss issues, features, new platforms or whatever you like.\n\nIt's also a great way to show you care about this project.
|
||||
internal static let message = L10n.tr("Core", "reddit.message")
|
||||
|
|
|
@ -43,9 +43,13 @@ class PurchaseViewController: UITableViewController, StrongTableHost {
|
|||
weak var delegate: PurchaseViewControllerDelegate?
|
||||
|
||||
private var skFeature: SKProduct?
|
||||
|
||||
private var skPlatformVersion: SKProduct?
|
||||
|
||||
private var skFullVersion: SKProduct?
|
||||
|
||||
private var platformVersionExtra: String?
|
||||
|
||||
private var fullVersionExtra: String?
|
||||
|
||||
// MARK: StrongTableHost
|
||||
|
@ -55,10 +59,14 @@ class PurchaseViewController: UITableViewController, StrongTableHost {
|
|||
func reloadModel() {
|
||||
model.clear()
|
||||
model.add(.products)
|
||||
model.setFooter(L10n.App.Purchase.Sections.Products.footer, forSection: .products)
|
||||
model.setFooter(L10n.Core.Purchase.Sections.Products.footer, forSection: .products)
|
||||
|
||||
var rows: [RowType] = []
|
||||
let pm = ProductManager.shared
|
||||
if let skPlatformVersion = pm.product(withIdentifier: .fullVersion_iOS) {
|
||||
self.skPlatformVersion = skPlatformVersion
|
||||
rows.append(.platformVersion)
|
||||
}
|
||||
if let skFullVersion = pm.product(withIdentifier: .fullVersion) {
|
||||
self.skFullVersion = skFullVersion
|
||||
rows.append(.fullVersion)
|
||||
|
@ -70,11 +78,17 @@ class PurchaseViewController: UITableViewController, StrongTableHost {
|
|||
rows.append(.restore)
|
||||
model.set(rows, forSection: .products)
|
||||
|
||||
let featureBulletsList: [String] = ProductManager.shared.featureProducts(excluding: [.fullVersion, .fullVersion_iOS]).map {
|
||||
let platformBulletsList: [String] = ProductManager.shared.featureProducts(excluding: [.fullVersion, .fullVersion_iOS, .fullVersion_macOS]).map {
|
||||
return "- \($0.localizedTitle)"
|
||||
}.sortedCaseInsensitive()
|
||||
let featureBullets = featureBulletsList.joined(separator: "\n")
|
||||
fullVersionExtra = L10n.App.Purchase.Cells.FullVersion.extraDescription(featureBullets)
|
||||
let platformBullets = platformBulletsList.joined(separator: "\n")
|
||||
platformVersionExtra = "- \(L10n.Core.Purchase.Cells.FullVersion.extraDescription(platformBullets))"
|
||||
|
||||
let fullBulletsList: [String] = ProductManager.shared.featureProducts(excluding: [.fullVersion, .fullVersion_iOS]).map {
|
||||
return "- \($0.localizedTitle)"
|
||||
}.sortedCaseInsensitive()
|
||||
let fullBullets = fullBulletsList.joined(separator: "\n")
|
||||
fullVersionExtra = "- \(L10n.Core.Purchase.Cells.FullVersion.extraDescription(fullBullets))"
|
||||
}
|
||||
|
||||
// MARK: UIViewController
|
||||
|
@ -86,7 +100,7 @@ class PurchaseViewController: UITableViewController, StrongTableHost {
|
|||
fatalError("No feature set for purchase")
|
||||
}
|
||||
|
||||
title = L10n.App.Purchase.title
|
||||
title = L10n.Core.Purchase.title
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(close))
|
||||
|
||||
isLoading = true
|
||||
|
@ -113,6 +127,13 @@ class PurchaseViewController: UITableViewController, StrongTableHost {
|
|||
}
|
||||
purchase(sk)
|
||||
}
|
||||
|
||||
private func purchasePlatformVersion() {
|
||||
guard let sk = skPlatformVersion else {
|
||||
return
|
||||
}
|
||||
purchase(sk)
|
||||
}
|
||||
|
||||
private func purchaseFullVersion() {
|
||||
guard let sk = skFullVersion else {
|
||||
|
@ -174,8 +195,10 @@ extension PurchaseViewController {
|
|||
enum RowType {
|
||||
case feature
|
||||
|
||||
case fullVersion
|
||||
case platformVersion
|
||||
|
||||
case fullVersion
|
||||
|
||||
case restore
|
||||
}
|
||||
|
||||
|
@ -203,6 +226,12 @@ extension PurchaseViewController {
|
|||
}
|
||||
cell.fill(product: product)
|
||||
|
||||
case .platformVersion:
|
||||
guard let product = skPlatformVersion else {
|
||||
fatalError("Loaded platform version cell, yet no corresponding product?")
|
||||
}
|
||||
cell.fill(product: product, customDescription: platformVersionExtra)
|
||||
|
||||
case .fullVersion:
|
||||
guard let product = skFullVersion else {
|
||||
fatalError("Loaded full version cell, yet no corresponding product?")
|
||||
|
@ -211,8 +240,8 @@ extension PurchaseViewController {
|
|||
|
||||
case .restore:
|
||||
cell.fill(
|
||||
title: L10n.App.Purchase.Cells.Restore.title,
|
||||
description: L10n.App.Purchase.Cells.Restore.description
|
||||
title: L10n.Core.Purchase.Cells.Restore.title,
|
||||
description: L10n.Core.Purchase.Cells.Restore.description
|
||||
)
|
||||
}
|
||||
return cell
|
||||
|
@ -225,6 +254,9 @@ extension PurchaseViewController {
|
|||
case .feature:
|
||||
purchaseFeature()
|
||||
|
||||
case .platformVersion:
|
||||
purchasePlatformVersion()
|
||||
|
||||
case .fullVersion:
|
||||
purchaseFullVersion()
|
||||
|
||||
|
|
|
@ -63,9 +63,3 @@
|
|||
|
||||
"shortcuts.edit.title" = "Kurzbefehle bearbeiten";
|
||||
"shortcuts.edit.cells.add_shortcut.caption" = "Kurzbefehl hinzufügen";
|
||||
|
||||
"purchase.title" = "Kaufen";
|
||||
"purchase.sections.products.footer" = "Jedes Produkt ist ein einmaliger Kauf. Der Kauf eines Providers beinhaltet kein VPN-Abonnement.";
|
||||
"purchase.cells.full_version.extra_description" = "- Alle Anbieter (inklusive Zukünftige)\n%@";
|
||||
"purchase.cells.restore.title" = "Einkäufe wiederherstellen";
|
||||
"purchase.cells.restore.description" = "Wenn Sie diese App oder Funktion in der Vergangenheit gekauft haben, können Sie Ihre Einkäufe wiederherstellen und dieser Bildschirm wird nicht mehr angezeigt.";
|
||||
|
|
|
@ -63,9 +63,3 @@
|
|||
|
||||
"shortcuts.edit.title" = "Διαχείριση συντομεύσεων";
|
||||
"shortcuts.edit.cells.add_shortcut.caption" = "Προσθήκη Συντόμευσης";
|
||||
|
||||
"purchase.title" = "Αγορά";
|
||||
"purchase.sections.products.footer" = "Κάθε προϊόν είναι μια αγορά. Οι αγορές παρόχων δεν περιλαμβάνουν τη συνδρομή VPN.";
|
||||
"purchase.cells.full_version.extra_description" = "- Όλοι οι πάροχοι (περιλαμβάνονται και οι μελλοντικοί)\n%@";
|
||||
"purchase.cells.restore.title" = "Επαναφορά Αγορών";
|
||||
"purchase.cells.restore.description" = "Εαν αγοράσατε την εφαρμογή στο παρελθόν, μπορείτε να κάνετε επαναφορά αγορών και αυτή η οθόνη δε θα εμφανιστεί ξανά.";
|
||||
|
|
|
@ -63,9 +63,3 @@
|
|||
|
||||
"shortcuts.edit.title" = "Manage shortcuts";
|
||||
"shortcuts.edit.cells.add_shortcut.caption" = "Add shortcut";
|
||||
|
||||
"purchase.title" = "Purchase";
|
||||
"purchase.sections.products.footer" = "Every product is a one-time purchase. Provider purchases do not include a VPN subscription.";
|
||||
"purchase.cells.full_version.extra_description" = "- All providers (including future ones)\n%@";
|
||||
"purchase.cells.restore.title" = "Restore purchases";
|
||||
"purchase.cells.restore.description" = "If you bought this app or feature in the past, you can restore your purchases and this screen won't show again.";
|
||||
|
|
|
@ -63,9 +63,3 @@
|
|||
|
||||
"shortcuts.edit.title" = "Gestionar atajos";
|
||||
"shortcuts.edit.cells.add_shortcut.caption" = "Añadir atajo";
|
||||
|
||||
"purchase.title" = "Compra";
|
||||
"purchase.sections.products.footer" = "Cada producto es una compra única y no recurrente. La compra de un proveedor no incluye una suscripción al servicio.";
|
||||
"purchase.cells.full_version.extra_description" = "- Todos los proveedores (incluye los futuros)\n%@";
|
||||
"purchase.cells.restore.title" = "Restaurar compras";
|
||||
"purchase.cells.restore.description" = "Si compraste esta aplicación o funcionalidad anteriormente, puedes restaurar tus compras y esta pantalla no volverá a aparecer.";
|
||||
|
|
|
@ -63,9 +63,3 @@
|
|||
|
||||
"shortcuts.edit.title" = "Gérer les raccourcis";
|
||||
"shortcuts.edit.cells.add_shortcut.caption" = "Ajouter un raccourcis";
|
||||
|
||||
"purchase.title" = "Acheter";
|
||||
"purchase.sections.products.footer" = "Chaque produit est un achat unique. Les achats n'incluent pas une souscription à un service de VPN.";
|
||||
"purchase.cells.full_version.extra_description" = "- Tous les fournisseurs (incluant les prochains)\n%@";
|
||||
"purchase.cells.restore.title" = "Restaurer les achats";
|
||||
"purchase.cells.restore.description" = "Si vous avez acheté l'application ou une fonctionnalité dans le passé, vous pouvez restaurer les achats et ce message ne s'affichera plus.";
|
||||
|
|
|
@ -63,9 +63,3 @@
|
|||
|
||||
"shortcuts.edit.title" = "Gestisci comandi rapidi";
|
||||
"shortcuts.edit.cells.add_shortcut.caption" = "Aggiungi comando rapido";
|
||||
|
||||
"purchase.title" = "Acquista";
|
||||
"purchase.sections.products.footer" = "Ogni prodotto è un acquisto unico e non ricorrente. L'acquisto di un provider non include una sottoscrizione.";
|
||||
"purchase.cells.full_version.extra_description" = "- Tutti i provider (inclusi quelli futuri)\n%@";
|
||||
"purchase.cells.restore.title" = "Ripristina acquisti";
|
||||
"purchase.cells.restore.description" = "Se hai comprato quest'applicazione o funzionalità in precedenza, puoi ripristinare i tuoi acquisti in modo che questa schermata non compaia più.";
|
||||
|
|
|
@ -63,9 +63,3 @@
|
|||
|
||||
"shortcuts.edit.title" = "Beheer snelkoppelingen";
|
||||
"shortcuts.edit.cells.add_shortcut.caption" = "Voeg snelkoppeling toe";
|
||||
|
||||
"purchase.title" = "Aanschaffen";
|
||||
"purchase.sections.products.footer" = "Elk product is een eenmalige aankoop. Aankopen van providers bevatten geen VPN-abonnement.";
|
||||
"purchase.cells.full_version.extra_description" = "- Alle providers (inclusief toekomstige)\n%@";
|
||||
"purchase.cells.restore.title" = "Herstel Aankopen";
|
||||
"purchase.cells.restore.description" = "Als u deze app of functie in het verleden heeft gekocht, kunt u uw aankopen herstellen en wordt dit scherm niet meer getoond.";
|
||||
|
|
|
@ -63,9 +63,3 @@
|
|||
|
||||
"shortcuts.edit.title" = "Zarządzaj skrótami";
|
||||
"shortcuts.edit.cells.add_shortcut.caption" = "Dodaj skrót";
|
||||
|
||||
"purchase.title" = "Kup";
|
||||
"purchase.sections.products.footer" = "Każdy produkt to zakup jednorazowy. Kuipno usługodawcy nie zawiera subskrypcji VPN.";
|
||||
"purchase.cells.full_version.extra_description" = "- Wszyscy usługodawcy (włączając przyszłych)\n%@";
|
||||
"purchase.cells.restore.title" = "Przywróć zakup";
|
||||
"purchase.cells.restore.description" = "Jeśli kupiłeś tą aplikację lub funkcję wcześniej, możesz przywrócić swoje zakupy i ten ekran nie będzie wyświetlony ponownie.";
|
||||
|
|
|
@ -63,9 +63,3 @@
|
|||
|
||||
"shortcuts.edit.title" = "Configuração de atalhos";
|
||||
"shortcuts.edit.cells.add_shortcut.caption" = "Adicionar atalho";
|
||||
|
||||
"purchase.title" = "Comprar";
|
||||
"purchase.sections.products.footer" = "Todo produto é uma compra única. As compras do fornecedor não incluem uma assinatura VPN.";
|
||||
"purchase.cells.full_version.extra_description" = "- Todos os provedores (incluindo os futuros)\n%@";
|
||||
"purchase.cells.restore.title" = "Restaurar compras";
|
||||
"purchase.cells.restore.description" = "Se você comprou este aplicativo ou recurso no passado, pode restaurar suas compras e essa tela não será exibida novamente.";
|
||||
|
|
|
@ -63,9 +63,3 @@
|
|||
|
||||
"shortcuts.edit.title" = "Управлять командами";
|
||||
"shortcuts.edit.cells.add_shortcut.caption" = "Создать команду";
|
||||
|
||||
"purchase.title" = "Покупка";
|
||||
"purchase.sections.products.footer" = "Каждый продукт является разовой покупкой. Покупка провайдера не включает подписку на VPN.";
|
||||
"purchase.cells.full_version.extra_description" = "- Все провайдеры (включая добавленных в будущем)\n%@";
|
||||
"purchase.cells.restore.title" = "Восстановить покупки";
|
||||
"purchase.cells.restore.description" = "Если Вы купили это приложение или совершили встроенные покупки в прошлом, вы можете восстановить ваши покупки, и этот баннер больше не появится.";
|
||||
|
|
|
@ -63,9 +63,3 @@
|
|||
|
||||
"shortcuts.edit.title" = "Hantera genvägar";
|
||||
"shortcuts.edit.cells.add_shortcut.caption" = "Lägg till genväg";
|
||||
|
||||
"purchase.title" = "Köp";
|
||||
"purchase.sections.products.footer" = "Varje produkt är ett engångsköp. Leverantörsköp inkluderar inte ett VPN-abonnemang.";
|
||||
"purchase.cells.full_version.extra_description" = "- Alla leverantörer (inklusive framtida)\n%@";
|
||||
"purchase.cells.restore.title" = "Återställ köp";
|
||||
"purchase.cells.restore.description" = "Om du köpte den här appen eller funktionen tidigare kan du återställa dina inköp och den här skärmen visas inte igen.";
|
||||
|
|
|
@ -63,9 +63,3 @@
|
|||
|
||||
"shortcuts.edit.title" = "管理捷径";
|
||||
"shortcuts.edit.cells.add_shortcut.caption" = "添加捷径";
|
||||
|
||||
"purchase.title" = "购买";
|
||||
"purchase.sections.products.footer" = "每件产品都是一次性的购买。 购买的提供商并不包含VPN订阅。";
|
||||
"purchase.cells.full_version.extra_description" = "- 所有的提供商(包括未来添加的)\n%@";
|
||||
"purchase.cells.restore.title" = "恢复购买";
|
||||
"purchase.cells.restore.description" = "如果你购买过此应用或其特征, 你可以恢复购买,此页面将不在显示。";
|
||||
|
|
|
@ -57,6 +57,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||
|
||||
NSApp.mainMenu = loadMainMenu()
|
||||
StatusMenu.shared.install()
|
||||
ProductManager.shared.reviewPurchases()
|
||||
|
||||
// if let appCenterSecret = appCenterSecret, !appCenterSecret.isEmpty {
|
||||
// AppCenter.start(withAppSecret: appCenterSecret, services: [Analytics.self, Crashes.self])
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="Rv5-Zx-TH3">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Purchase View Controller-->
|
||||
<scene sceneID="9TJ-0E-yCE">
|
||||
<objects>
|
||||
<viewController id="Rv5-Zx-TH3" customClass="PurchaseViewController" customModule="Passepartout" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="8QZ-37-H7U">
|
||||
<rect key="frame" x="0.0" y="0.0" width="442" height="500"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<scrollView autohidesScrollers="YES" horizontalLineScroll="80" horizontalPageScroll="10" verticalLineScroll="80" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="WTb-Wq-BLo">
|
||||
<rect key="frame" x="20" y="120" width="402" height="360"/>
|
||||
<clipView key="contentView" id="dfi-7t-ces">
|
||||
<rect key="frame" x="1" y="1" width="400" height="358"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnSelection="YES" multipleSelection="NO" autosaveColumns="NO" rowHeight="80" rowSizeStyle="automatic" viewBased="YES" id="9w7-b6-jXh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="400" height="358"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="17" height="0.0"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn width="388" minWidth="40" maxWidth="1000" id="GRZ-an-Pd1">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" title="Text" id="gwL-5E-ehb">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<customView identifier="ProductCellIdentifier" misplaced="YES" id="pnP-i1-QiM" customClass="PurchaseProductView" customModule="Passepartout" customModuleProvider="target">
|
||||
<rect key="frame" x="8" y="0.0" width="383" height="80"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q6k-22-i5F">
|
||||
<rect key="frame" x="18" y="44" width="278" height="20"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="<title>" id="3zZ-WV-GIN">
|
||||
<font key="font" metaFont="systemMedium" size="17"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xYH-wg-hUh">
|
||||
<rect key="frame" x="18" y="20" width="347" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="<description>" id="bqY-NM-eTC">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kRO-hG-JPd">
|
||||
<rect key="frame" x="300" y="44" width="65" height="20"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="<price>" id="YYL-xk-bbo">
|
||||
<font key="font" metaFont="systemMedium" size="17"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="xYH-wg-hUh" secondAttribute="bottom" constant="20" symbolic="YES" id="1EP-V1-gsU"/>
|
||||
<constraint firstAttribute="trailing" secondItem="xYH-wg-hUh" secondAttribute="trailing" constant="20" symbolic="YES" id="Des-Zp-REX"/>
|
||||
<constraint firstAttribute="trailing" secondItem="kRO-hG-JPd" secondAttribute="trailing" constant="20" symbolic="YES" id="OY4-cV-sdC"/>
|
||||
<constraint firstItem="kRO-hG-JPd" firstAttribute="leading" secondItem="q6k-22-i5F" secondAttribute="trailing" constant="8" symbolic="YES" id="Olt-aL-NpR"/>
|
||||
<constraint firstItem="xYH-wg-hUh" firstAttribute="top" secondItem="q6k-22-i5F" secondAttribute="bottom" constant="8" symbolic="YES" id="R2x-ie-ECU"/>
|
||||
<constraint firstItem="q6k-22-i5F" firstAttribute="leading" secondItem="pnP-i1-QiM" secondAttribute="leading" constant="20" symbolic="YES" id="TQm-gM-KGy"/>
|
||||
<constraint firstItem="q6k-22-i5F" firstAttribute="top" secondItem="pnP-i1-QiM" secondAttribute="top" constant="20" symbolic="YES" id="dO0-4G-6TX"/>
|
||||
<constraint firstItem="kRO-hG-JPd" firstAttribute="centerY" secondItem="q6k-22-i5F" secondAttribute="centerY" id="f9p-8K-1bF"/>
|
||||
<constraint firstItem="xYH-wg-hUh" firstAttribute="leading" secondItem="pnP-i1-QiM" secondAttribute="leading" constant="20" symbolic="YES" id="nZu-CX-k7N"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="labelDescription" destination="xYH-wg-hUh" id="TSs-OW-1hh"/>
|
||||
<outlet property="labelPrice" destination="kRO-hG-JPd" id="QE3-Yk-pxQ"/>
|
||||
<outlet property="labelTitle" destination="q6k-22-i5F" id="0IP-Gv-Wh0"/>
|
||||
</connections>
|
||||
</customView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="Rv5-Zx-TH3" id="QpY-8y-snp"/>
|
||||
<outlet property="delegate" destination="Rv5-Zx-TH3" id="vc2-tf-15J"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="CeE-dS-NyP">
|
||||
<rect key="frame" x="1" y="329" width="400" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="2Ip-OW-X0v">
|
||||
<rect key="frame" x="224" y="17" width="15" height="102"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TKV-I9-3N8">
|
||||
<rect key="frame" x="19" y="96" width="404" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="400" id="uyO-So-B3k"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" title="<footer>" id="thS-No-Evy">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<progressIndicator hidden="YES" maxValue="100" displayedWhenStopped="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="2hB-Jy-QEw">
|
||||
<rect key="frame" x="213" y="22" width="16" height="16"/>
|
||||
</progressIndicator>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="XMI-x0-24k">
|
||||
<rect key="frame" x="230" y="13" width="94" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="<restore>" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Tce-5t-Hj1">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="doRestorePurchases:" target="Rv5-Zx-TH3" id="5Nz-PR-cvY"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lBO-a6-qnj">
|
||||
<rect key="frame" x="322" y="13" width="107" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="<purchase>" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="uxN-5f-y4i">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="doPurchase:" target="Rv5-Zx-TH3" id="e5O-mz-tRo"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="72F-hB-DPP">
|
||||
<rect key="frame" x="19" y="60" width="404" height="16"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="400" id="ejH-6r-pkl"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" title="<restore>" id="VuC-Wl-HpD">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="72F-hB-DPP" firstAttribute="top" secondItem="TKV-I9-3N8" secondAttribute="bottom" constant="20" id="8kK-yQ-UGc"/>
|
||||
<constraint firstAttribute="bottom" secondItem="lBO-a6-qnj" secondAttribute="bottom" constant="20" symbolic="YES" id="ENp-WT-Z1C"/>
|
||||
<constraint firstAttribute="trailing" secondItem="WTb-Wq-BLo" secondAttribute="trailing" constant="20" symbolic="YES" id="Ed7-QK-8qI"/>
|
||||
<constraint firstItem="lBO-a6-qnj" firstAttribute="leading" secondItem="XMI-x0-24k" secondAttribute="trailing" constant="12" symbolic="YES" id="HGl-z7-xCS"/>
|
||||
<constraint firstItem="XMI-x0-24k" firstAttribute="centerY" secondItem="lBO-a6-qnj" secondAttribute="centerY" id="Lkd-1j-rt9"/>
|
||||
<constraint firstItem="lBO-a6-qnj" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="9w7-b6-jXh" secondAttribute="leading" id="PAx-YO-dub"/>
|
||||
<constraint firstItem="TKV-I9-3N8" firstAttribute="trailing" secondItem="9w7-b6-jXh" secondAttribute="trailing" id="QMH-Dc-Vbo"/>
|
||||
<constraint firstItem="XMI-x0-24k" firstAttribute="leading" secondItem="2hB-Jy-QEw" secondAttribute="trailing" constant="8" symbolic="YES" id="Rlz-6Y-GTq"/>
|
||||
<constraint firstItem="WTb-Wq-BLo" firstAttribute="leading" secondItem="8QZ-37-H7U" secondAttribute="leading" constant="20" symbolic="YES" id="UZT-z0-YQI"/>
|
||||
<constraint firstItem="72F-hB-DPP" firstAttribute="leading" secondItem="TKV-I9-3N8" secondAttribute="leading" id="ZI9-mi-dTu"/>
|
||||
<constraint firstAttribute="trailing" secondItem="lBO-a6-qnj" secondAttribute="trailing" constant="20" symbolic="YES" id="Ztx-Qh-mHp"/>
|
||||
<constraint firstItem="lBO-a6-qnj" firstAttribute="top" secondItem="72F-hB-DPP" secondAttribute="bottom" constant="20" id="agT-yo-xZF"/>
|
||||
<constraint firstItem="TKV-I9-3N8" firstAttribute="top" secondItem="WTb-Wq-BLo" secondAttribute="bottom" constant="8" symbolic="YES" id="kzd-et-rve"/>
|
||||
<constraint firstItem="72F-hB-DPP" firstAttribute="trailing" secondItem="TKV-I9-3N8" secondAttribute="trailing" id="m9x-V7-HTG"/>
|
||||
<constraint firstItem="2hB-Jy-QEw" firstAttribute="centerY" secondItem="lBO-a6-qnj" secondAttribute="centerY" id="mX6-db-4Tp"/>
|
||||
<constraint firstItem="TKV-I9-3N8" firstAttribute="leading" secondItem="9w7-b6-jXh" secondAttribute="leading" id="pNd-TR-JzE"/>
|
||||
<constraint firstItem="WTb-Wq-BLo" firstAttribute="top" secondItem="8QZ-37-H7U" secondAttribute="top" constant="20" symbolic="YES" id="q88-zV-efL"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="activityPurchase" destination="2hB-Jy-QEw" id="WcK-o8-BZZ"/>
|
||||
<outlet property="buttonPurchase" destination="lBO-a6-qnj" id="m5t-5u-goQ"/>
|
||||
<outlet property="buttonRestore" destination="XMI-x0-24k" id="Xhd-Ph-uUY"/>
|
||||
<outlet property="labelFooter" destination="TKV-I9-3N8" id="DTN-XY-TSD"/>
|
||||
<outlet property="labelRestore" destination="72F-hB-DPP" id="rnt-Zy-gEz"/>
|
||||
<outlet property="tableView" destination="9w7-b6-jXh" id="0Ju-MH-sbN"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="GVf-vI-DWL" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="428" y="-9"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
|
@ -1220,7 +1220,7 @@ DQ
|
|||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<segue destination="KOf-Ss-PtI" kind="sheet" id="131-KX-EPr"/>
|
||||
<segue destination="KOf-Ss-PtI" kind="sheet" identifier="TrustedNetworkAddSegueIdentifier" id="131-KX-EPr"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="C9Y-vu-Z9f">
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
//
|
||||
|
||||
import Cocoa
|
||||
import PassepartoutCore
|
||||
|
||||
class Macros {
|
||||
static func warning(_ title: String, _ message: String) -> NSAlert {
|
||||
|
@ -89,6 +90,15 @@ extension NSAlert {
|
|||
}
|
||||
}
|
||||
|
||||
extension NSViewController {
|
||||
func presentPurchaseScreen(forProduct product: Product, delegate: PurchaseViewControllerDelegate? = nil) {
|
||||
let vc = StoryboardScene.Purchase.initialScene.instantiate()
|
||||
vc.feature = product
|
||||
vc.delegate = delegate
|
||||
presentAsModalWindow(vc)
|
||||
}
|
||||
}
|
||||
|
||||
extension NSView {
|
||||
static func get<T: NSView>() -> T {
|
||||
let name = String(describing: T.self)
|
||||
|
|
|
@ -24,6 +24,11 @@ internal enum StoryboardScene {
|
|||
|
||||
internal static let initialScene = InitialSceneType<PreferencesViewController>(storyboard: Preferences.self)
|
||||
}
|
||||
internal enum Purchase: StoryboardType {
|
||||
internal static let storyboardName = "Purchase"
|
||||
|
||||
internal static let initialScene = InitialSceneType<PurchaseViewController>(storyboard: Purchase.self)
|
||||
}
|
||||
internal enum Service: StoryboardType {
|
||||
internal static let storyboardName = "Service"
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ internal enum StoryboardSegue {
|
|||
internal enum Service: String, SegueType {
|
||||
case accountSegueIdentifier = "AccountSegueIdentifier"
|
||||
case customizeSegueIdentifier = "CustomizeSegueIdentifier"
|
||||
case trustedNetworkAddSegueIdentifier = "TrustedNetworkAddSegueIdentifier"
|
||||
}
|
||||
}
|
||||
// swiftlint:enable explicit_type_interface identifier_name line_length type_body_length type_name
|
||||
|
|
|
@ -763,6 +763,30 @@ internal enum L10n {
|
|||
}
|
||||
}
|
||||
}
|
||||
internal enum Purchase {
|
||||
/// Purchase
|
||||
internal static let title = L10n.tr("Core", "purchase.title")
|
||||
internal enum Cells {
|
||||
internal enum FullVersion {
|
||||
/// - All providers (including future ones)\n%@
|
||||
internal static func extraDescription(_ p1: Any) -> String {
|
||||
return L10n.tr("Core", "purchase.cells.full_version.extra_description", String(describing: p1))
|
||||
}
|
||||
}
|
||||
internal enum Restore {
|
||||
/// If you bought this app or feature in the past, you can restore your purchases and this screen won't show again.
|
||||
internal static let description = L10n.tr("Core", "purchase.cells.restore.description")
|
||||
/// Restore purchases
|
||||
internal static let title = L10n.tr("Core", "purchase.cells.restore.title")
|
||||
}
|
||||
}
|
||||
internal enum Sections {
|
||||
internal enum Products {
|
||||
/// Every product is a one-time purchase. Provider purchases do not include a VPN subscription.
|
||||
internal static let footer = L10n.tr("Core", "purchase.sections.products.footer")
|
||||
}
|
||||
}
|
||||
}
|
||||
internal enum Reddit {
|
||||
/// Did you know that Passepartout has a subreddit? Subscribe for updates or to discuss issues, features, new platforms or whatever you like.\n\nIt's also a great way to show you care about this project.
|
||||
internal static let message = L10n.tr("Core", "reddit.message")
|
||||
|
|
|
@ -131,28 +131,32 @@ class StatusMenu: NSObject {
|
|||
|
||||
let menuSupport = NSMenu()
|
||||
let itemCommunity = NSMenuItem(title: L10n.Core.Organizer.Cells.JoinCommunity.caption.asContinuation, action: #selector(joinCommunity), keyEquivalent: "")
|
||||
let itemReview = NSMenuItem(title: L10n.Core.Organizer.Cells.WriteReview.caption.asContinuation, action: #selector(writeReview), keyEquivalent: "")
|
||||
// let itemDonate = NSMenuItem(title: L10n.Core.Organizer.Cells.Donate.caption.asContinuation, action: #selector(showDonations), keyEquivalent: "")
|
||||
// let itemGitHubSponsors = NSMenuItem(title: L10n.Core.Organizer.Cells.GithubSponsors.caption.asContinuation, action: #selector(seeGitHubSponsors), keyEquivalent: "")
|
||||
// let itemTranslate = NSMenuItem(title: L10n.Core.Organizer.Cells.Translate.caption.asContinuation, action: #selector(offerToTranslate), keyEquivalent: "")
|
||||
let itemFAQ = NSMenuItem(title: L10n.Core.About.Cells.Faq.caption.asContinuation, action: #selector(visitFAQ), keyEquivalent: "")
|
||||
let itemReport = NSMenuItem(title: L10n.Core.Service.Cells.ReportIssue.caption.asContinuation, action: #selector(reportConnectivityIssue), keyEquivalent: "")
|
||||
itemCommunity.target = self
|
||||
itemReview.target = self
|
||||
// itemDonate.target = self
|
||||
// itemGitHubSponsors.target = self
|
||||
// itemTranslate.target = self
|
||||
itemFAQ.target = self
|
||||
itemReport.target = self
|
||||
// menuSupport.addItem(itemDonate)
|
||||
menuSupport.addItem(itemCommunity)
|
||||
// menuSupport.addItem(.separator())
|
||||
// menuSupport.addItem(itemGitHubSponsors)
|
||||
// menuSupport.addItem(itemTranslate)
|
||||
menuSupport.addItem(itemReview)
|
||||
if ProductManager.shared.isEligibleForFeedback() {
|
||||
let itemReview = NSMenuItem(title: L10n.Core.Organizer.Cells.WriteReview.caption.asContinuation, action: #selector(writeReview), keyEquivalent: "")
|
||||
itemReview.target = self
|
||||
menuSupport.addItem(itemReview)
|
||||
}
|
||||
menuSupport.addItem(.separator())
|
||||
menuSupport.addItem(itemFAQ)
|
||||
menuSupport.addItem(itemReport)
|
||||
if ProductManager.shared.isEligibleForFeedback() {
|
||||
let itemReport = NSMenuItem(title: L10n.Core.Service.Cells.ReportIssue.caption.asContinuation, action: #selector(reportConnectivityIssue), keyEquivalent: "")
|
||||
itemReport.target = self
|
||||
menuSupport.addItem(itemReport)
|
||||
}
|
||||
let itemSupport = NSMenuItem(title: L10n.App.Menu.Support.title, action: nil, keyEquivalent: "")
|
||||
menu.setSubmenu(menuSupport, for: itemSupport)
|
||||
menu.addItem(itemSupport)
|
||||
|
|
|
@ -88,6 +88,12 @@ class OrganizerViewController: NSViewController {
|
|||
guard let item = sender as? NSMenuItem, let metadata = item.representedObject as? Infrastructure.Metadata else {
|
||||
return
|
||||
}
|
||||
do {
|
||||
try ProductManager.shared.verifyEligible(forProvider: metadata)
|
||||
} catch {
|
||||
presentPurchaseScreen(forProduct: metadata.product)
|
||||
return
|
||||
}
|
||||
perform(segue: StoryboardSegue.Main.enterAccountSegueIdentifier, sender: metadata.name)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
//
|
||||
// PurchaseProductView.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 2/4/21.
|
||||
// Copyright (c) 2021 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/passepartoutvpn
|
||||
//
|
||||
// This file is part of Passepartout.
|
||||
//
|
||||
// Passepartout is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Passepartout is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import StoreKit
|
||||
|
||||
class PurchaseProductView: NSView {
|
||||
@IBOutlet private weak var labelTitle: NSTextField?
|
||||
|
||||
@IBOutlet private weak var labelPrice: NSTextField?
|
||||
|
||||
@IBOutlet private weak var labelDescription: NSTextField?
|
||||
|
||||
func fill(product: SKProduct, customDescription: String? = nil) {
|
||||
fill(
|
||||
title: product.localizedTitle,
|
||||
description: customDescription ?? "\(product.localizedDescription)."
|
||||
)
|
||||
labelPrice?.stringValue = product.localizedPrice ?? ""
|
||||
}
|
||||
|
||||
func fill(title: String, description: String) {
|
||||
labelTitle?.stringValue = title
|
||||
labelDescription?.stringValue = description
|
||||
labelPrice?.stringValue = ""
|
||||
}
|
||||
}
|
|
@ -0,0 +1,274 @@
|
|||
//
|
||||
// PurchaseViewController.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 2/2/21.
|
||||
// Copyright (c) 2021 Davide De Rosa. All rights reserved.
|
||||
//
|
||||
// https://github.com/passepartoutvpn
|
||||
//
|
||||
// This file is part of Passepartout.
|
||||
//
|
||||
// Passepartout is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Passepartout is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import StoreKit
|
||||
import PassepartoutCore
|
||||
import SwiftyBeaver
|
||||
import Convenience
|
||||
|
||||
private let log = SwiftyBeaver.self
|
||||
|
||||
protocol PurchaseViewControllerDelegate: class {
|
||||
func purchaseController(_ purchaseController: PurchaseViewController, didPurchase product: Product)
|
||||
}
|
||||
|
||||
class PurchaseViewController: NSViewController {
|
||||
private struct Columns {
|
||||
static let product = NSUserInterfaceItemIdentifier("ProductCellIdentifier")
|
||||
}
|
||||
|
||||
@IBOutlet private weak var tableView: NSTableView!
|
||||
|
||||
@IBOutlet private weak var labelFooter: NSTextField!
|
||||
|
||||
@IBOutlet private weak var labelRestore: NSTextField!
|
||||
|
||||
@IBOutlet private weak var activityPurchase: NSProgressIndicator!
|
||||
|
||||
@IBOutlet private weak var buttonPurchase: NSButton!
|
||||
|
||||
@IBOutlet private weak var buttonRestore: NSButton!
|
||||
|
||||
var feature: Product!
|
||||
|
||||
weak var delegate: PurchaseViewControllerDelegate?
|
||||
|
||||
private var skFeature: SKProduct?
|
||||
|
||||
private var skPlatformVersion: SKProduct?
|
||||
|
||||
private var skFullVersion: SKProduct?
|
||||
|
||||
private var platformVersionExtra: String?
|
||||
|
||||
private var fullVersionExtra: String?
|
||||
|
||||
var rows: [RowType] = []
|
||||
|
||||
func reloadModel() {
|
||||
rows = []
|
||||
let pm = ProductManager.shared
|
||||
if let skPlatformVersion = pm.product(withIdentifier: .fullVersion_macOS) {
|
||||
self.skPlatformVersion = skPlatformVersion
|
||||
rows.append(.platformVersion)
|
||||
}
|
||||
if let skFullVersion = pm.product(withIdentifier: .fullVersion) {
|
||||
self.skFullVersion = skFullVersion
|
||||
rows.append(.fullVersion)
|
||||
}
|
||||
if let skFeature = pm.product(withIdentifier: feature) {
|
||||
self.skFeature = skFeature
|
||||
rows.append(.feature)
|
||||
}
|
||||
|
||||
let platformBulletsList: [String] = ProductManager.shared.featureProducts(excluding: [.fullVersion, .fullVersion_iOS, .fullVersion_macOS]).map {
|
||||
return $0.localizedTitle
|
||||
}.sortedCaseInsensitive()
|
||||
let platformBullets = platformBulletsList.joined(separator: "\n")
|
||||
platformVersionExtra = L10n.Core.Purchase.Cells.FullVersion.extraDescription(platformBullets)
|
||||
|
||||
let fullBulletsList: [String] = ProductManager.shared.featureProducts(excluding: [.fullVersion, .fullVersion_macOS]).map {
|
||||
return $0.localizedTitle
|
||||
}.sortedCaseInsensitive()
|
||||
let fullBullets = fullBulletsList.joined(separator: "\n")
|
||||
fullVersionExtra = L10n.Core.Purchase.Cells.FullVersion.extraDescription(fullBullets)
|
||||
}
|
||||
|
||||
// MARK: NSViewController
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
title = L10n.Core.Purchase.title
|
||||
labelFooter.stringValue = L10n.Core.Purchase.Sections.Products.footer
|
||||
labelRestore.stringValue = L10n.Core.Purchase.Cells.Restore.description
|
||||
buttonPurchase.title = L10n.Core.Purchase.title
|
||||
buttonRestore.title = L10n.Core.Purchase.Cells.Restore.title
|
||||
|
||||
guard let _ = feature else {
|
||||
fatalError("No feature set for purchase")
|
||||
}
|
||||
|
||||
tableView.usesAutomaticRowHeights = true
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
override func viewWillAppear() {
|
||||
super.viewWillAppear()
|
||||
|
||||
view.window?.styleMask = [.closable, .titled]
|
||||
}
|
||||
|
||||
override func viewDidAppear() {
|
||||
super.viewDidAppear()
|
||||
|
||||
startWaiting()
|
||||
ProductManager.shared.listProducts { [weak self] (_, _) in
|
||||
self?.reloadModel()
|
||||
self?.tableView.reloadData()
|
||||
self?.stopWaiting()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
@IBAction private func doPurchase(_ sender: Any) {
|
||||
guard tableView.selectedRow != -1 else {
|
||||
return
|
||||
}
|
||||
switch rows[tableView.selectedRow] {
|
||||
case .feature:
|
||||
purchaseFeature()
|
||||
|
||||
case .platformVersion:
|
||||
purchasePlatformVersion()
|
||||
|
||||
case .fullVersion:
|
||||
purchaseFullVersion()
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction private func doRestorePurchases(_ sender: Any) {
|
||||
startWaiting()
|
||||
ProductManager.shared.restorePurchases { [weak self] in
|
||||
self?.stopWaiting()
|
||||
guard $0 == nil else {
|
||||
return
|
||||
}
|
||||
self?.dismiss(nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func purchaseFeature() {
|
||||
guard let sk = skFeature else {
|
||||
return
|
||||
}
|
||||
purchase(sk)
|
||||
}
|
||||
|
||||
private func purchasePlatformVersion() {
|
||||
guard let sk = skPlatformVersion else {
|
||||
return
|
||||
}
|
||||
purchase(sk)
|
||||
}
|
||||
|
||||
private func purchaseFullVersion() {
|
||||
guard let sk = skFullVersion else {
|
||||
return
|
||||
}
|
||||
purchase(sk)
|
||||
}
|
||||
|
||||
private func purchase(_ skProduct: SKProduct) {
|
||||
startWaiting()
|
||||
ProductManager.shared.purchase(skProduct) { [weak self] in
|
||||
self?.stopWaiting()
|
||||
guard $0 == .success else {
|
||||
if let error = $1 {
|
||||
self?.reportPurchaseError(withProduct: skProduct, error: error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard let weakSelf = self else {
|
||||
return
|
||||
}
|
||||
let product = weakSelf.feature.matchesStoreKitProduct(skProduct) ? weakSelf.feature! : .fullVersion
|
||||
weakSelf.delegate?.purchaseController(weakSelf, didPurchase: product)
|
||||
|
||||
self?.dismiss(nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func reportPurchaseError(withProduct product: SKProduct, error: Error) {
|
||||
log.error("Unable to purchase \(product): \(error)")
|
||||
|
||||
let alert = Macros.warning(product.localizedTitle, error.localizedDescription)
|
||||
_ = alert.presentModally(withOK: L10n.Core.Global.ok, cancel: nil)
|
||||
}
|
||||
|
||||
@objc private func close() {
|
||||
dismiss(nil)
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
||||
private func startWaiting() {
|
||||
tableView.isEnabled = false
|
||||
buttonPurchase.isEnabled = false
|
||||
buttonRestore.isEnabled = false
|
||||
activityPurchase.isHidden = false
|
||||
activityPurchase.startAnimation(nil)
|
||||
}
|
||||
|
||||
private func stopWaiting() {
|
||||
activityPurchase.stopAnimation(nil)
|
||||
tableView.isEnabled = true
|
||||
buttonPurchase.isEnabled = true
|
||||
buttonRestore.isEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
extension PurchaseViewController: NSTableViewDataSource, NSTableViewDelegate {
|
||||
enum RowType {
|
||||
case feature
|
||||
|
||||
case platformVersion
|
||||
|
||||
case fullVersion
|
||||
}
|
||||
|
||||
func numberOfRows(in tableView: NSTableView) -> Int {
|
||||
return rows.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
||||
guard let view = tableView.makeView(withIdentifier: Columns.product, owner: nil) as? PurchaseProductView else {
|
||||
return nil
|
||||
}
|
||||
switch rows[row] {
|
||||
case .feature:
|
||||
guard let product = skFeature else {
|
||||
fatalError("Loaded feature cell, yet no corresponding product?")
|
||||
}
|
||||
view.fill(product: product)
|
||||
|
||||
case .platformVersion:
|
||||
guard let product = skPlatformVersion else {
|
||||
fatalError("Loaded platform version cell, yet no corresponding product?")
|
||||
}
|
||||
view.fill(product: product, customDescription: platformVersionExtra)
|
||||
|
||||
case .fullVersion:
|
||||
guard let product = skFullVersion else {
|
||||
fatalError("Loaded full version cell, yet no corresponding product?")
|
||||
}
|
||||
view.fill(product: product, customDescription: fullVersionExtra)
|
||||
}
|
||||
return view
|
||||
}
|
||||
}
|
|
@ -107,6 +107,13 @@ class TrustedNetworksViewController: NSViewController, ProfileCustomization {
|
|||
}
|
||||
|
||||
@IBAction private func toggleTrustEthernet(_ sender: Any?) {
|
||||
do {
|
||||
try ProductManager.shared.verifyEligibleForTrustedNetworks()
|
||||
} catch {
|
||||
checkTrustEthernet.state = .off
|
||||
presentPurchaseScreen(forProduct: .fullVersion_macOS)
|
||||
return
|
||||
}
|
||||
trustedNetworks.includesEthernet = (checkTrustEthernet.state == .on)
|
||||
|
||||
delegate?.profileCustomization(self, didUpdateTrustedNetworks: trustedNetworks)
|
||||
|
@ -122,6 +129,18 @@ class TrustedNetworksViewController: NSViewController, ProfileCustomization {
|
|||
delegate?.profileCustomization(self, didUpdateTrustedNetworks: trustedNetworks)
|
||||
}
|
||||
|
||||
override func shouldPerformSegue(withIdentifier identifier: NSStoryboardSegue.Identifier, sender: Any?) -> Bool {
|
||||
if identifier == StoryboardSegue.Service.trustedNetworkAddSegueIdentifier.rawValue {
|
||||
do {
|
||||
try ProductManager.shared.verifyEligibleForTrustedNetworks()
|
||||
} catch {
|
||||
presentPurchaseScreen(forProduct: .fullVersion_macOS)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
|
||||
if let addVC = segue.destinationController as? TrustedNetworksAddViewController {
|
||||
addVC.delegate = self
|
||||
|
|
|
@ -8,9 +8,9 @@ strings:
|
|||
|
||||
ib:
|
||||
inputs:
|
||||
#- Base.lproj/About.storyboard
|
||||
- Base.lproj/Main.storyboard
|
||||
- Base.lproj/Preferences.storyboard
|
||||
- Base.lproj/Purchase.storyboard
|
||||
- Base.lproj/Service.storyboard
|
||||
#- Base.lproj/Shortcuts.storyboard
|
||||
outputs:
|
||||
|
|
|
@ -240,6 +240,12 @@
|
|||
|
||||
"shortcuts.edit.sections.all.header" = "Existierende Kurzbefehle";
|
||||
|
||||
"purchase.title" = "Kaufen";
|
||||
"purchase.sections.products.footer" = "Jedes Produkt ist ein einmaliger Kauf. Der Kauf eines Providers beinhaltet kein VPN-Abonnement.";
|
||||
"purchase.cells.full_version.extra_description" = "Alle Anbieter (inklusive Zukünftige)\n%@";
|
||||
"purchase.cells.restore.title" = "Einkäufe wiederherstellen";
|
||||
"purchase.cells.restore.description" = "Wenn Sie diese App oder Funktion in der Vergangenheit gekauft haben, können Sie Ihre Einkäufe wiederherstellen und dieser Bildschirm wird nicht mehr angezeigt.";
|
||||
|
||||
"donation.title" = "Spenden";
|
||||
"donation.sections.one_time.header" = "Einmalig";
|
||||
"donation.sections.one_time.footer" = "Wenn du dich erkenntlich zeigen möchtest für meine Arbeit, gibt es hier ein paar Beträge die du direkt spenden kannst.\n\nDu bezahlst pro Spende nur einmal und kannst mehrmals spenden wenn du möchtest.";
|
||||
|
|
|
@ -240,6 +240,12 @@
|
|||
|
||||
"shortcuts.edit.sections.all.header" = "Υπάρχουσες συντομεύσεις";
|
||||
|
||||
"purchase.title" = "Αγορά";
|
||||
"purchase.sections.products.footer" = "Κάθε προϊόν είναι μια αγορά. Οι αγορές παρόχων δεν περιλαμβάνουν τη συνδρομή VPN.";
|
||||
"purchase.cells.full_version.extra_description" = "Όλοι οι πάροχοι (περιλαμβάνονται και οι μελλοντικοί)\n%@";
|
||||
"purchase.cells.restore.title" = "Επαναφορά Αγορών";
|
||||
"purchase.cells.restore.description" = "Εαν αγοράσατε την εφαρμογή στο παρελθόν, μπορείτε να κάνετε επαναφορά αγορών και αυτή η οθόνη δε θα εμφανιστεί ξανά.";
|
||||
|
||||
"donation.title" = "Δωρεά";
|
||||
"donation.sections.one_time.header" = "Μια Φορά";
|
||||
"donation.sections.one_time.footer" = "Αν είστε χαρούμενη με τη δουλειά μου, εδώ είναι λίγα ποσά που μπορείτε να δώσετε αμέσως.\n\nΘα χρεωθείτε μόνο μία φορά και μπορείτε να δώσετε πολλές φορές.";
|
||||
|
|
|
@ -240,6 +240,12 @@
|
|||
|
||||
"shortcuts.edit.sections.all.header" = "Existing shortcuts";
|
||||
|
||||
"purchase.title" = "Purchase";
|
||||
"purchase.sections.products.footer" = "Every product is a one-time purchase. Provider purchases do not include a VPN subscription.";
|
||||
"purchase.cells.full_version.extra_description" = "All providers (including future ones)\n%@";
|
||||
"purchase.cells.restore.title" = "Restore purchases";
|
||||
"purchase.cells.restore.description" = "If you bought this app or feature in the past, you can restore your purchases and this screen won't show again.";
|
||||
|
||||
"donation.title" = "Donate";
|
||||
"donation.sections.one_time.header" = "One time";
|
||||
"donation.sections.one_time.footer" = "If you want to display gratitude for my free work, here are a couple amounts you can donate instantly.\n\nYou will only be charged once per donation, and you can donate multiple times.";
|
||||
|
|
|
@ -240,6 +240,12 @@
|
|||
|
||||
"shortcuts.edit.sections.all.header" = "Atajos existentes";
|
||||
|
||||
"purchase.title" = "Compra";
|
||||
"purchase.sections.products.footer" = "Cada producto es una compra única y no recurrente. La compra de un proveedor no incluye una suscripción al servicio.";
|
||||
"purchase.cells.full_version.extra_description" = "Todos los proveedores (incluye los futuros)\n%@";
|
||||
"purchase.cells.restore.title" = "Restaurar compras";
|
||||
"purchase.cells.restore.description" = "Si compraste esta aplicación o funcionalidad anteriormente, puedes restaurar tus compras y esta pantalla no volverá a aparecer.";
|
||||
|
||||
"donation.title" = "Donar";
|
||||
"donation.sections.one_time.header" = "Única";
|
||||
"donation.sections.one_time.footer" = "Si te gusta mi trabajo, aquí puedes colaborar con una donación.\n\nSólo se te cobrará una vez por donación, y puedes donar las veces que quieras.";
|
||||
|
|
|
@ -240,6 +240,12 @@
|
|||
|
||||
"shortcuts.edit.sections.all.header" = "Raccourcis existants";
|
||||
|
||||
"purchase.title" = "Acheter";
|
||||
"purchase.sections.products.footer" = "Chaque produit est un achat unique. Les achats n'incluent pas une souscription à un service de VPN.";
|
||||
"purchase.cells.full_version.extra_description" = "Tous les fournisseurs (incluant les prochains)\n%@";
|
||||
"purchase.cells.restore.title" = "Restaurer les achats";
|
||||
"purchase.cells.restore.description" = "Si vous avez acheté l'application ou une fonctionnalité dans le passé, vous pouvez restaurer les achats et ce message ne s'affichera plus.";
|
||||
|
||||
"donation.title" = "Faire un don";
|
||||
"donation.sections.one_time.header" = "Une seule fois";
|
||||
"donation.sections.one_time.footer" = "Si vous voulez manifester votre gratitude envers mon travail bénévole, voici certains montants pour faire un don instantanément.\n\n Vous n'allez être chargé qu'une seule fois par don et vous pouvez faire un don plus d'une fois.";
|
||||
|
|
|
@ -240,6 +240,12 @@
|
|||
|
||||
"shortcuts.edit.sections.all.header" = "Comandi esistenti";
|
||||
|
||||
"purchase.title" = "Acquista";
|
||||
"purchase.sections.products.footer" = "Ogni prodotto è un acquisto unico e non ricorrente. L'acquisto di un provider non include una sottoscrizione.";
|
||||
"purchase.cells.full_version.extra_description" = "Tutti i provider (inclusi quelli futuri)\n%@";
|
||||
"purchase.cells.restore.title" = "Ripristina acquisti";
|
||||
"purchase.cells.restore.description" = "Se hai comprato quest'applicazione o funzionalità in precedenza, puoi ripristinare i tuoi acquisti in modo che questa schermata non compaia più.";
|
||||
|
||||
"donation.title" = "Donazione";
|
||||
"donation.sections.one_time.header" = "Unica";
|
||||
"donation.sections.one_time.footer" = "Se vuoi mostrare gratitudine per il mio lavoro a titolo gratuito, qui trovi varie somme da donare all'istante.\n\nLa donazione ti sarà addebitata solo una volta, e puoi effettuare più donazioni.";
|
||||
|
|
|
@ -240,6 +240,12 @@
|
|||
|
||||
"shortcuts.edit.sections.all.header" = "Bestaande snelkoppelingen";
|
||||
|
||||
"purchase.title" = "Aanschaffen";
|
||||
"purchase.sections.products.footer" = "Elk product is een eenmalige aankoop. Aankopen van providers bevatten geen VPN-abonnement.";
|
||||
"purchase.cells.full_version.extra_description" = "Alle providers (inclusief toekomstige)\n%@";
|
||||
"purchase.cells.restore.title" = "Herstel Aankopen";
|
||||
"purchase.cells.restore.description" = "Als u deze app of functie in het verleden heeft gekocht, kunt u uw aankopen herstellen en wordt dit scherm niet meer getoond.";
|
||||
|
||||
"donation.title" = "Donatie";
|
||||
"donation.sections.one_time.header" = "Eenmalig";
|
||||
"donation.sections.one_time.footer" = "Als je dankbaarheid wilt tonen voor mijn gratis werk, zijn hier een paar bedragen die je direct kunt doneren.\n\nHet bedrag wordt slechts één keer per donatie in rekening gebracht en u kunt meerdere keren doneren.";
|
||||
|
|
|
@ -240,6 +240,12 @@
|
|||
|
||||
"shortcuts.edit.sections.all.header" = "Istniejące skróty";
|
||||
|
||||
"purchase.title" = "Kup";
|
||||
"purchase.sections.products.footer" = "Każdy produkt to zakup jednorazowy. Kuipno usługodawcy nie zawiera subskrypcji VPN.";
|
||||
"purchase.cells.full_version.extra_description" = "Wszyscy usługodawcy (włączając przyszłych)\n%@";
|
||||
"purchase.cells.restore.title" = "Przywróć zakup";
|
||||
"purchase.cells.restore.description" = "Jeśli kupiłeś tą aplikację lub funkcję wcześniej, możesz przywrócić swoje zakupy i ten ekran nie będzie wyświetlony ponownie.";
|
||||
|
||||
"donation.title" = "Dotacja";
|
||||
"donation.sections.one_time.header" = "Jeden raz";
|
||||
"donation.sections.one_time.footer" = "Jeśli chcesz docenić moją pracę, poniżej znajdziesz kilka kwot do wyboru dotacji.\n\nTwoje konto zostanie obciążone tylko raz na jedną dotację, możesz wysłać dotację kilka razy.";
|
||||
|
|
|
@ -240,6 +240,12 @@
|
|||
|
||||
"shortcuts.edit.sections.all.header" = "Atalhos existentes";
|
||||
|
||||
"purchase.title" = "Comprar";
|
||||
"purchase.sections.products.footer" = "Todo produto é uma compra única. As compras do fornecedor não incluem uma assinatura VPN.";
|
||||
"purchase.cells.full_version.extra_description" = "Todos os provedores (incluindo os futuros)\n%@";
|
||||
"purchase.cells.restore.title" = "Restaurar compras";
|
||||
"purchase.cells.restore.description" = "Se você comprou este aplicativo ou recurso no passado, pode restaurar suas compras e essa tela não será exibida novamente.";
|
||||
|
||||
"donation.title" = "Doar";
|
||||
"donation.sections.one_time.header" = "Uma vez";
|
||||
"donation.sections.one_time.footer" = "Se você deseja mostrar gratidão pelo meu trabalho, aqui estão alguns valores do qual você pode contribuir.\n\nVocé só será cobrado uma única vez, ou doar mais vezes caso desejar.";
|
||||
|
|
|
@ -240,6 +240,12 @@
|
|||
|
||||
"shortcuts.edit.sections.all.header" = "Существующие команды";
|
||||
|
||||
"purchase.title" = "Покупка";
|
||||
"purchase.sections.products.footer" = "Каждый продукт является разовой покупкой. Покупка провайдера не включает подписку на VPN.";
|
||||
"purchase.cells.full_version.extra_description" = "Все провайдеры (включая добавленных в будущем)\n%@";
|
||||
"purchase.cells.restore.title" = "Восстановить покупки";
|
||||
"purchase.cells.restore.description" = "Если Вы купили это приложение или совершили встроенные покупки в прошлом, вы можете восстановить ваши покупки, и этот баннер больше не появится.";
|
||||
|
||||
"donation.title" = "Пожертвовать";
|
||||
"donation.sections.one_time.header" = "Один раз";
|
||||
"donation.sections.one_time.footer" = "Если Вы хотите поблагодарить мою бесплатную работу, здесь есть несколько сумм, которые Вы можете пожертвовать прямо сейчас.\n\nСумма будет списана только один раз, а Вы можете пожертвовать несколько раз.";
|
||||
|
|
|
@ -240,6 +240,12 @@
|
|||
|
||||
"shortcuts.edit.sections.all.header" = "Befintliga genvägar";
|
||||
|
||||
"purchase.title" = "Köp";
|
||||
"purchase.sections.products.footer" = "Varje produkt är ett engångsköp. Leverantörsköp inkluderar inte ett VPN-abonnemang.";
|
||||
"purchase.cells.full_version.extra_description" = "Alla leverantörer (inklusive framtida)\n%@";
|
||||
"purchase.cells.restore.title" = "Återställ köp";
|
||||
"purchase.cells.restore.description" = "Om du köpte den här appen eller funktionen tidigare kan du återställa dina inköp och den här skärmen visas inte igen.";
|
||||
|
||||
"donation.title" = "Donera";
|
||||
"donation.sections.one_time.header" = "En gång";
|
||||
"donation.sections.one_time.footer" = "Om du vill visa tacksamhet för mitt fria arbete, här är några belopp du kan donera direkt. \n\nDu betalas endast en gång per donation, och du kan donera flera gånger. ";
|
||||
|
|
|
@ -240,6 +240,12 @@
|
|||
|
||||
"shortcuts.edit.sections.all.header" = "已经存在的捷径";
|
||||
|
||||
"purchase.title" = "购买";
|
||||
"purchase.sections.products.footer" = "每件产品都是一次性的购买。 购买的提供商并不包含VPN订阅。";
|
||||
"purchase.cells.full_version.extra_description" = "所有的提供商(包括未来添加的)\n%@";
|
||||
"purchase.cells.restore.title" = "恢复购买";
|
||||
"purchase.cells.restore.description" = "如果你购买过此应用或其特征, 你可以恢复购买,此页面将不在显示。";
|
||||
|
||||
"donation.title" = "捐助";
|
||||
"donation.sections.one_time.header" = "一次性";
|
||||
"donation.sections.one_time.footer" = "如果你想对我免费的工作表示感谢,这里是一些你可以捐助的数额。\n\n每次捐助只需要付款一次,你可以捐助多次。";
|
||||
|
|
|
@ -359,12 +359,7 @@ public class AppConstants {
|
|||
|
||||
static let lastFullVersionBuild = 2016
|
||||
#else
|
||||
static var isBetaFullVersion: Bool {
|
||||
guard !ProcessInfo.processInfo.arguments.contains("FULL_VERSION") else {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
static let isBetaFullVersion = false
|
||||
|
||||
static let lastFullVersionBuild = 0
|
||||
#endif
|
||||
|
|
|
@ -553,25 +553,29 @@ public class ConnectionService: Codable {
|
|||
log.verbose(protocolConfiguration)
|
||||
|
||||
var rules: [NEOnDemandRule] = []
|
||||
#if os(iOS)
|
||||
if profile.trustedNetworks.includesMobile {
|
||||
let rule = policyRule(for: profile)
|
||||
rule.interfaceTypeMatch = .cellular
|
||||
rules.append(rule)
|
||||
}
|
||||
#else
|
||||
if profile.trustedNetworks.includesEthernet {
|
||||
let rule = policyRule(for: profile)
|
||||
rule.interfaceTypeMatch = .ethernet
|
||||
rules.append(rule)
|
||||
}
|
||||
#endif
|
||||
let reallyTrustedWifis = Array(profile.trustedNetworks.includedWiFis.filter { $1 }.keys)
|
||||
if !reallyTrustedWifis.isEmpty {
|
||||
let rule = policyRule(for: profile)
|
||||
rule.interfaceTypeMatch = .wiFi
|
||||
rule.ssidMatch = reallyTrustedWifis
|
||||
rules.append(rule)
|
||||
do {
|
||||
try ProductManager.shared.verifyEligibleForTrustedNetworks()
|
||||
#if os(iOS)
|
||||
if profile.trustedNetworks.includesMobile {
|
||||
let rule = policyRule(for: profile)
|
||||
rule.interfaceTypeMatch = .cellular
|
||||
rules.append(rule)
|
||||
}
|
||||
#else
|
||||
if profile.trustedNetworks.includesEthernet {
|
||||
let rule = policyRule(for: profile)
|
||||
rule.interfaceTypeMatch = .ethernet
|
||||
rules.append(rule)
|
||||
}
|
||||
#endif
|
||||
let reallyTrustedWifis = Array(profile.trustedNetworks.includedWiFis.filter { $1 }.keys)
|
||||
if !reallyTrustedWifis.isEmpty {
|
||||
let rule = policyRule(for: profile)
|
||||
rule.interfaceTypeMatch = .wiFi
|
||||
rule.ssidMatch = reallyTrustedWifis
|
||||
rules.append(rule)
|
||||
}
|
||||
} catch {
|
||||
}
|
||||
let connection = NEOnDemandRuleConnect()
|
||||
connection.interfaceTypeMatch = .any
|
||||
|
|
Loading…
Reference in New Issue