diff --git a/Passepartout-iOS/Base.lproj/Organizer.storyboard b/Passepartout-iOS/Base.lproj/Organizer.storyboard
index 8bf8cf4f..459eb6b2 100644
--- a/Passepartout-iOS/Base.lproj/Organizer.storyboard
+++ b/Passepartout-iOS/Base.lproj/Organizer.storyboard
@@ -246,13 +246,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -302,6 +369,7 @@
+
diff --git a/Passepartout-iOS/Global/InApp.swift b/Passepartout-iOS/Global/InApp.swift
index dddefa3c..dbd8b548 100644
--- a/Passepartout-iOS/Global/InApp.swift
+++ b/Passepartout-iOS/Global/InApp.swift
@@ -26,17 +26,30 @@
import Foundation
struct InApp {
- struct Donations {
- static let tiny = "com.algoritmico.ios.Passepartout.donations.Tiny"
+ enum Donation: String {
+ static let all: [Donation] = [
+ .tiny,
+ .small,
+ .medium,
+ .big,
+ .huge,
+ .maxi
+ ]
- static let small = "com.algoritmico.ios.Passepartout.donations.Small"
+ case tiny = "com.algoritmico.ios.Passepartout.donations.Tiny"
- static let medium = "com.algoritmico.ios.Passepartout.donations.Medium"
+ case small = "com.algoritmico.ios.Passepartout.donations.Small"
- static let big = "com.algoritmico.ios.Passepartout.donations.Big"
+ case medium = "com.algoritmico.ios.Passepartout.donations.Medium"
- static let huge = "com.algoritmico.ios.Passepartout.donations.Huge"
+ case big = "com.algoritmico.ios.Passepartout.donations.Big"
- static let maxi = "com.algoritmico.ios.Passepartout.donations.Maxi"
+ case huge = "com.algoritmico.ios.Passepartout.donations.Huge"
+
+ case maxi = "com.algoritmico.ios.Passepartout.donations.Maxi"
+
+ static func allIdentifiers() -> Set {
+ return Set(all.map { $0.rawValue })
+ }
}
}
diff --git a/Passepartout-iOS/Global/SwiftGen+Segues.swift b/Passepartout-iOS/Global/SwiftGen+Segues.swift
index bdca6a43..2a978941 100644
--- a/Passepartout-iOS/Global/SwiftGen+Segues.swift
+++ b/Passepartout-iOS/Global/SwiftGen+Segues.swift
@@ -26,6 +26,7 @@ internal enum StoryboardSegue {
internal enum Organizer: String, SegueType {
case aboutSegueIdentifier = "AboutSegueIdentifier"
case addProviderSegueIdentifier = "AddProviderSegueIdentifier"
+ case donateSegueIdentifier = "DonateSegueIdentifier"
case importHostSegueIdentifier = "ImportHostSegueIdentifier"
case selectProfileSegueIdentifier = "SelectProfileSegueIdentifier"
case showImportedHostsSegueIdentifier = "ShowImportedHostsSegueIdentifier"
diff --git a/Passepartout-iOS/Global/Theme.swift b/Passepartout-iOS/Global/Theme.swift
index b06fd2f3..5486131d 100644
--- a/Passepartout-iOS/Global/Theme.swift
+++ b/Passepartout-iOS/Global/Theme.swift
@@ -25,6 +25,7 @@
import UIKit
import MessageUI
+import StoreKit
import Passepartout_Core
extension UIColor {
@@ -165,3 +166,12 @@ extension Pool {
return ImageAsset(name: country.lowercased()).image
}
}
+
+extension SKProduct {
+ var localizedPrice: String? {
+ let fmt = NumberFormatter()
+ fmt.numberStyle = .currency
+ fmt.locale = priceLocale
+ return fmt.string(from: price)
+ }
+}
diff --git a/Passepartout-iOS/Scenes/Organizer/DonationViewController.swift b/Passepartout-iOS/Scenes/Organizer/DonationViewController.swift
new file mode 100644
index 00000000..50408974
--- /dev/null
+++ b/Passepartout-iOS/Scenes/Organizer/DonationViewController.swift
@@ -0,0 +1,120 @@
+//
+// DonationViewController.swift
+// Passepartout-iOS
+//
+// Created by Davide De Rosa on 4/6/19.
+// Copyright (c) 2019 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 .
+//
+
+import UIKit
+import StoreKit
+import Passepartout_Core
+
+class DonationViewController: UITableViewController, TableModelHost {
+ private var productsByIdentifier: [String: SKProduct] = [:]
+
+ private func setProducts(_ products: [SKProduct]) {
+ for p in products {
+ productsByIdentifier[p.productIdentifier] = p
+ }
+ reloadModel()
+ tableView.reloadData()
+ }
+
+ // MARK: TableModel
+
+ var model: TableModel = TableModel()
+
+ func reloadModel() {
+ model.clear()
+
+ let completeList: [InApp.Donation] = [.tiny, .small, .medium, .big, .huge, .maxi]
+ var list: [InApp.Donation] = []
+ for row in completeList {
+ guard let _ = productsByIdentifier[row.rawValue] else {
+ continue
+ }
+ list.append(row)
+ }
+ model.add(.oneTime)
+// model.add(.recurring)
+ model.set(list, in: .oneTime)
+ }
+
+ // MARK: UIViewController
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ title = L10n.Donation.title
+
+ let req = SKProductsRequest(productIdentifiers: InApp.Donation.allIdentifiers())
+ req.delegate = self
+ req.start()
+ }
+
+ @IBAction private func close() {
+ dismiss(animated: true, completion: nil)
+ }
+
+ // MARK: UITableViewController
+
+ override func numberOfSections(in tableView: UITableView) -> Int {
+ return model.count
+ }
+
+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return model.count(for: section)
+ }
+
+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ let productId = productIdentifier(at: indexPath)
+ guard let product = productsByIdentifier[productId] else {
+ fatalError("Row with no associated product")
+ }
+ let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
+ cell.leftText = product.localizedTitle
+ cell.rightText = product.localizedPrice
+ return cell
+ }
+}
+
+extension DonationViewController {
+ enum SectionType {
+ case oneTime
+
+ case recurring
+ }
+
+ private func productIdentifier(at indexPath: IndexPath) -> String {
+ return model.row(at: indexPath).rawValue
+ }
+}
+
+extension DonationViewController: SKProductsRequestDelegate {
+ func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
+ DispatchQueue.main.async {
+ self.setProducts(response.products)
+ }
+ }
+
+ func request(_ request: SKRequest, didFailWithError error: Error) {
+ }
+}
diff --git a/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift b/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift
index 40a43e20..0f87af3f 100644
--- a/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift
+++ b/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift
@@ -192,7 +192,7 @@ class OrganizerViewController: UITableViewController, TableModelHost {
}
private func donateToDeveloper() {
- // TODO
+ perform(segue: StoryboardSegue.Organizer.donateSegueIdentifier, sender: nil)
}
private func visitPatreon() {
diff --git a/Passepartout.xcodeproj/project.pbxproj b/Passepartout.xcodeproj/project.pbxproj
index 1df2119f..83a7ee2e 100644
--- a/Passepartout.xcodeproj/project.pbxproj
+++ b/Passepartout.xcodeproj/project.pbxproj
@@ -18,6 +18,7 @@
0E1D72B4213C118500BA1586 /* ConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */; };
0E24273A225950450064A1A3 /* About.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E24273C225950450064A1A3 /* About.storyboard */; };
0E242740225951B00064A1A3 /* InApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E24273F225951B00064A1A3 /* InApp.swift */; };
+ 0E242742225956AC0064A1A3 /* DonationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E242741225956AC0064A1A3 /* DonationViewController.swift */; };
0E2B494020FCFF990094784C /* Theme+Titles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2B493F20FCFF990094784C /* Theme+Titles.swift */; };
0E3152A4223F9EF500F61841 /* Passepartout_Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E31529B223F9EF400F61841 /* Passepartout_Core.framework */; };
0E3152AD223F9EF500F61841 /* Passepartout_Core.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E31529D223F9EF500F61841 /* Passepartout_Core.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -179,6 +180,7 @@
0E242735225944060064A1A3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = ""; };
0E24273B225950450064A1A3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/About.storyboard; sourceTree = ""; };
0E24273F225951B00064A1A3 /* InApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InApp.swift; sourceTree = ""; };
+ 0E242741225956AC0064A1A3 /* DonationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DonationViewController.swift; sourceTree = ""; };
0E2B493F20FCFF990094784C /* Theme+Titles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Titles.swift"; sourceTree = ""; };
0E2B494120FD16540094784C /* TransientStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransientStore.swift; sourceTree = ""; };
0E2D11B9217DBEDE0096822C /* ConnectionService+Configurations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConnectionService+Configurations.swift"; sourceTree = ""; };
@@ -456,6 +458,7 @@
0E89DFCC213EEDE700741BA1 /* Organizer */ = {
isa = PBXGroup;
children = (
+ 0E242741225956AC0064A1A3 /* DonationViewController.swift */,
0EB67D6A2184581E00BA6200 /* ImportedHostsViewController.swift */,
0EBE3A78213C4E5400BFA2F5 /* OrganizerViewController.swift */,
0ED38AD8213F33150004D387 /* WizardHostViewController.swift */,
@@ -1055,6 +1058,7 @@
0E36D25C224034AD006AF062 /* ShortcutsConnectToViewController.swift in Sources */,
0E05C61D20D27C82006EE732 /* Theme.swift in Sources */,
0ECC60DE2256B68A0020BEAC /* SwiftGen+Assets.swift in Sources */,
+ 0E242742225956AC0064A1A3 /* DonationViewController.swift in Sources */,
0ED38AEC2141260D0004D387 /* ConfigurationModificationDelegate.swift in Sources */,
0ECEE45020E1182E00A6BB43 /* Theme+Cells.swift in Sources */,
0E242740225951B00064A1A3 /* InApp.swift in Sources */,
diff --git a/Passepartout/Resources/en.lproj/Localizable.strings b/Passepartout/Resources/en.lproj/Localizable.strings
index a2b47ef8..b34ecc1c 100644
--- a/Passepartout/Resources/en.lproj/Localizable.strings
+++ b/Passepartout/Resources/en.lproj/Localizable.strings
@@ -247,6 +247,8 @@
"about.cells.join_community.caption" = "Join community";
"about.cells.write_review.caption" = "Write a review";
+"donation.title" = "Donate";
+
"share.message" = "Passepartout is an user-friendly, open source OpenVPN client for iOS and macOS";
"version.title" = "Version";
diff --git a/Passepartout/Sources/SwiftGen+Strings.swift b/Passepartout/Sources/SwiftGen+Strings.swift
index 5a67db46..e5840689 100644
--- a/Passepartout/Sources/SwiftGen+Strings.swift
+++ b/Passepartout/Sources/SwiftGen+Strings.swift
@@ -295,6 +295,11 @@ public enum L10n {
}
}
+ public enum Donation {
+ /// Donate
+ public static let title = L10n.tr("Localizable", "donation.title")
+ }
+
public enum Endpoint {
public enum Cells {
public enum AnyAddress {