diff --git a/.gitignore b/.gitignore index cb6b16a8..e3d81491 100644 --- a/.gitignore +++ b/.gitignore @@ -11,8 +11,9 @@ fastlane/report.xml fastlane/test_output fastlane/metadata/review_information fastlane/metadata/trade_representative_contact_information -build -dist +build/ +dist/ +ci/BUILD .env.secret* Preview.html Gemfile.lock diff --git a/Passepartout-iOS/Global/Macros.swift b/Passepartout-iOS/Global/Macros.swift index 4b3cc95f..c28a1e24 100644 --- a/Passepartout-iOS/Global/Macros.swift +++ b/Passepartout-iOS/Global/Macros.swift @@ -58,6 +58,13 @@ extension UIAlertController { } } + func addAction(_ title: String, handler: @escaping () -> Void) { + let action = UIAlertAction(title: title, style: .default) { (action) in + handler() + } + addAction(action) + } + func addDestructiveAction(_ title: String, handler: @escaping () -> Void) { let action = UIAlertAction(title: title, style: .destructive) { (action) in handler() diff --git a/Passepartout-iOS/Scenes/Organizer/AboutViewController.swift b/Passepartout-iOS/Scenes/Organizer/AboutViewController.swift index cbfa1c75..a2665ae1 100644 --- a/Passepartout-iOS/Scenes/Organizer/AboutViewController.swift +++ b/Passepartout-iOS/Scenes/Organizer/AboutViewController.swift @@ -39,7 +39,7 @@ class AboutViewController: UITableViewController, TableModelHost { model.setHeader(L10n.About.Sections.Feedback.header, for: .feedback) model.set([.version, .credits, .website], in: .info) model.set([.sourcePassepartout, .sourceTunnelKit], in: .source) - model.set([.reportIssue, .writeReview], in: .feedback) + model.set([.discussReddit, .reportIssue, .writeReview], in: .feedback) return model }() @@ -78,6 +78,10 @@ class AboutViewController: UITableViewController, TableModelHost { UIApplication.shared.open(url, options: [:], completionHandler: nil) } + private func discussReddit() { + UIApplication.shared.open(AppConstants.URLs.subreddit, options: [:], completionHandler: nil) + } + private func reportIssue() { IssueReporter.shared.present(in: self) } @@ -114,9 +118,11 @@ extension AboutViewController { case sourceTunnelKit - case writeReview + case discussReddit case reportIssue + + case writeReview } override func numberOfSections(in tableView: UITableView) -> Int { @@ -163,6 +169,11 @@ extension AboutViewController { cell.leftText = GroupConstants.App.tunnelKitName return cell + case .discussReddit: + let cell = Cells.setting.dequeue(from: tableView, for: indexPath) + cell.leftText = L10n.About.Cells.DiscussReddit.caption + return cell + case .reportIssue: let cell = Cells.setting.dequeue(from: tableView, for: indexPath) cell.leftText = L10n.IssueReporter.title @@ -192,6 +203,9 @@ extension AboutViewController { case .sourceTunnelKit: visitRepository(AppConstants.Repos.tunnelKit) + case .discussReddit: + discussReddit() + case .reportIssue: reportIssue() diff --git a/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift b/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift index b6b703a2..2a3cd84c 100644 --- a/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift +++ b/Passepartout-iOS/Scenes/Organizer/OrganizerViewController.swift @@ -35,7 +35,9 @@ class OrganizerViewController: UITableViewController, TableModelHost { private var hostProfiles: [HostConnectionProfile] = [] private var availableProviderNames: [Infrastructure.Name]? - + + private var didShowSubreddit = false + // MARK: TableModelHost let model: TableModel = { @@ -103,6 +105,25 @@ class OrganizerViewController: UITableViewController, TableModelHost { service.delegate = self } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if !didShowSubreddit && !TransientStore.shared.didHandleSubreddit { + didShowSubreddit = true + + let alert = Macros.alert(L10n.About.Cells.DiscussReddit.caption, L10n.Reddit.message) + alert.addDefaultAction(L10n.Reddit.Buttons.subscribe) { + TransientStore.shared.didHandleSubreddit = true + self.subscribeSubreddit() + } + alert.addAction(L10n.Reddit.Buttons.never) { + TransientStore.shared.didHandleSubreddit = true + } + alert.addCancelAction(L10n.Reddit.Buttons.remind) + present(alert, animated: true, completion: nil) + } + } + override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { if let cell = sender as? UITableViewCell, let indexPath = tableView.indexPath(for: cell) { return model.row(at: indexPath) == .profile @@ -227,6 +248,10 @@ class OrganizerViewController: UITableViewController, TableModelHost { alert.addCancelAction(L10n.Global.cancel) present(alert, animated: true, completion: nil) } + + private func subscribeSubreddit() { + UIApplication.shared.open(AppConstants.URLs.subreddit, options: [:], completionHandler: nil) + } } // MARK: - diff --git a/Passepartout/Resources/en.lproj/Localizable.strings b/Passepartout/Resources/en.lproj/Localizable.strings index ce1bb8ea..b2b1b152 100644 --- a/Passepartout/Resources/en.lproj/Localizable.strings +++ b/Passepartout/Resources/en.lproj/Localizable.strings @@ -27,6 +27,11 @@ "global.cancel" = "Cancel"; "global.next" = "Next"; +"reddit.message" = "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."; +"reddit.buttons.subscribe" = "Subscribe now!"; +"reddit.buttons.remind" = "Remind me later"; +"reddit.buttons.never" = "Don't ask again"; + "organizer.sections.providers.header" = "Networks"; "organizer.sections.providers.footer" = "Here you find a few public infrastructures offering preset configuration profiles."; "organizer.sections.hosts.header" = "Hosts"; @@ -155,7 +160,7 @@ "vpn.inactive" = "Inactive"; "vpn.disabled" = "Disabled"; -"issue_reporter.title" = "Report issue"; +"issue_reporter.title" = "Report an issue"; "issue_reporter.message" = "Do you want to attach the debug log of your latest connections? The log is crucial to resolve your connectivity issues and is completely anonymous."; "issue_reporter.buttons.with_log" = "Attach debug log"; "issue_reporter.buttons.without_log" = "Omit debug log"; @@ -168,6 +173,7 @@ "about.sections.source.header" = "Source code"; "about.sections.feedback.header" = "Feedback"; "about.cells.version.caption" = "Version"; +"about.cells.discuss_reddit.caption" = "Discuss on Reddit"; "about.cells.write_review.caption" = "Write a review"; "about.cells.website.caption" = "Visit website"; diff --git a/Passepartout/Sources/AppConstants.swift b/Passepartout/Sources/AppConstants.swift index 0988eccb..b05c52ab 100644 --- a/Passepartout/Sources/AppConstants.swift +++ b/Passepartout/Sources/AppConstants.swift @@ -110,6 +110,8 @@ class AppConstants { static let changelog = Repos.passepartout.appendingPathComponent("blob/master/CHANGELOG.md") + static let subreddit = URL(string: "https://www.reddit.com/r/passepartout")! + static func review(withId id: String) -> URL { return URL(string: "https://itunes.apple.com/app/id\(id)?action=write-review")! } diff --git a/Passepartout/Sources/Model/TransientStore.swift b/Passepartout/Sources/Model/TransientStore.swift index 8125e194..58ee4846 100644 --- a/Passepartout/Sources/Model/TransientStore.swift +++ b/Passepartout/Sources/Model/TransientStore.swift @@ -29,12 +29,25 @@ import SwiftyBeaver private let log = SwiftyBeaver.self class TransientStore { + private struct Keys { + static let didHandleSubreddit = "DidHandleSubreddit" + } + static let shared = TransientStore() private let servicePath: URL let service: ConnectionService + var didHandleSubreddit: Bool { + get { + return UserDefaults.standard.bool(forKey: Keys.didHandleSubreddit) + } + set { + UserDefaults.standard.set(newValue, forKey: Keys.didHandleSubreddit) + } + } + private init() { servicePath = FileManager.default.userURL( for: .documentDirectory, diff --git a/Passepartout/Sources/SwiftGen+Strings.swift b/Passepartout/Sources/SwiftGen+Strings.swift index 66e18426..4e37c50a 100644 --- a/Passepartout/Sources/SwiftGen+Strings.swift +++ b/Passepartout/Sources/SwiftGen+Strings.swift @@ -14,6 +14,11 @@ internal enum L10n { internal enum Cells { + internal enum DiscussReddit { + /// Discuss on Reddit + internal static let caption = L10n.tr("Localizable", "about.cells.discuss_reddit.caption") + } + internal enum Version { /// Version internal static let caption = L10n.tr("Localizable", "about.cells.version.caption") @@ -275,7 +280,7 @@ internal enum L10n { internal enum IssueReporter { /// Do you want to attach the debug log of your latest connections? The log is crucial to resolve your connectivity issues and is completely anonymous. internal static let message = L10n.tr("Localizable", "issue_reporter.message") - /// Report issue + /// Report an issue internal static let title = L10n.tr("Localizable", "issue_reporter.title") internal enum Alerts { @@ -390,6 +395,20 @@ internal enum L10n { } } + 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("Localizable", "reddit.message") + + internal enum Buttons { + /// Don't ask again + internal static let never = L10n.tr("Localizable", "reddit.buttons.never") + /// Remind me later + internal static let remind = L10n.tr("Localizable", "reddit.buttons.remind") + /// Subscribe now! + internal static let subscribe = L10n.tr("Localizable", "reddit.buttons.subscribe") + } + } + internal enum Service { internal enum Alerts {