diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index a194106e3..1e6fe7f26 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -74,6 +74,8 @@ 344825C6211390C800DB4BD8 /* OWSOrphanDataCleaner.m in Sources */ = {isa = PBXBuildFile; fileRef = 344825C5211390C800DB4BD8 /* OWSOrphanDataCleaner.m */; }; 3448E15C22133274004B052E /* OnboardingPermissionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */; }; 3448E15E221333F5004B052E /* OnboardingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E15D221333F5004B052E /* OnboardingController.swift */; }; + 3448E16022134C89004B052E /* OnboardingSplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */; }; + 3448E1622213585C004B052E /* OnboardingBaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3448E1612213585C004B052E /* OnboardingBaseViewController.swift */; }; 344F248D2007CCD600CFB4F4 /* DisplayableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 344F248C2007CCD600CFB4F4 /* DisplayableText.swift */; }; 345BC30C2047030700257B7C /* OWS2FASettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 345BC30B2047030600257B7C /* OWS2FASettingsViewController.m */; }; 3461284B1FD0B94000532771 /* SAELoadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3461284A1FD0B93F00532771 /* SAELoadViewController.swift */; }; @@ -727,6 +729,8 @@ 344825C5211390C800DB4BD8 /* OWSOrphanDataCleaner.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSOrphanDataCleaner.m; sourceTree = ""; }; 3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingPermissionsViewController.swift; sourceTree = ""; }; 3448E15D221333F5004B052E /* OnboardingController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingController.swift; sourceTree = ""; }; + 3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingSplashViewController.swift; sourceTree = ""; }; + 3448E1612213585C004B052E /* OnboardingBaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingBaseViewController.swift; sourceTree = ""; }; 34491FC11FB0F78500B3E5A3 /* my */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = my; path = translations/my.lproj/Localizable.strings; sourceTree = ""; }; 344F248C2007CCD600CFB4F4 /* DisplayableText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableText.swift; sourceTree = ""; }; 345BC30A2047030600257B7C /* OWS2FASettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWS2FASettingsViewController.h; sourceTree = ""; }; @@ -1443,8 +1447,10 @@ 3441FD9E21A3604F00BB9542 /* BackupRestoreViewController.swift */, 340FC879204DAC8C007AEB0F /* CodeVerificationViewController.h */, 340FC877204DAC8C007AEB0F /* CodeVerificationViewController.m */, + 3448E1612213585C004B052E /* OnboardingBaseViewController.swift */, 3448E15D221333F5004B052E /* OnboardingController.swift */, 3448E15B22133274004B052E /* OnboardingPermissionsViewController.swift */, + 3448E15F22134C88004B052E /* OnboardingSplashViewController.swift */, 346E9D5321B040B600562252 /* RegistrationController.swift */, 340FC878204DAC8C007AEB0F /* RegistrationViewController.h */, 340FC876204DAC8C007AEB0F /* RegistrationViewController.m */, @@ -3478,6 +3484,7 @@ D221A09A169C9E5E00537ABF /* main.m in Sources */, 3496957221A301A100DCFE74 /* OWSBackup.m in Sources */, 34B3F87B1E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift in Sources */, + 3448E1622213585C004B052E /* OnboardingBaseViewController.swift in Sources */, 34E5DC8220D8050D00C08145 /* RegistrationUtils.m in Sources */, 452EA09E1EA7ABE00078744B /* AttachmentPointerView.swift in Sources */, 45638BDC1F3DD0D400128435 /* DebugUICalling.swift in Sources */, @@ -3510,6 +3517,7 @@ 457C87B82032645C008D52D6 /* DebugUINotifications.swift in Sources */, 4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */, 4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */, + 3448E16022134C89004B052E /* OnboardingSplashViewController.swift in Sources */, 34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */, 458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */, 34B6A905218B4C91007C4606 /* TypingIndicatorInteraction.swift in Sources */, diff --git a/Signal/Images.xcassets/onboarding_splash_hero.imageset/Contents.json b/Signal/Images.xcassets/onboarding_splash_hero.imageset/Contents.json new file mode 100644 index 000000000..2eb68d3be --- /dev/null +++ b/Signal/Images.xcassets/onboarding_splash_hero.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Screen Shot 2019-02-12 at 2.22.35 PM.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/onboarding_splash_hero.imageset/Screen Shot 2019-02-12 at 2.22.35 PM.png b/Signal/Images.xcassets/onboarding_splash_hero.imageset/Screen Shot 2019-02-12 at 2.22.35 PM.png new file mode 100644 index 000000000..6ef277fb8 Binary files /dev/null and b/Signal/Images.xcassets/onboarding_splash_hero.imageset/Screen Shot 2019-02-12 at 2.22.35 PM.png differ diff --git a/Signal/src/ViewControllers/Registration/OnboardingBaseViewController.swift b/Signal/src/ViewControllers/Registration/OnboardingBaseViewController.swift new file mode 100644 index 000000000..9f6546b09 --- /dev/null +++ b/Signal/src/ViewControllers/Registration/OnboardingBaseViewController.swift @@ -0,0 +1,75 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +import UIKit +import PromiseKit + +@objc +public class OnboardingBaseViewController: OWSViewController { + // Unlike a delegate, we can and should retain a strong reference to the OnboardingController. + let onboardingController: OnboardingController + + @objc + public init(onboardingController: OnboardingController) { + self.onboardingController = onboardingController + + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable, message: "use other init() instead.") + required public init?(coder aDecoder: NSCoder) { + notImplemented() + } + + // MARK: - + + func titleLabel(text: String) -> UILabel { + let titleLabel = UILabel() + titleLabel.text = text + titleLabel.textColor = Theme.primaryColor + titleLabel.font = UIFont.ows_dynamicTypeTitle2.ows_mediumWeight() + titleLabel.numberOfLines = 0 + titleLabel.lineBreakMode = .byWordWrapping + titleLabel.textAlignment = .center + return titleLabel + } + + func explanationLabel(explanationText: String, linkText: String, selector: Selector) -> UILabel { + let explanationText = NSAttributedString(string: explanationText) + .rtlSafeAppend(NSAttributedString(string: " ")) + .rtlSafeAppend(linkText, + attributes: [ + NSAttributedStringKey.foregroundColor: UIColor.ows_materialBlue + ]) + let explanationLabel = UILabel() + explanationLabel.textColor = Theme.secondaryColor + explanationLabel.font = UIFont.ows_dynamicTypeCaption1 + explanationLabel.attributedText = explanationText + explanationLabel.numberOfLines = 0 + explanationLabel.textAlignment = .center + explanationLabel.lineBreakMode = .byWordWrapping + explanationLabel.isUserInteractionEnabled = true + explanationLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: selector)) + return explanationLabel + } + + func button(title: String, selector: Selector) -> UIView { + // TODO: Make sure this all fits if dynamic font sizes are maxed out. + let buttonHeight: CGFloat = 48 + let button = OWSFlatButton.button(title: title, + font: OWSFlatButton.fontForHeight(buttonHeight), + titleColor: .white, + backgroundColor: .ows_materialBlue, + target: self, + selector: selector) + button.autoSetDimension(.height, toSize: buttonHeight) + return button + } + + // MARK: Orientation + + public override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return .portrait + } +} diff --git a/Signal/src/ViewControllers/Registration/OnboardingController.swift b/Signal/src/ViewControllers/Registration/OnboardingController.swift index 8869bdda4..7514dc8b3 100644 --- a/Signal/src/ViewControllers/Registration/OnboardingController.swift +++ b/Signal/src/ViewControllers/Registration/OnboardingController.swift @@ -6,6 +6,10 @@ import UIKit @objc public protocol OnboardingController: class { + func initialViewController() -> UIViewController + + func onboardingSplashDidComplete(viewController: UIViewController) + func onboardingPermissionsWasSkipped(viewController: UIViewController) func onboardingPermissionsDidComplete(viewController: UIViewController) } @@ -13,8 +17,18 @@ public protocol OnboardingController: class { // MARK: - @objc -public class MockOnboardingController: NSObject, OnboardingController { +public class OnboardingControllerImpl: NSObject, OnboardingController { + public func initialViewController() -> UIViewController { + let view = OnboardingSplashViewController(onboardingController: self) + return view + } + + public func onboardingSplashDidComplete(viewController: UIViewController) { + let view = OnboardingPermissionsViewController(onboardingController: self) + viewController.navigationController?.pushViewController(view, animated: true) + } + public func onboardingPermissionsWasSkipped(viewController: UIViewController) {} - + public func onboardingPermissionsDidComplete(viewController: UIViewController) {} } diff --git a/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift b/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift index 495d4b6c3..c19dd160d 100644 --- a/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift +++ b/Signal/src/ViewControllers/Registration/OnboardingPermissionsViewController.swift @@ -6,23 +6,7 @@ import UIKit import PromiseKit @objc -public class OnboardingPermissionsViewController: OWSViewController { - // Unlike a delegate, we can and should retain a strong reference to the OnboardingController. - private var onboardingController: OnboardingController - - @objc - public init(onboardingController: OnboardingController) { - self.onboardingController = onboardingController - - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable, message: "use other init() instead.") - required public init?(coder aDecoder: NSCoder) { - notImplemented() - } - - // MARK: - +public class OnboardingPermissionsViewController: OnboardingBaseViewController { override public func loadView() { super.loadView() @@ -38,46 +22,25 @@ public class OnboardingPermissionsViewController: OWSViewController { target: self, action: #selector(skipWasPressed)) - let titleLabel = UILabel() - titleLabel.text = NSLocalizedString("ONBOARDING_PERMISSIONS_TITLE", comment: "Title of the 'onboarding permissions' view.") - titleLabel.textColor = Theme.primaryColor - titleLabel.font = UIFont.ows_dynamicTypeTitle2.ows_mediumWeight() - titleLabel.numberOfLines = 0 - titleLabel.lineBreakMode = .byWordWrapping - titleLabel.textAlignment = .center + let titleLabel = self.titleLabel(text: NSLocalizedString("ONBOARDING_PERMISSIONS_TITLE", comment: "Title of the 'onboarding permissions' view.")) view.addSubview(titleLabel) titleLabel.autoPinEdges(toSuperviewMarginsExcludingEdge: .bottom) - let explainerLabel = UILabel() // TODO: Finalize copy. - explainerLabel.text = NSLocalizedString("ONBOARDING_PERMISSIONS_EXPLANATION", comment: "Explanation in the 'onboarding permissions' view.") - explainerLabel.textColor = Theme.secondaryColor - explainerLabel.font = UIFont.ows_dynamicTypeCaption1 - explainerLabel.numberOfLines = 0 - explainerLabel.textAlignment = .center - explainerLabel.lineBreakMode = .byWordWrapping - explainerLabel.isUserInteractionEnabled = true - explainerLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(explainerLabelTapped))) + let explanationLabel = self.explanationLabel(explanationText: NSLocalizedString("ONBOARDING_PERMISSIONS_EXPLANATION", + comment: "Explanation in the 'onboarding permissions' view."), + linkText: NSLocalizedString("ONBOARDING_PERMISSIONS_LEARN_MORE_LINK", + comment: "Link to the 'learn more' in the 'onboarding permissions' view."), + selector: #selector(explanationLabelTapped)) // TODO: Make sure this all fits if dynamic font sizes are maxed out. - let buttonHeight: CGFloat = 48 - let giveAccessButton = OWSFlatButton.button(title: NSLocalizedString("ONBOARDING_PERMISSIONS_GIVE_ACCESS_BUTTON", - comment: "Label for the 'give access' button in the 'onboarding permissions' view."), - font: OWSFlatButton.fontForHeight(buttonHeight), - titleColor: .white, - backgroundColor: .ows_materialBlue, - target: self, - selector: #selector(giveAccessPressed)) - giveAccessButton.autoSetDimension(.height, toSize: buttonHeight) - - let notNowButton = OWSFlatButton.button(title: NSLocalizedString("ONBOARDING_PERMISSIONS_GIVE_ACCESS_BUTTON", - comment: "Label for the 'give access' button in the 'onboarding permissions' view."), - font: OWSFlatButton.fontForHeight(buttonHeight), - titleColor: .white, - backgroundColor: .ows_materialBlue, - target: self, - selector: #selector(notNowPressed)) - notNowButton.autoSetDimension(.height, toSize: buttonHeight) + let giveAccessButton = self.button(title: NSLocalizedString("ONBOARDING_PERMISSIONS_GIVE_ACCESS_BUTTON", + comment: "Label for the 'give access' button in the 'onboarding permissions' view."), + selector: #selector(giveAccessPressed)) + + let notNowButton = self.button(title: NSLocalizedString("ONBOARDING_PERMISSIONS_NOT_NOW_BUTTON", + comment: "Label for the 'not now' button in the 'onboarding permissions' view."), + selector: #selector(notNowPressed)) let buttonStack = UIStackView(arrangedSubviews: [ giveAccessButton, @@ -88,7 +51,7 @@ public class OnboardingPermissionsViewController: OWSViewController { buttonStack.spacing = 12 let stackView = UIStackView(arrangedSubviews: [ - explainerLabel, + explanationLabel, buttonStack ]) stackView.axis = .vertical @@ -146,12 +109,6 @@ public class OnboardingPermissionsViewController: OWSViewController { return promise } - // MARK: Orientation - - public override var supportedInterfaceOrientations: UIInterfaceOrientationMask { - return .portrait - } - // MARK: - Events @objc func skipWasPressed() { @@ -160,7 +117,7 @@ public class OnboardingPermissionsViewController: OWSViewController { onboardingController.onboardingPermissionsWasSkipped(viewController: self) } - @objc func explainerLabelTapped(sender: UIGestureRecognizer) { + @objc func explanationLabelTapped(sender: UIGestureRecognizer) { guard sender.state == .recognized else { return } diff --git a/Signal/src/ViewControllers/Registration/OnboardingSplashViewController.swift b/Signal/src/ViewControllers/Registration/OnboardingSplashViewController.swift new file mode 100644 index 000000000..aa4b0ce71 --- /dev/null +++ b/Signal/src/ViewControllers/Registration/OnboardingSplashViewController.swift @@ -0,0 +1,91 @@ +// +// Copyright (c) 2019 Open Whisper Systems. All rights reserved. +// + +import UIKit +import PromiseKit + +@objc +public class OnboardingSplashViewController: OnboardingBaseViewController { + + override public func loadView() { + super.loadView() + + view.backgroundColor = Theme.backgroundColor + view.layoutMargins = .zero + + // TODO: + // navigationItem.title = NSLocalizedString("SETTINGS_BACKUP", comment: "Label for the backup view in app settings.") + + let heroImage = UIImage(named: "onboarding_splash_hero") + let heroImageView = UIImageView(image: heroImage) + heroImageView.contentMode = .scaleAspectFit + heroImageView.layer.minificationFilter = kCAFilterTrilinear + heroImageView.layer.magnificationFilter = kCAFilterTrilinear + heroImageView.setCompressionResistanceLow() + heroImageView.setContentHuggingVerticalLow() + + let titleLabel = self.titleLabel(text: NSLocalizedString("ONBOARDING_SPLASH_TITLE", comment: "Title of the 'onboarding splash' view.")) + view.addSubview(titleLabel) + titleLabel.autoPinWidthToSuperviewMargins() + titleLabel.autoPinEdge(toSuperviewMargin: .top) + + // TODO: Finalize copy. + let explanationLabel = self.explanationLabel(explanationText: NSLocalizedString("ONBOARDING_SPLASH_EXPLANATION", + comment: "Explanation in the 'onboarding splash' view."), + linkText: NSLocalizedString("ONBOARDING_SPLASH_TERM_AND_PRIVACY_POLICY", + comment: "Link to the 'terms and privacy policy' in the 'onboarding splash' view."), + selector: #selector(explanationLabelTapped)) + + // TODO: Make sure this all fits if dynamic font sizes are maxed out. + let continueButton = self.button(title: NSLocalizedString("BUTTON_CONTINUE", + comment: "Label for 'continue' button."), + selector: #selector(continuePressed)) + view.addSubview(continueButton) + + let stackView = UIStackView(arrangedSubviews: [ + heroImageView, + UIView.spacer(withHeight: 22), + titleLabel, + UIView.spacer(withHeight: 56), + explanationLabel, + UIView.spacer(withHeight: 40), + continueButton + ]) + stackView.axis = .vertical + stackView.alignment = .fill + stackView.layoutMargins = UIEdgeInsets(top: 32, left: 32, bottom: 32, right: 32) + stackView.isLayoutMarginsRelativeArrangement = true + view.addSubview(stackView) + stackView.autoPinWidthToSuperviewMargins() + stackView.autoPin(toTopLayoutGuideOf: self, withInset: 0) + stackView.autoPin(toBottomLayoutGuideOf: self, withInset: 0) + } + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.navigationController?.isNavigationBarHidden = true + } + + public override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.navigationController?.isNavigationBarHidden = true + } + + // MARK: - Events + + @objc func explanationLabelTapped(sender: UIGestureRecognizer) { + guard sender.state == .recognized else { + return + } + // TODO: + } + + @objc func continuePressed() { + Logger.info("") + + onboardingController.onboardingSplashDidComplete(viewController: self) + } +} diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index ef6fbda3c..8d153c632 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -1496,6 +1496,30 @@ /* No comment provided by engineer. */ "OK" = "OK"; +/* Explanation in the 'onboarding permissions' view. */ +"ONBOARDING_PERMISSIONS_EXPLANATION" = "ONBOARDING_PERMISSIONS_EXPLANATION"; + +/* Label for the 'give access' button in the 'onboarding permissions' view. */ +"ONBOARDING_PERMISSIONS_GIVE_ACCESS_BUTTON" = "Give Access"; + +/* Link to the 'learn more' in the 'onboarding permissions' view. */ +"ONBOARDING_PERMISSIONS_LEARN_MORE_LINK" = "Learn More"; + +/* Label for the 'not now' button in the 'onboarding permissions' view. */ +"ONBOARDING_PERMISSIONS_NOT_NOW_BUTTON" = "Not Now"; + +/* Title of the 'onboarding permissions' view. */ +"ONBOARDING_PERMISSIONS_TITLE" = "We need access to your contacts and notifications"; + +/* Explanation in the 'onboarding splash' view. */ +"ONBOARDING_SPLASH_EXPLANATION" = "By continuing, you agree to Signal's terms."; + +/* Link to the 'terms and privacy policy' in the 'onboarding splash' view. */ +"ONBOARDING_SPLASH_TERM_AND_PRIVACY_POLICY" = "Terms and Privacy Policy"; + +/* Title of the 'onboarding splash' view. */ +"ONBOARDING_SPLASH_TITLE" = "Signal is the private messenger for everybody"; + /* Button text which opens the settings app */ "OPEN_SETTINGS_BUTTON" = "Settings"; diff --git a/SignalMessaging/categories/UIView+OWS.swift b/SignalMessaging/categories/UIView+OWS.swift index 1e826eb0a..d6ce22e95 100644 --- a/SignalMessaging/categories/UIView+OWS.swift +++ b/SignalMessaging/categories/UIView+OWS.swift @@ -72,4 +72,18 @@ extension UIView { return nil } } + + @objc + public class func spacer(withWidth width: CGFloat) -> UIView { + let view = UIView() + view.autoSetDimension(.width, toSize: width) + return view + } + + @objc + public class func spacer(withHeight height: CGFloat) -> UIView { + let view = UIView() + view.autoSetDimension(.height, toSize: height) + return view + } }