diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index de3b60800..39439a41d 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -425,6 +425,7 @@ 4C090A1B210FD9C7001FD7F9 /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */; }; 4C11AA5020FD59C700351FBD /* MessageStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */; }; 4C13C9F620E57BA30089A98B /* ColorPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */; }; + 4C1D233D218B96A000A0598F /* typing-animation.gif in Resources */ = {isa = PBXBuildFile; fileRef = 4C1D233C218B96A000A0598F /* typing-animation.gif */; }; 4C20B2B720CA0034001BAC90 /* ThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4542DF51208B82E9007B4E76 /* ThreadViewModel.swift */; }; 4C20B2B920CA10DE001BAC90 /* ConversationSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */; }; 4C23A5F2215C4ADE00534937 /* SheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C23A5F1215C4ADE00534937 /* SheetViewController.swift */; }; @@ -1115,6 +1116,7 @@ 4C090A1A210FD9C7001FD7F9 /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HapticFeedback.swift; path = UserInterface/HapticFeedback.swift; sourceTree = ""; }; 4C11AA4F20FD59C700351FBD /* MessageStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStatusView.swift; sourceTree = ""; }; 4C13C9F520E57BA30089A98B /* ColorPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorPickerViewController.swift; sourceTree = ""; }; + 4C1D233C218B96A000A0598F /* typing-animation.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = "typing-animation.gif"; path = "../../../../../Downloads/typing-animation.gif"; sourceTree = ""; }; 4C20B2B820CA10DE001BAC90 /* ConversationSearchViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearchViewController.swift; sourceTree = ""; }; 4C23A5F1215C4ADE00534937 /* SheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetViewController.swift; sourceTree = ""; }; 4C2F454E214C00E1004871FF /* AvatarTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarTableViewCell.swift; sourceTree = ""; }; @@ -2261,6 +2263,7 @@ B633C4FD1A1D190B0059AC12 /* Images */ = { isa = PBXGroup; children = ( + 4C1D233C218B96A000A0598F /* typing-animation.gif */, AD83FF461A73428300B5C81A /* audio_play_button_blue.png */, AD83FF381A73426500B5C81A /* audio_pause_button_blue.png */, AD83FF391A73426500B5C81A /* audio_pause_button_blue@2x.png */, @@ -2856,6 +2859,7 @@ 45B74A742044AAB600CD42F8 /* aurora-quiet.aifc in Resources */, 45B74A852044AAB600CD42F8 /* bamboo.aifc in Resources */, 45B74A782044AAB600CD42F8 /* bamboo-quiet.aifc in Resources */, + 4C1D233D218B96A000A0598F /* typing-animation.gif in Resources */, 45B74A7B2044AAB600CD42F8 /* chord.aifc in Resources */, 45B74A812044AAB600CD42F8 /* chord-quiet.aifc in Resources */, 45B74A832044AAB600CD42F8 /* circles.aifc in Resources */, diff --git a/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift b/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift index f7c104b69..731da061f 100644 --- a/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift +++ b/Signal/src/ViewControllers/ExperienceUpgradesPageViewController.swift @@ -225,6 +225,125 @@ private class IntroductingReadReceiptsExperienceUpgradeViewController: Experienc } } +private class IntroductingTypingIndicatorsExperienceUpgradeViewController: ExperienceUpgradeViewController { + + var buttonAction: ((UIButton) -> Void)? + + override func loadView() { + self.view = UIView.container() + + /// Create Views + + // Title label + let titleLabel = UILabel() + view.addSubview(titleLabel) + titleLabel.text = header + titleLabel.textAlignment = .center + titleLabel.font = UIFont.ows_regularFont(withSize: ScaleFromIPhone5(24)) + 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 = Theme.primaryColor + bodyLabel.numberOfLines = 0 + bodyLabel.lineBreakMode = .byWordWrapping + bodyLabel.textAlignment = .center + + // Image + + let imageView: UIView + if let gifPath = Bundle.main.path(forResource: "typing-animation", ofType: "gif") { + let animatedImage = YYImage(contentsOfFile: gifPath) + imageView = YYAnimatedImageView(image: animatedImage) + } else { + owsFailDebug("gifPath was unexpectedly nil") + imageView = UIImageView(image: image) + } + + view.addSubview(imageView) + imageView.contentMode = .scaleAspectFit + + let buttonTitle = NSLocalizedString("UPGRADE_EXPERIENCE_INTRODUCING_TYPING_INDICATOR_PRIVACY_SETTINGS", comment: "button label shown one time, after upgrade") + let button = addButton(title: buttonTitle) { _ in + // dismiss the modally presented view controller, then proceed. + self.experienceUpgradesPageViewController.dismiss(animated: true) { + guard let fromViewController = UIApplication.shared.frontmostViewController as? HomeViewController else { + owsFailDebug("unexpected frontmostViewController: \(String(describing: UIApplication.shared.frontmostViewController))") + return + } + + // Construct the "settings" view & push the "privacy settings" view. + let navigationController = AppSettingsViewController.inModalNavigationController() + navigationController.pushViewController(PrivacySettingsTableViewController(), animated: false) + + fromViewController.present(navigationController, animated: true) + } + } + + let bottomSpacer = UIView() + view.addSubview(bottomSpacer) + + /// Layout Views + + // Image layout + imageView.autoAlignAxis(toSuperviewAxis: .vertical) + imageView.autoPinToSquareAspectRatio() + imageView.autoPinEdge(.top, to: .bottom, of: titleLabel, withOffset: ScaleFromIPhone5To7Plus(36, 40)) + imageView.autoSetDimension(.width, toSize: ScaleFromIPhone5(180)) + + // Title label layout + titleLabel.autoSetDimension(.height, toSize: ScaleFromIPhone5(40)) + titleLabel.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5To7Plus(16, 24)) + titleLabel.autoPinTopToSuperviewMargin() + + // Body label layout + bodyLabel.autoPinEdge(.top, to: .bottom, of: imageView, withOffset: ScaleFromIPhone5To7Plus(18, 28)) + bodyLabel.autoPinWidthToSuperview(withMargin: bodyMargin) + bodyLabel.setContentHuggingVerticalHigh() + + // Button layout + button.autoPinEdge(.top, to: .bottom, of: bodyLabel, withOffset: ScaleFromIPhone5(16)) + button.autoPinWidthToSuperview(withMargin: ScaleFromIPhone5(32)) + + bottomSpacer.autoPinEdge(.top, to: .bottom, of: button, withOffset: ScaleFromIPhone5(16)) + bottomSpacer.autoPinEdge(toSuperviewEdge: .bottom) + bottomSpacer.autoPinWidthToSuperview() + } + + // MARK: - Actions + + func addButton(title: String, action: @escaping (UIButton) -> Void) -> UIButton { + self.buttonAction = action + let button = MultiLineButton() + view.addSubview(button) + button.setTitle(title, for: .normal) + button.setTitleColor(UIColor.ows_signalBrandBlue, for: .normal) + button.isUserInteractionEnabled = true + button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside) + button.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) + button.titleLabel?.textAlignment = .center + button.titleLabel?.font = UIFont.ows_mediumFont(withSize: ScaleFromIPhone5(18)) + return button + } + + @objc func didTapButton(sender: UIButton) { + Logger.debug("") + + guard let buttonAction = self.buttonAction else { + owsFailDebug("button action was nil") + return + } + + buttonAction(sender) + } +} + /** * Allows multiple lines of button text, and ensures the buttons intrinsic content size reflects that of it's label. */ @@ -667,6 +786,8 @@ public class ExperienceUpgradesPageViewController: OWSViewController, UIPageView return IntroductingReadReceiptsExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self) case .introducingCustomNotificationAudio: return IntroducingCustomNotificationAudioExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self) + case .introducingTypingIndicators: + return IntroductingTypingIndicatorsExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self) default: return ExperienceUpgradeViewController(experienceUpgrade: experienceUpgrade, experienceUpgradesPageViewController: self) } diff --git a/Signal/src/environment/ExperienceUpgrades/ExperienceUpgradeFinder.swift b/Signal/src/environment/ExperienceUpgrades/ExperienceUpgradeFinder.swift index 859809d8b..03ccb1cef 100644 --- a/Signal/src/environment/ExperienceUpgrades/ExperienceUpgradeFinder.swift +++ b/Signal/src/environment/ExperienceUpgrades/ExperienceUpgradeFinder.swift @@ -10,7 +10,8 @@ enum ExperienceUpgradeId: String { callKit = "002", introducingProfiles = "003", introducingReadReceipts = "004", - introducingCustomNotificationAudio = "005" + introducingCustomNotificationAudio = "005", + introducingTypingIndicators = "006" } @objc public class ExperienceUpgradeFinder: NSObject { @@ -61,6 +62,13 @@ enum ExperienceUpgradeId: String { image: #imageLiteral(resourceName: "introductory_splash_custom_audio")) } + var typingIndicators: ExperienceUpgrade { + return ExperienceUpgrade(uniqueId: ExperienceUpgradeId.introducingTypingIndicators.rawValue, + title: NSLocalizedString("UPGRADE_EXPERIENCE_INTRODUCING_TYPING_INDICATORS_TITLE", comment: "Header for upgrading users"), + body: NSLocalizedString("UPGRADE_EXPERIENCE_INTRODUCING_TYPING_INDICATORS_DESCRIPTION", comment: "Body text for upgrading users"), + image: #imageLiteral(resourceName: "introductory_splash_custom_audio")) + } + // Keep these ordered by increasing uniqueId. @objc public var allExperienceUpgrades: [ExperienceUpgrade] { @@ -73,7 +81,8 @@ enum ExperienceUpgradeId: String { // (UIDevice.current.supportsCallKit ? callKit : nil), // introducingProfiles, // introducingReadReceipts, - configurableNotificationAudio + // configurableNotificationAudio + typingIndicators ].compactMap { $0 } } diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index e83999b99..11ce3a134 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -1161,7 +1161,7 @@ /* Confirmation button text to delete selected media message from the gallery */ "MEDIA_GALLERY_DELETE_SINGLE_MESSAGE" = "Delete Message"; -/* embeds {{sender name}} and {{sent date}}, e.g. 'Sarah on 10/30/18, 3:29' */ +/* embeds {{sender name}} and {{sent datetime}}, e.g. 'Sarah on 10/30/18, 3:29' */ "MEDIA_GALLERY_LANDSCAPE_TITLE_FORMAT" = "%@ on %@"; /* Short sender label for media sent by you */ @@ -2303,6 +2303,15 @@ /* Header for upgrade experience */ "UPGRADE_EXPERIENCE_INTRODUCING_READ_RECEIPTS_TITLE" = "Introducing Read Receipts"; +/* button label shown one time, after upgrade */ +"UPGRADE_EXPERIENCE_INTRODUCING_TYPING_INDICATOR_PRIVACY_SETTINGS" = "Enable typing indicators in your privacy settings."; + +/* Body text for upgrading users */ +"UPGRADE_EXPERIENCE_INTRODUCING_TYPING_INDICATORS_DESCRIPTION" = "Now you can optionally see and share when messages are being typed."; + +/* Header for upgrading users */ +"UPGRADE_EXPERIENCE_INTRODUCING_TYPING_INDICATORS_TITLE" = "Introducing Typing Indicators"; + /* Description of video calling to upgrading (existing) users */ "UPGRADE_EXPERIENCE_VIDEO_DESCRIPTION" = "Signal now supports secure video calling. Just start a call like normal, tap the camera button, and wave hello.";