From 3c904be87921af8c684bb70f39d100ffefd684bf Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Wed, 4 Dec 2019 15:17:06 +1100 Subject: [PATCH] Implement join public chat screen redesign --- Signal.xcodeproj/project.pbxproj | 10 +- .../src/Loki/Messaging/JoinPublicChatVC.swift | 100 ------ .../View Controllers/JoinPublicChatVC.swift | 296 ++++++++++++++++++ .../View Controllers/NewPrivateChatVC.swift | 23 +- .../ScanQRCodeWrapperVC.swift | 0 .../HomeView/HomeViewController.m | 6 +- .../translations/en.lproj/Localizable.strings | 10 + 7 files changed, 323 insertions(+), 122 deletions(-) delete mode 100644 Signal/src/Loki/Messaging/JoinPublicChatVC.swift create mode 100644 Signal/src/Loki/Redesign/View Controllers/JoinPublicChatVC.swift rename Signal/src/Loki/{Messaging => Redesign/View Controllers}/ScanQRCodeWrapperVC.swift (100%) diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index a5c815f44..583d52bb7 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ 241C6315231F64CE00B4198E /* CGFloat+Rounding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C6312231F5F1D00B4198E /* CGFloat+Rounding.swift */; }; 241C6316231F64CE00B4198E /* UIColor+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 241C6310231F5C4400B4198E /* UIColor+Helper.swift */; }; 24A830A22293CD0100F4CAC0 /* LokiP2PServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24A830A12293CD0100F4CAC0 /* LokiP2PServer.swift */; }; - 24BD2609234DA2050008EB0A /* JoinPublicChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24BD2608234DA2050008EB0A /* JoinPublicChatVC.swift */; }; 2AE2882E4C2B96BFFF9EE27C /* Pods_SignalShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0F94C85CB0B235DA37F68ED0 /* Pods_SignalShareExtension.framework */; }; 3403B95D20EA9527001A1F44 /* OWSContactShareButtonsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3403B95B20EA9526001A1F44 /* OWSContactShareButtonsView.m */; }; 34074F61203D0CBE004596AE /* OWSSounds.m in Sources */ = {isa = PBXBuildFile; fileRef = 34074F5F203D0CBD004596AE /* OWSSounds.m */; }; @@ -597,6 +596,7 @@ B8CCF6352396005F0091D419 /* SpaceMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B8CCF6342396005F0091D419 /* SpaceMono-Regular.ttf */; }; B8CCF63723961D6D0091D419 /* NewPrivateChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */; }; B8CCF639239721E20091D419 /* TabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF638239721E20091D419 /* TabBar.swift */; }; + B8CCF63F23975CFB0091D419 /* JoinPublicChatVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF63E23975CFB0091D419 /* JoinPublicChatVC.swift */; }; B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; }; B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; }; BFF3FB9730634F37D25903F4 /* Pods_Signal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D17BB5C25D615AB49813100C /* Pods_Signal.framework */; }; @@ -698,7 +698,6 @@ 241C6310231F5C4400B4198E /* UIColor+Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Helper.swift"; sourceTree = ""; }; 241C6312231F5F1D00B4198E /* CGFloat+Rounding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+Rounding.swift"; sourceTree = ""; }; 24A830A12293CD0100F4CAC0 /* LokiP2PServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LokiP2PServer.swift; sourceTree = ""; }; - 24BD2608234DA2050008EB0A /* JoinPublicChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinPublicChatVC.swift; sourceTree = ""; }; 264242150E87D10A357DB07B /* Pods_SignalMessaging.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SignalMessaging.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3403B95B20EA9526001A1F44 /* OWSContactShareButtonsView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactShareButtonsView.m; sourceTree = ""; }; 3403B95C20EA9527001A1F44 /* OWSContactShareButtonsView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactShareButtonsView.h; sourceTree = ""; }; @@ -1422,6 +1421,7 @@ B8CCF6342396005F0091D419 /* SpaceMono-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpaceMono-Regular.ttf"; sourceTree = ""; }; B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPrivateChatVC.swift; sourceTree = ""; }; B8CCF638239721E20091D419 /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = ""; }; + B8CCF63E23975CFB0091D419 /* JoinPublicChatVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinPublicChatVC.swift; sourceTree = ""; }; B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = ""; }; B90418E5183E9DD40038554A /* DateUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DateUtil.m; sourceTree = ""; }; B97940251832BD2400BD66CB /* UIUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIUtil.h; sourceTree = ""; }; @@ -2760,8 +2760,6 @@ B825849F2315024B001B41CB /* LokiRSSFeedPoller.swift */, B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */, B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */, - 24BD2608234DA2050008EB0A /* JoinPublicChatVC.swift */, - B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */, ); path = Messaging; sourceTree = ""; @@ -2804,6 +2802,8 @@ children = ( B8BB82A4238F627000BA5194 /* HomeVC.swift */, B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */, + B8CCF63E23975CFB0091D419 /* JoinPublicChatVC.swift */, + B893063E2383961A005EAA8E /* ScanQRCodeWrapperVC.swift */, ); path = "View Controllers"; sourceTree = ""; @@ -3959,11 +3959,11 @@ 45F32C222057297A00A300D5 /* MediaDetailViewController.m in Sources */, B8B26C8F234D629C004ED98C /* MentionCandidateSelectionView.swift in Sources */, 34B3F8851E8DF1700035BE1A /* NewGroupViewController.m in Sources */, + B8CCF63F23975CFB0091D419 /* JoinPublicChatVC.swift in Sources */, 34ABC0E421DD20C500ED9469 /* ConversationMessageMapping.swift in Sources */, B821F2F82272CED3002C88C0 /* DisplayNameVC.swift in Sources */, 34D8C0271ED3673300188D7C /* DebugUIMessages.m in Sources */, B885D5F62334A32100EE0D8E /* UIView+Constraints.swift in Sources */, - 24BD2609234DA2050008EB0A /* JoinPublicChatVC.swift in Sources */, 34DBF003206BD5A500025978 /* OWSMessageTextView.m in Sources */, 34D1F0B41F86D31D0066283D /* ConversationCollectionView.m in Sources */, 34B3F8821E8DF1700035BE1A /* NewContactThreadViewController.m in Sources */, diff --git a/Signal/src/Loki/Messaging/JoinPublicChatVC.swift b/Signal/src/Loki/Messaging/JoinPublicChatVC.swift deleted file mode 100644 index 42deb659a..000000000 --- a/Signal/src/Loki/Messaging/JoinPublicChatVC.swift +++ /dev/null @@ -1,100 +0,0 @@ - -@objc(LKJoinPublicChatVC) -final class JoinPublicChatVC : OWSViewController { - - // MARK: Components - private lazy var urlTextField: UITextField = { - let result = UITextField() - result.textColor = Theme.primaryColor - result.font = .ows_dynamicTypeBodyClamped - let placeholder = NSMutableAttributedString(string: NSLocalizedString("Enter a URL", comment: "")) - placeholder.addAttribute(.foregroundColor, value: Theme.placeholderColor, range: NSRange(location: 0, length: placeholder.length)) - result.attributedPlaceholder = placeholder - result.tintColor = .lokiGreen() - result.keyboardAppearance = .dark - result.keyboardType = .URL - result.autocapitalizationType = .none - return result - }() - - private lazy var addButton = OWSFlatButton.button(title: NSLocalizedString("Add", comment: ""), font: UIFont.ows_dynamicTypeBodyClamped.ows_mediumWeight(), titleColor: .white, backgroundColor: .lokiGreen(), target: self, selector: #selector(handleAddButtonTapped)) - - // MARK: Lifecycle - override func viewDidLoad() { - // Background color & margins - view.backgroundColor = Theme.backgroundColor - view.layoutMargins = .zero - // Navigation bar - navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(close)) - title = NSLocalizedString("Add Public Chat", comment: "") - // Separator - let separator = UIView() - separator.autoSetDimension(.height, toSize: 1 / UIScreen.main.scale) - separator.backgroundColor = Theme.hairlineColor - // Explanation label - let explanationLabel = UILabel() - explanationLabel.textColor = Theme.primaryColor - explanationLabel.font = UIFont.ows_dynamicTypeSubheadlineClamped - explanationLabel.text = NSLocalizedString("Enter the URL of the public chat you'd like to join. The Loki Public Chat URL is https://chat.lokinet.org.", comment: "") - explanationLabel.numberOfLines = 0 - explanationLabel.lineBreakMode = .byWordWrapping - // Add button - let addButtonHeight = addButton.button.titleLabel!.font.pointSize * 48 / 17 - addButton.autoSetDimension(.height, toSize: addButtonHeight) - updateAddButton(isConnecting: false) - // Stack view - let stackView = UIStackView(arrangedSubviews: [ urlTextField, UIView.spacer(withHeight: 8), separator, UIView.spacer(withHeight: 24), explanationLabel, UIView.vStretchingSpacer(), addButton ]) - stackView.axis = .vertical - stackView.alignment = .fill - stackView.layoutMargins = UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 16) - stackView.isLayoutMarginsRelativeArrangement = true - view.addSubview(stackView) - stackView.autoPinWidthToSuperview() - stackView.autoPin(toTopLayoutGuideOf: self, withInset: 0) - autoPinView(toBottomOfViewControllerOrKeyboard: stackView, avoidNotch: true) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - urlTextField.becomeFirstResponder() - } - - // MARK: Updating - private func updateAddButton(isConnecting: Bool) { - addButton.setEnabled(!isConnecting) - addButton.setTitle(isConnecting ? NSLocalizedString("Connecting...", comment: "") : NSLocalizedString("Add", comment: "")) - } - - // MARK: General - private func showError(title: String, message: String = "") { - let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil)) - presentAlert(alert) - } - - // MARK: Interaction - @objc private func close() { - dismiss(animated: true, completion: nil) - } - - @objc private func handleAddButtonTapped() { - let uncheckedURL = (urlTextField.text?.trimmingCharacters(in: .whitespaces) ?? "").lowercased().replacingOccurrences(of: "http://", with: "https://") - guard let url = URL(string: uncheckedURL), let scheme = url.scheme, scheme == "https", url.host != nil else { - return showError(title: NSLocalizedString("Invalid URL", comment: ""), message: NSLocalizedString("Please check the URL you entered and try again.", comment: "")) - } - updateAddButton(isConnecting: true) - let channelID: UInt64 = 1 - let urlAsString = url.absoluteString - let displayName = OWSProfileManager.shared().localProfileName() - LokiPublicChatManager.shared.addChat(server: urlAsString, channel: channelID) - .done(on: .main) { [weak self] _ in - let _ = LokiPublicChatAPI.getMessages(for: channelID, on: urlAsString) - let _ = LokiPublicChatAPI.setDisplayName(to: displayName, on: urlAsString) - self?.presentingViewController!.dismiss(animated: true, completion: nil) - } - .catch(on: .main) { [weak self] _ in - self?.updateAddButton(isConnecting: false) - self?.showError(title: NSLocalizedString("Couldn't Connect", comment: "")) - } - } -} diff --git a/Signal/src/Loki/Redesign/View Controllers/JoinPublicChatVC.swift b/Signal/src/Loki/Redesign/View Controllers/JoinPublicChatVC.swift new file mode 100644 index 000000000..fd8e7091a --- /dev/null +++ b/Signal/src/Loki/Redesign/View Controllers/JoinPublicChatVC.swift @@ -0,0 +1,296 @@ + +final class JoinPublicChatVC : UIViewController, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate { + private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) + private var pages: [UIViewController] = [] + private var isJoining = false + private var targetVCIndex: Int? + + // MARK: Components + private lazy var tabBar: TabBar = { + let tabs = [ + TabBar.Tab(title: NSLocalizedString("Enter Chat URL", comment: "")) { [weak self] in + guard let self = self else { return } + self.pageVC.setViewControllers([ self.pages[0] ], direction: .forward, animated: false, completion: nil) + }, + TabBar.Tab(title: NSLocalizedString("Scan QR Code", comment: "")) { [weak self] in + guard let self = self else { return } + self.pageVC.setViewControllers([ self.pages[1] ], direction: .forward, animated: false, completion: nil) + } + ] + return TabBar(tabs: tabs) + }() + + private lazy var enterChatURLVC: EnterChatURLVC = { + let result = EnterChatURLVC() + result.joinPublicChatVC = self + return result + }() + + private lazy var scanQRCodePlaceholderVC: ScanQRCodePlaceholderVC = { + let result = ScanQRCodePlaceholderVC() + result.joinPublicChatVC = self + return result + }() + + private lazy var scanQRCodeWrapperVC: ScanQRCodeWrapperVC = { + let message = NSLocalizedString("Scan the QR code of the public chat you'd like to join", comment: "") + let result = ScanQRCodeWrapperVC(message: message) + result.delegate = self + return result + }() + + // MARK: Lifecycle + override func viewDidLoad() { + // Set gradient background + view.backgroundColor = .clear + let gradient = Gradients.defaultLokiBackground + view.setGradient(gradient) + // Set navigation bar background color + let navigationBar = navigationController!.navigationBar + navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default) + navigationBar.shadowImage = UIImage() + navigationBar.isTranslucent = false + navigationBar.barTintColor = Colors.navigationBarBackground + // Set up navigation bar buttons + let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close)) + closeButton.tintColor = Colors.text + navigationItem.leftBarButtonItem = closeButton + // Customize title + let titleLabel = UILabel() + titleLabel.text = NSLocalizedString("Join Public Chat", comment: "") + titleLabel.textColor = Colors.text + titleLabel.font = UIFont.boldSystemFont(ofSize: Values.veryLargeFontSize) + navigationItem.titleView = titleLabel + // Set up page VC + let hasCameraAccess = (AVCaptureDevice.authorizationStatus(for: .video) == .authorized) + pages = [ enterChatURLVC, (hasCameraAccess ? scanQRCodeWrapperVC : scanQRCodePlaceholderVC) ] + pageVC.dataSource = self + pageVC.delegate = self + pageVC.setViewControllers([ enterChatURLVC ], direction: .forward, animated: false, completion: nil) + // Set up tab bar + view.addSubview(tabBar) + tabBar.pin(.leading, to: .leading, of: view) + tabBar.pin(.top, to: .top, of: view, withInset: navigationBar.height()) + view.pin(.trailing, to: .trailing, of: tabBar) + // Set up page VC constraints + let pageVCView = pageVC.view! + view.addSubview(pageVCView) + pageVCView.pin(.leading, to: .leading, of: view) + pageVCView.pin(.top, to: .bottom, of: tabBar) + view.pin(.trailing, to: .trailing, of: pageVCView) + view.pin(.bottom, to: .bottom, of: pageVCView) + let screen = UIScreen.main.bounds + pageVCView.set(.width, to: screen.width) + let height = navigationController!.view.bounds.height - navigationBar.height() - Values.tabBarHeight + pageVCView.set(.height, to: height) + enterChatURLVC.constrainHeight(to: height) + scanQRCodePlaceholderVC.constrainHeight(to: height) + } + + // MARK: General + func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { + guard let index = pages.firstIndex(of: viewController), index != 0 else { return nil } + return pages[index - 1] + } + + func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { + guard let index = pages.firstIndex(of: viewController), index != (pages.count - 1) else { return nil } + return pages[index + 1] + } + + fileprivate func handleCameraAccessGranted() { + pages[1] = scanQRCodeWrapperVC + pageVC.setViewControllers([ scanQRCodeWrapperVC ], direction: .forward, animated: false, completion: nil) + } + + // MARK: Updating + func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) { + guard let targetVC = pendingViewControllers.first, let index = pages.firstIndex(of: targetVC) else { return } + targetVCIndex = index + } + + func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating isFinished: Bool, previousViewControllers: [UIViewController], transitionCompleted isCompleted: Bool) { + guard isCompleted, let index = targetVCIndex else { return } + tabBar.selectTab(at: index) + } + + // MARK: Interaction + @objc private func close() { + dismiss(animated: true, completion: nil) + } + + func controller(_ controller: OWSQRCodeScanningViewController, didDetectQRCodeWith string: String) { + let chatURL = string + joinPublicChatIfPossible(with: chatURL) + } + + fileprivate func joinPublicChatIfPossible(with chatURL: String) { + guard !isJoining else { return } + guard let url = URL(string: chatURL), let scheme = url.scheme, scheme == "https", url.host != nil else { + return showError(title: NSLocalizedString("Invalid URL", comment: ""), message: NSLocalizedString("Please check the URL you entered and try again", comment: "")) + } + isJoining = true + let channelID: UInt64 = 1 + let urlAsString = url.absoluteString + let displayName = OWSProfileManager.shared().localProfileName() + // TODO: Profile picture & profile key + LokiPublicChatManager.shared.addChat(server: urlAsString, channel: channelID) + .done(on: .main) { [weak self] _ in + let _ = LokiPublicChatAPI.getMessages(for: channelID, on: urlAsString) + let _ = LokiPublicChatAPI.setDisplayName(to: displayName, on: urlAsString) + self?.presentingViewController!.dismiss(animated: true, completion: nil) + } + .catch(on: .main) { [weak self] _ in + self?.isJoining = false + self?.showError(title: NSLocalizedString("Couldn't Join", comment: "")) + } + } + + // MARK: Convenience + private func showError(title: String, message: String = "") { + let alert = UIAlertController(title: title, message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil)) + presentAlert(alert) + } +} + +private final class EnterChatURLVC : UIViewController { + weak var joinPublicChatVC: JoinPublicChatVC! + private var bottomConstraint: NSLayoutConstraint! + + // MARK: Components + private lazy var chatURLTextField: TextField = { + let result = TextField(placeholder: "https://chat.lokinet.org") + result.keyboardType = .URL + result.autocapitalizationType = .none + return result + }() + + // MARK: Lifecycle + override func viewDidLoad() { + // Remove background color + view.backgroundColor = .clear + // Set up explanation label + let explanationLabel = UILabel() + explanationLabel.textColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity) + explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) + explanationLabel.text = NSLocalizedString("Enter the URL of the public chat you'd like to join", comment: "") + explanationLabel.numberOfLines = 0 + explanationLabel.textAlignment = .center + explanationLabel.lineBreakMode = .byWordWrapping + // Next button + let nextButton = Button(style: .prominent) + nextButton.setTitle(NSLocalizedString("Next", comment: ""), for: UIControl.State.normal) + nextButton.addTarget(self, action: #selector(joinPublicChatIfPossible), for: UIControl.Event.touchUpInside) + let nextButtonContainer = UIView() + nextButtonContainer.addSubview(nextButton) + nextButton.pin(.leading, to: .leading, of: nextButtonContainer, withInset: 80) + nextButton.pin(.top, to: .top, of: nextButtonContainer) + nextButtonContainer.pin(.trailing, to: .trailing, of: nextButton, withInset: 80) + nextButtonContainer.pin(.bottom, to: .bottom, of: nextButton) + // Stack view + let stackView = UIStackView(arrangedSubviews: [ chatURLTextField, UIView.spacer(withHeight: Values.smallSpacing), explanationLabel, UIView.vStretchingSpacer(), nextButtonContainer ]) + stackView.axis = .vertical + stackView.alignment = .fill + stackView.layoutMargins = UIEdgeInsets(top: Values.mediumSpacing, left: Values.largeSpacing, bottom: Values.mediumSpacing, right: Values.largeSpacing) + stackView.isLayoutMarginsRelativeArrangement = true + view.addSubview(stackView) + stackView.pin(.leading, to: .leading, of: view) + stackView.pin(.top, to: .top, of: view) + view.pin(.trailing, to: .trailing, of: stackView) + bottomConstraint = view.pin(.bottom, to: .bottom, of: stackView) + // Set up width constraint + view.set(.width, to: UIScreen.main.bounds.width) + // Dismiss keyboard on tap + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard)) + view.addGestureRecognizer(tapGestureRecognizer) + // Listen to keyboard notifications + let notificationCenter = NotificationCenter.default + notificationCenter.addObserver(self, selector: #selector(handleKeyboardWillChangeFrameNotification(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) + notificationCenter.addObserver(self, selector: #selector(handleKeyboardWillHideNotification(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + // MARK: General + func constrainHeight(to height: CGFloat) { + view.set(.height, to: height) + } + + @objc private func dismissKeyboard() { + chatURLTextField.resignFirstResponder() + } + + // MARK: Updating + @objc private func handleKeyboardWillChangeFrameNotification(_ notification: Notification) { + guard let newHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size.height else { return } + bottomConstraint.constant = newHeight + UIView.animate(withDuration: 0.25) { + self.view.layoutIfNeeded() + } + } + + @objc private func handleKeyboardWillHideNotification(_ notification: Notification) { + bottomConstraint.constant = 0 + UIView.animate(withDuration: 0.25) { + self.view.layoutIfNeeded() + } + } + + // MARK: Interaction + @objc private func joinPublicChatIfPossible() { + let chatURL = chatURLTextField.text?.trimmingCharacters(in: .whitespaces) ?? "" + joinPublicChatVC.joinPublicChatIfPossible(with: chatURL) + } +} + +private final class ScanQRCodePlaceholderVC : UIViewController { + weak var joinPublicChatVC: JoinPublicChatVC! + + override func viewDidLoad() { + // Remove background color + view.backgroundColor = .clear + // Set up explanation label + let explanationLabel = UILabel() + explanationLabel.textColor = Colors.text + explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) + explanationLabel.text = NSLocalizedString("Loki Messenger needs camera access to scan QR codes", comment: "") + explanationLabel.numberOfLines = 0 + explanationLabel.textAlignment = .center + explanationLabel.lineBreakMode = .byWordWrapping + // Set up call to action button + let callToActionButton = UIButton() + callToActionButton.titleLabel!.font = .boldSystemFont(ofSize: Values.mediumFontSize) + callToActionButton.setTitleColor(Colors.accent, for: UIControl.State.normal) + callToActionButton.setTitle(NSLocalizedString("Enable Camera Access", comment: ""), for: UIControl.State.normal) + callToActionButton.addTarget(self, action: #selector(requestCameraAccess), for: UIControl.Event.touchUpInside) + // Set up stack view + let stackView = UIStackView(arrangedSubviews: [ explanationLabel, callToActionButton ]) + stackView.axis = .vertical + stackView.spacing = Values.mediumSpacing + stackView.alignment = .center + // Set up constraints + view.set(.width, to: UIScreen.main.bounds.width) + view.addSubview(stackView) + stackView.pin(.leading, to: .leading, of: view, withInset: Values.massiveSpacing) + view.pin(.trailing, to: .trailing, of: stackView, withInset: Values.massiveSpacing) + let verticalCenteringConstraint = stackView.center(.vertical, in: view) + verticalCenteringConstraint.constant = -16 // Makes things appear centered visually + } + + func constrainHeight(to height: CGFloat) { + view.set(.height, to: height) + } + + @objc private func requestCameraAccess() { + ows_ask(forCameraPermissions: { [weak self] hasCameraAccess in + if hasCameraAccess { + self?.joinPublicChatVC.handleCameraAccessGranted() + } else { + // Do nothing + } + }) + } +} diff --git a/Signal/src/Loki/Redesign/View Controllers/NewPrivateChatVC.swift b/Signal/src/Loki/Redesign/View Controllers/NewPrivateChatVC.swift index 9d397b1ff..0ffd22357 100644 --- a/Signal/src/Loki/Redesign/View Controllers/NewPrivateChatVC.swift +++ b/Signal/src/Loki/Redesign/View Controllers/NewPrivateChatVC.swift @@ -7,11 +7,11 @@ final class NewPrivateChatVC : UIViewController, UIPageViewControllerDataSource, // MARK: Components private lazy var tabBar: TabBar = { let tabs = [ - TabBar.Tab(title: "Enter Public Key") { [weak self] in + TabBar.Tab(title: NSLocalizedString("Enter Public Key", comment: "")) { [weak self] in guard let self = self else { return } self.pageVC.setViewControllers([ self.pages[0] ], direction: .forward, animated: false, completion: nil) }, - TabBar.Tab(title: "Scan QR Code") { [weak self] in + TabBar.Tab(title: NSLocalizedString("Scan QR Code", comment: "")) { [weak self] in guard let self = self else { return } self.pageVC.setViewControllers([ self.pages[1] ], direction: .forward, animated: false, completion: nil) } @@ -32,7 +32,7 @@ final class NewPrivateChatVC : UIViewController, UIPageViewControllerDataSource, }() private lazy var scanQRCodeWrapperVC: ScanQRCodeWrapperVC = { - let message = NSLocalizedString("Scan the QR code of the person you'd like to securely message. They can find their QR code by going into Loki Messenger's in-app settings and tapping \"Show QR Code\".", comment: "") + let message = NSLocalizedString("Users can share their QR code by going into their account settings and tapping \"Share QR Code\".", comment: "") let result = ScanQRCodeWrapperVC(message: message) result.delegate = self return result @@ -137,7 +137,7 @@ final class NewPrivateChatVC : UIViewController, UIPageViewControllerDataSource, } private final class EnterPublicKeyVC : UIViewController { - var newPrivateChatVC: NewPrivateChatVC? + weak var newPrivateChatVC: NewPrivateChatVC! private lazy var userHexEncodedPublicKey: String = { let userDefaults = UserDefaults.standard @@ -200,7 +200,7 @@ private final class EnterPublicKeyVC : UIViewController { nextButtonContainer.pin(.trailing, to: .trailing, of: nextButton, withInset: 80) nextButtonContainer.pin(.bottom, to: .bottom, of: nextButton) // Stack view - let stackView = UIStackView(arrangedSubviews: [ publicKeyTextField, UIView.spacer(withHeight: Values.smallSpacing), explanationLabel, UIView.spacer(withHeight: Values.veryLargeSpacing), separator, UIView.spacer(withHeight: Values.veryLargeSpacing), userPublicKeyLabel, UIView.spacer(withHeight: Values.veryLargeSpacing), buttonContainer, UIView.spacer(withHeight: Values.veryLargeSpacing), nextButtonContainer, UIView.vStretchingSpacer() ]) + let stackView = UIStackView(arrangedSubviews: [ publicKeyTextField, UIView.spacer(withHeight: Values.smallSpacing), explanationLabel, UIView.vStretchingSpacer(), separator, UIView.spacer(withHeight: Values.veryLargeSpacing), userPublicKeyLabel, UIView.spacer(withHeight: Values.veryLargeSpacing), buttonContainer, UIView.spacer(withHeight: Values.veryLargeSpacing), nextButtonContainer ]) stackView.axis = .vertical stackView.alignment = .fill stackView.layoutMargins = UIEdgeInsets(top: Values.mediumSpacing, left: Values.largeSpacing, bottom: Values.mediumSpacing, right: Values.largeSpacing) @@ -214,11 +214,6 @@ private final class EnterPublicKeyVC : UIViewController { view.addGestureRecognizer(tapGestureRecognizer) } - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - publicKeyTextField.becomeFirstResponder() - } - // MARK: General func constrainHeight(to height: CGFloat) { view.set(.height, to: height) @@ -247,17 +242,17 @@ private final class EnterPublicKeyVC : UIViewController { @objc private func sharePublicKey() { let shareVC = UIActivityViewController(activityItems: [ userHexEncodedPublicKey ], applicationActivities: nil) - newPrivateChatVC!.navigationController!.present(shareVC, animated: true, completion: nil) + newPrivateChatVC.navigationController!.present(shareVC, animated: true, completion: nil) } @objc private func startNewPrivateChatIfPossible() { let hexEncodedPublicKey = publicKeyTextField.text?.trimmingCharacters(in: .whitespaces) ?? "" - newPrivateChatVC!.startNewPrivateChatIfPossible(with: hexEncodedPublicKey) + newPrivateChatVC.startNewPrivateChatIfPossible(with: hexEncodedPublicKey) } } private final class ScanQRCodePlaceholderVC : UIViewController { - var newPrivateChatVC: NewPrivateChatVC? + weak var newPrivateChatVC: NewPrivateChatVC! override func viewDidLoad() { // Remove background color @@ -297,7 +292,7 @@ private final class ScanQRCodePlaceholderVC : UIViewController { @objc private func requestCameraAccess() { ows_ask(forCameraPermissions: { [weak self] hasCameraAccess in if hasCameraAccess { - self?.newPrivateChatVC!.handleCameraAccessGranted() + self?.newPrivateChatVC.handleCameraAccessGranted() } else { // Do nothing } diff --git a/Signal/src/Loki/Messaging/ScanQRCodeWrapperVC.swift b/Signal/src/Loki/Redesign/View Controllers/ScanQRCodeWrapperVC.swift similarity index 100% rename from Signal/src/Loki/Messaging/ScanQRCodeWrapperVC.swift rename to Signal/src/Loki/Redesign/View Controllers/ScanQRCodeWrapperVC.swift diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index 441959082..14404235d 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -869,9 +869,9 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) { - (void)showNewPublicChatVC { - LKJoinPublicChatVC *joinPublicChatVC = [LKJoinPublicChatVC new]; - OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:joinPublicChatVC]; - [self.navigationController presentViewController:navigationController animated:YES completion:nil]; +// LKJoinPublicChatVC *joinPublicChatVC = [LKJoinPublicChatVC new]; +// OWSNavigationController *navigationController = [[OWSNavigationController alloc] initWithRootViewController:joinPublicChatVC]; +// [self.navigationController presentViewController:navigationController animated:YES completion:nil]; } - (void)viewWillAppear:(BOOL)animated diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index ee8673722..1968a580b 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2681,6 +2681,7 @@ "New Conversation" = "New Conversation"; "Enter public key of recipient" = "Enter public key of recipient"; "Users can share their public key by going into their account settings and tapping \"Share Public Key\", or by sharing their QR code." = "Users can share their public key by going into their account settings and tapping \"Share Public Key\", or by sharing their QR code."; +"Users can share their QR code by going into their account settings and tapping \"Share QR Code\"." = "Users can share their QR code by going into their account settings and tapping \"Share QR Code\"."; "Your Public Key" = "Your Public Key"; "Copy" = "Copy"; "Copied" = "Copied"; @@ -2689,3 +2690,12 @@ "Loki Messenger needs camera access to scan QR codes" = "Loki Messenger needs camera access to scan QR codes"; "Enable Camera Access" = "Enable Camera Access"; "Scan the QR code of the person you'd like to securely message. They can find their QR code by going into Loki Messenger's in-app settings and tapping \"Show QR Code\"." = "Scan the QR code of the person you'd like to securely message. They can find their QR code by going into Loki Messenger's in-app settings and tapping \"Show QR Code\"."; +"Enter Public Key" = "Enter Public Key"; +"Enter Chat URL" = "Enter Chat URL"; +"Scan QR Code" = "Scan QR Code"; +"Scan the QR code of the public chat you'd like to join" = "Scan the QR code of the public chat you'd like to join"; +"Join Public Chat" = "Join Public Chat"; +"Enter the URL of the public chat you'd like to join" = "Enter the URL of the public chat you'd like to join"; +"Invalid URL" = "Invalid URL"; +"Please check the URL you entered and try again" = "Please check the URL you entered and try again"; +"Couldn't Join" = "Couldn't Join";