// // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // import Foundation private class CallKitExperienceUpgradeViewController: ExperienceUpgradeViewController { override func loadView() { super.loadView() assert(view != nil) assert(bodyLabel != nil) // Privacy Settings Button let privacySettingsButton = UIButton() view.addSubview(privacySettingsButton) let privacyTitle = NSLocalizedString("UPGRADE_EXPERIENCE_CALLKIT_PRIVACY_SETTINGS_BUTTON", comment: "button label shown once when when user upgrades app, in context of call kit") privacySettingsButton.setTitle(privacyTitle, for: .normal) privacySettingsButton.setTitleColor(UIColor.ows_signalBrandBlue(), for: .normal) privacySettingsButton.isUserInteractionEnabled = true privacySettingsButton.addTarget(self, action:#selector(didTapPrivacySettingsButton), for: .touchUpInside) privacySettingsButton.titleLabel?.font = bodyLabel.font // Privacy Settings Button layout privacySettingsButton.autoPinWidthToSuperview(withMargin: bodyMargin) privacySettingsButton.autoPinEdge(.top, to: .bottom, of: bodyLabel, withOffset: ScaleFromIPhone5(12)) privacySettingsButton.sizeToFit() } // MARK: - Actions func didTapPrivacySettingsButton(sender: UIButton) { Logger.debug("\(TAG) in \(#function)") // dismiss the modally presented view controller, then proceed. experienceUpgradesPageViewController.dismiss(animated: true) { let fromViewController = UIApplication.shared.frontmostViewController assert(fromViewController != nil) // Construct the "settings" view & push the "privacy settings" view. let navigationController = UIStoryboard.main.instantiateViewController(withIdentifier:"SettingsNavigationController") as! UINavigationController assert(navigationController.viewControllers.count == 1) let privacySettingsViewController = PrivacySettingsTableViewController() navigationController.pushViewController(privacySettingsViewController, animated:false) fromViewController?.present(navigationController, animated: true, completion: nil) } } } private class ExperienceUpgradeViewController: UIViewController { let TAG = "[ExperienceUpgradeViewController]" let header: String let body: String let image: UIImage? let experienceUpgradesPageViewController: ExperienceUpgradesPageViewController var bodyLabel: UILabel! let bodyMargin = ScaleFromIPhone5To7Plus(12, 24) init(experienceUpgrade: ExperienceUpgrade, experienceUpgradesPageViewController: ExperienceUpgradesPageViewController) { header = experienceUpgrade.title body = experienceUpgrade.body image = experienceUpgrade.image self.experienceUpgradesPageViewController = experienceUpgradesPageViewController super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func loadView() { self.view = UIView() /// Create Views // Title label let titleLabel = UILabel() view.addSubview(titleLabel) titleLabel.text = header titleLabel.textAlignment = .center titleLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5To7Plus(26, 32)) titleLabel.textColor = UIColor.white titleLabel.minimumScaleFactor = 0.5 titleLabel.adjustsFontSizeToFitWidth = true; // Body label let bodyLabel = UILabel() self.bodyLabel = bodyLabel view.addSubview(bodyLabel) bodyLabel.text = body bodyLabel.font = UIFont.ows_lightFont(withSize: ScaleFromIPhone5To7Plus(17, 22)) bodyLabel.textColor = UIColor.black bodyLabel.numberOfLines = 0 bodyLabel.lineBreakMode = .byWordWrapping bodyLabel.textAlignment = .center // Image let imageView = UIImageView(image: image) view.addSubview(imageView) imageView.contentMode = .scaleAspectFit /// Layout Views // Title label layout titleLabel.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5To7Plus(16, 24)) titleLabel.autoPinEdge(toSuperviewEdge: .top) // Body label layout bodyLabel.autoPinWidthToSuperview(withMargin: bodyMargin) bodyLabel.sizeToFit() // Image layout imageView.autoPinWidthToSuperview() imageView.autoSetDimension(.height, toSize: ScaleFromIPhone5To7Plus(200, 280)) imageView.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: ScaleFromIPhone5To7Plus(24, 32)) imageView.autoPinEdge(.bottom, to: .top, of: bodyLabel, withOffset: -ScaleFromIPhone5To7Plus(18, 28)) } } func setPageControlAppearance() { if #available(iOS 9.0, *) { let pageControl = UIPageControl.appearance(whenContainedInInstancesOf: [UIPageViewController.self]) pageControl.pageIndicatorTintColor = UIColor.lightGray pageControl.currentPageIndicatorTintColor = UIColor.white } else { // iOS8 won't see the page controls =( } } class ExperienceUpgradesPageViewController: UIViewController, UIPageViewControllerDataSource { let TAG = "[ExperienceUpgradeViewController]" private let experienceUpgrades: [ExperienceUpgrade] private var allViewControllers = [UIViewController]() private var viewControllerIndexes = [UIViewController: Int]() let pageViewController: UIPageViewController // MARK: - Initializers required init(experienceUpgrades: [ExperienceUpgrade]) { self.experienceUpgrades = experienceUpgrades setPageControlAppearance() self.pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation:.horizontal, options: nil) super.init(nibName: nil, bundle: nil) self.pageViewController.dataSource = self experienceUpgrades.forEach { addViewController(experienceUpgrade: $0) } } @available(*, unavailable, message:"unavailable, use initWithExperienceUpgrade instead") required init?(coder aDecoder: NSCoder) { assert(false) // This should never happen, but so as not to explode we give some bogus data self.experienceUpgrades = [ExperienceUpgrade()] self.pageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation:.horizontal, options: nil) super.init(coder: aDecoder) self.pageViewController.dataSource = self } // MARK: - View lifecycle override func viewDidLoad() { guard let firstViewController = allViewControllers.first else { Logger.error("\(TAG) no pages to show.") assertionFailure() dismiss(animated: true) return } addDismissGesture() self.pageViewController.setViewControllers([ firstViewController ], direction: .forward, animated: false, completion: nil) } override func loadView() { self.view = UIView() view.backgroundColor = UIColor.white //// Create Views // Header Background let headerBackgroundView = UIView() view.addSubview(headerBackgroundView) headerBackgroundView.backgroundColor = UIColor.ows_materialBlue() // Footer Background let footerBackgroundView = UIView() view.addSubview(footerBackgroundView) footerBackgroundView.backgroundColor = UIColor.ows_materialBlue() // Dismiss button let dismissButton = UIButton() view.addSubview(dismissButton) dismissButton.setTitle(NSLocalizedString("DISMISS_BUTTON_TEXT", comment: ""), for: .normal) dismissButton.setTitleColor(UIColor.white, for: .normal) dismissButton.isUserInteractionEnabled = true dismissButton.addTarget(self, action:#selector(didTapDismissButton), for: .touchUpInside) dismissButton.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(20)) let dismissInsetValue: CGFloat = ScaleFromIPhone5(10) dismissButton.contentEdgeInsets = UIEdgeInsets(top: dismissInsetValue, left: dismissInsetValue, bottom: dismissInsetValue, right: dismissInsetValue) guard let carouselView = self.pageViewController.view else { Logger.error("\(TAG) carousel view was unexpectedly nil") return } self.view.addSubview(carouselView) //// Layout Views // Header Background layout headerBackgroundView.autoPinWidthToSuperview() headerBackgroundView.autoPinEdge(toSuperviewEdge: .top) headerBackgroundView.autoSetDimension(.height, toSize: ScaleFromIPhone5(80)) // Footer Background layout footerBackgroundView.autoPinWidthToSuperview() footerBackgroundView.autoPinEdge(toSuperviewEdge: .bottom) footerBackgroundView.autoSetDimension(.height, toSize: ScaleFromIPhone5(95)) // Dismiss button layout dismissButton.autoHCenterInSuperview() dismissButton.autoPinEdge(toSuperviewEdge: .bottom, withInset: ScaleFromIPhone5(10)) // Carousel View layout carouselView.autoPinWidthToSuperview() // negative inset so as to overlay the header text in the carousel view with the header background which // lives outside of the carousel. We do this so that the user can't bounce past the page view controllers // width limits, exposing the edge of the header. carouselView.autoPinEdge(.top, to: .bottom, of: headerBackgroundView, withOffset: ScaleFromIPhone5(-35)) carouselView.autoPinEdge(.bottom, to: .top, of: dismissButton, withOffset: ScaleFromIPhone5(-10)) } private func addDismissGesture() { let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(handleDismissGesture)) swipeGesture.direction = .down view.addGestureRecognizer(swipeGesture) } // MARK: - UIPageViewControllerDataSource public func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { Logger.debug("\(TAG) in \(#function)") guard let currentIndex = self.viewControllerIndexes[viewController] else { assertionFailure() Logger.error("\(TAG) unknown view controller: \(viewController)") return nil } if currentIndex + 1 == allViewControllers.count { // already at last view controller return nil } return allViewControllers[currentIndex + 1] } public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { Logger.debug("\(TAG) in \(#function)") guard let currentIndex = self.viewControllerIndexes[viewController] else { assertionFailure() Logger.error("\(TAG) unknown view controller: \(viewController)") return nil } if currentIndex <= 0 { // already at first view controller return nil } return allViewControllers[currentIndex - 1] } public func presentationCount(for pageViewController: UIPageViewController) -> Int { // don't show a page indicator if there's only one page. return allViewControllers.count == 1 ? 0 : allViewControllers.count } public func presentationIndex(for pageViewController: UIPageViewController) -> Int { guard let currentViewController = pageViewController.viewControllers?.first else { Logger.error("\(TAG) unexpectedly empty view controllers.") return 0 } guard let currentIndex = self.viewControllerIndexes[currentViewController] else { Logger.error("\(TAG) unknown view controller: \(currentViewController)") return 0 } return currentIndex } public func addViewController(experienceUpgrade: ExperienceUpgrade) { guard let identifier = ExperienceUpgradeId(rawValue: experienceUpgrade.uniqueId) else { Logger.error("\(TAG) unknown experience upgrade. skipping") assertionFailure() return } let viewController: ExperienceUpgradeViewController = { switch identifier { case .callKit: return CallKitExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self) default: return ExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self) } }() let count = allViewControllers.count viewControllerIndexes[viewController] = count allViewControllers.append(viewController) } func didTapDismissButton(sender: UIButton) { Logger.debug("\(TAG) in \(#function)") self.dismiss(animated: true) } func handleDismissGesture(sender: AnyObject) { Logger.debug("\(TAG) in \(#function)") self.dismiss(animated: true) } }