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 {