From c985d3b3610708bfe1bce9151d865f3c69c62067 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO <> Date: Mon, 29 Jan 2024 14:24:15 +1100 Subject: [PATCH] clean up --- Session.xcodeproj/project.pbxproj | 8 - Session/Home/HomeVC.swift | 5 +- .../New Conversation/NewConversationVC.swift | 352 --------- Session/Home/New Conversation/NewDMVC.swift | 737 ------------------ .../New Conversation/NewMessageScreen.swift | 6 +- .../StartConversationScreen.swift | 6 +- 6 files changed, 12 insertions(+), 1102 deletions(-) delete mode 100644 Session/Home/New Conversation/NewConversationVC.swift delete mode 100644 Session/Home/New Conversation/NewDMVC.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 496f539e2..c243a4eef 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -127,7 +127,6 @@ 7B87EF4A2A92DFB4002A0E8F /* LoadAccountScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B87EF492A92DFB4002A0E8F /* LoadAccountScreen.swift */; }; 7B87EF4C2A933355002A0E8F /* LoadingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B87EF4B2A933355002A0E8F /* LoadingScreen.swift */; }; 7B8914772A7CAAE200A4C627 /* SessionHostingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8914762A7CAAE200A4C627 /* SessionHostingViewController.swift */; }; - 7B8C44C528B49DDA00FBE25F /* NewConversationVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8C44C428B49DDA00FBE25F /* NewConversationVC.swift */; }; 7B8D5FC428332600008324D9 /* VisibleMessage+Reaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */; }; 7B93D07127CF194000811CB6 /* MessageRequestResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D06F27CF194000811CB6 /* MessageRequestResponse.swift */; }; 7B93D07727CF1A8A00811CB6 /* MockDataGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B93D07527CF1A8900811CB6 /* MockDataGenerator.swift */; }; @@ -238,7 +237,6 @@ B8BF43BA26CC95FB007828D1 /* WebRTC+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8BF43B926CC95FB007828D1 /* WebRTC+Utilities.swift */; }; B8C2B2C82563685C00551B4D /* CircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8C2B2C72563685C00551B4D /* CircleView.swift */; }; B8CCF6352396005F0091D419 /* SpaceMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B8CCF6342396005F0091D419 /* SpaceMono-Regular.ttf */; }; - B8CCF63723961D6D0091D419 /* NewDMVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF63623961D6D0091D419 /* NewDMVC.swift */; }; B8CCF63F23975CFB0091D419 /* JoinOpenGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8CCF63E23975CFB0091D419 /* JoinOpenGroupVC.swift */; }; B8D07405265C683300F77E07 /* ElegantIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B8D07404265C683300F77E07 /* ElegantIcons.ttf */; }; B8D07406265C683A00F77E07 /* ElegantIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B8D07404265C683300F77E07 /* ElegantIcons.ttf */; }; @@ -1258,7 +1256,6 @@ 7B87EF492A92DFB4002A0E8F /* LoadAccountScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadAccountScreen.swift; sourceTree = ""; }; 7B87EF4B2A933355002A0E8F /* LoadingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingScreen.swift; sourceTree = ""; }; 7B8914762A7CAAE200A4C627 /* SessionHostingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionHostingViewController.swift; sourceTree = ""; }; - 7B8C44C428B49DDA00FBE25F /* NewConversationVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationVC.swift; sourceTree = ""; }; 7B8D5FC328332600008324D9 /* VisibleMessage+Reaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+Reaction.swift"; sourceTree = ""; }; 7B93D06F27CF194000811CB6 /* MessageRequestResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageRequestResponse.swift; sourceTree = ""; }; 7B93D07527CF1A8900811CB6 /* MockDataGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockDataGenerator.swift; sourceTree = ""; }; @@ -1385,7 +1382,6 @@ B8BF43B926CC95FB007828D1 /* WebRTC+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTC+Utilities.swift"; sourceTree = ""; }; B8C2B2C72563685C00551B4D /* CircleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleView.swift; sourceTree = ""; }; B8CCF6342396005F0091D419 /* SpaceMono-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpaceMono-Regular.ttf"; sourceTree = ""; }; - B8CCF63623961D6D0091D419 /* NewDMVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewDMVC.swift; sourceTree = ""; }; B8CCF638239721E20091D419 /* TabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBar.swift; sourceTree = ""; }; B8CCF63E23975CFB0091D419 /* JoinOpenGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JoinOpenGroupVC.swift; sourceTree = ""; }; B8D07404265C683300F77E07 /* ElegantIcons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = ElegantIcons.ttf; sourceTree = ""; }; @@ -2420,8 +2416,6 @@ 7B8C44C328B49DA900FBE25F /* New Conversation */ = { isa = PBXGroup; children = ( - B8CCF63623961D6D0091D419 /* NewDMVC.swift */, - 7B8C44C428B49DDA00FBE25F /* NewConversationVC.swift */, 7BB92B3E28C825FD0082762F /* NewConversationViewModel.swift */, 946B344C2B5F67B4004CB4A3 /* StartConversationScreen.swift */, 946B344E2B61D80B004CB4A3 /* InviteAFriendScreen.swift */, @@ -6174,7 +6168,6 @@ FDF2220B2818F38D000A4995 /* SessionApp.swift in Sources */, FD7115EB28C5D78E00B47552 /* ThreadSettingsViewModel.swift in Sources */, B8041AA725C90927003C2166 /* TypingIndicatorCell.swift in Sources */, - B8CCF63723961D6D0091D419 /* NewDMVC.swift in Sources */, FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */, 4C1885D2218F8E1C00B67051 /* PhotoGridViewCell.swift in Sources */, 34D1F0501F7D45A60066283D /* GifPickerCell.swift in Sources */, @@ -6218,7 +6211,6 @@ 34A8B3512190A40E00218A25 /* MediaAlbumView.swift in Sources */, FD09C5E828264937000CE219 /* MediaDetailViewController.swift in Sources */, 3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */, - 7B8C44C528B49DDA00FBE25F /* NewConversationVC.swift in Sources */, 7B1B52E028580D51006069F2 /* EmojiSkinTonePicker.swift in Sources */, B849789625D4A2F500D0D0B3 /* LinkPreviewView.swift in Sources */, FD71164428E2CB8A00B47552 /* SessionCell+Accessory.swift in Sources */, diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index bc0de2395..faecee9a5 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -884,8 +884,9 @@ final class HomeVC: BaseVC, SessionUtilRespondingViewController, UITableViewData } func createNewDMFromDeepLink(sessionId: String) { - let newDMVC = NewDMVC(sessionId: sessionId, shouldShowBackButton: false) - let navigationController = StyledNavigationController(rootViewController: newDMVC) + let viewController: SessionHostingViewController = SessionHostingViewController(rootView: NewMessageScreen(accountId: sessionId)) + viewController.setNavBarTitle("vc_create_private_chat_title".localized()) + let navigationController = StyledNavigationController(rootViewController: viewController) if UIDevice.current.isIPad { navigationController.modalPresentationStyle = .fullScreen } diff --git a/Session/Home/New Conversation/NewConversationVC.swift b/Session/Home/New Conversation/NewConversationVC.swift deleted file mode 100644 index 34b78e82f..000000000 --- a/Session/Home/New Conversation/NewConversationVC.swift +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import UIKit -import GRDB -import SessionUIKit -import SessionMessagingKit -import SignalUtilitiesKit - -final class NewConversationVC: BaseVC, ThemedNavigation, UITableViewDelegate, UITableViewDataSource { - private let newConversationViewModel = NewConversationViewModel() - private var groupedContacts: OrderedDictionary = OrderedDictionary() - - // MARK: - UI - - var navigationBackground: ThemeValue { .newConversation_background } - - private lazy var newDMButton: NewConversationButton = { - let result = NewConversationButton(icon: #imageLiteral(resourceName: "Message"), title: "vc_create_private_chat_title".localized()) - result.accessibilityIdentifier = "New direct message" - result.isAccessibilityElement = true - - return result - }() - - private lazy var newGroupButton: NewConversationButton = { - let result = NewConversationButton(icon: #imageLiteral(resourceName: "Group"), title: "vc_create_closed_group_title".localized()) - result.accessibilityLabel = "Create group" - result.isAccessibilityElement = true - - return result - }() - private lazy var joinCommunityButton: NewConversationButton = NewConversationButton(icon: #imageLiteral(resourceName: "Globe"), title: "vc_join_public_chat_title".localized(), shouldShowSeparator: false) - - private lazy var buttonStackView: UIStackView = { - let lineTop: UIView = UIView() - lineTop.themeBackgroundColor = .borderSeparator - lineTop.set(.height, to: Values.separatorThickness) - - let lineBottom = UIView() - lineBottom.themeBackgroundColor = .borderSeparator - lineBottom.set(.height, to: Values.separatorThickness) - - let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - tapGestureRecognizer.numberOfTapsRequired = 1 - - let result = UIStackView( - arrangedSubviews: [ - lineTop, - newDMButton, - newGroupButton, - joinCommunityButton, - lineBottom - ] - ) - result.axis = .vertical - result.addGestureRecognizer(tapGestureRecognizer) - - return result - }() - - private lazy var buttonStackViewContainer = UIView(wrapping: buttonStackView, withInsets: .zero) - - private lazy var contactsTitleLabel: UILabel = { - let result: UILabel = UILabel() - result.font = .systemFont(ofSize: Values.mediumFontSize) - result.text = (newConversationViewModel.sectionData.isEmpty ? - "vc_create_closed_group_empty_state_message".localized() : - "NEW_CONVERSATION_CONTACTS_SECTION_TITLE".localized() - ) - result.themeTextColor = (newConversationViewModel.sectionData.isEmpty ? - .textSecondary : - .textPrimary - ) - - return result - }() - - private lazy var contactsTableView: UITableView = { - let result: UITableView = UITableView() - result.delegate = self - result.dataSource = self - result.separatorStyle = .none - result.themeBackgroundColor = .newConversation_background - result.register(view: SessionCell.self) - - if #available(iOS 15.0, *) { - result.sectionHeaderTopPadding = 0 - } - - return result - }() - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - setNavBarTitle("vc_new_conversation_title".localized()) - view.themeBackgroundColor = .newConversation_background - - // Set up navigation bar buttons - let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close)) - closeButton.themeTintColor = .textPrimary - navigationItem.leftBarButtonItem = closeButton - setUpViewHierarchy() - } - - private func setUpViewHierarchy() { - buttonStackViewContainer.themeBackgroundColor = .newConversation_background - - let headerView = UIView( - frame: CGRect( - x: 0, y: 0, - width: UIScreen.main.bounds.width, - height: NewConversationButton.height * 3 + Values.mediumSpacing * 2 + Values.mediumFontSize - ) - ) - headerView.addSubview(buttonStackViewContainer) - buttonStackViewContainer.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing], to: headerView) - buttonStackViewContainer.pin(.top, to: .top, of: headerView, withInset: Values.verySmallSpacing) - headerView.addSubview(contactsTitleLabel) - contactsTitleLabel.pin(.leading, to: .leading, of: headerView, withInset: Values.mediumSpacing) - contactsTitleLabel.pin(.top, to: .bottom, of: buttonStackViewContainer, withInset: Values.mediumSpacing) - - contactsTableView.tableHeaderView = headerView - view.addSubview(contactsTableView) - contactsTableView.pin(to: view) - } - - // MARK: - UITableViewDataSource - - func numberOfSections(in tableView: UITableView) -> Int { - return newConversationViewModel.sectionData.count - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return newConversationViewModel.sectionData[section].contacts.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell: SessionCell = tableView.dequeue(type: SessionCell.self, for: indexPath) - let profile = newConversationViewModel.sectionData[indexPath.section].contacts[indexPath.row] - cell.update( - with: SessionCell.Info( - id: profile, - position: Position.with( - indexPath.row, - count: newConversationViewModel.sectionData[indexPath.section].contacts.count - ), - leftAccessory: .profile(id: profile.id, profile: profile), - title: profile.displayName(), - styling: SessionCell.StyleInfo(backgroundStyle: .edgeToEdge) - ) - ) - - return cell - } - - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - let label: UILabel = UILabel() - label.font = .systemFont(ofSize: Values.smallFontSize) - label.text = newConversationViewModel.sectionData[section].sectionName - label.themeTextColor = .textPrimary - - let headerView: UIView = UIView() - headerView.themeBackgroundColor = .newConversation_background - headerView.addSubview(label) - - label.pin(.leading, to: .leading, of: headerView, withInset: Values.mediumSpacing) - label.pin(.top, to: .top, of: headerView, withInset: Values.verySmallSpacing) - label.pin(.bottom, to: .bottom, of: headerView, withInset: -Values.verySmallSpacing) - - return headerView - } - - // MARK: - UITableViewDelegate - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - - let sessionId = newConversationViewModel.sectionData[indexPath.section].contacts[indexPath.row].id - - SessionApp.presentConversationCreatingIfNeeded( - for: sessionId, - variant: .contact, - dismissing: navigationController, - animated: false - ) - } - - func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) { - view.themeBackgroundColor = .newConversation_background - } - - // MARK: - Interaction - - @objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { - let location = gestureRecognizer.location(in: self.view) - - if newDMButton.frame.contains(location) { - createNewDM() - } - else if newGroupButton.frame.contains(location) { - createClosedGroup() - } - else if joinCommunityButton.frame.contains(location) { - joinOpenGroup() - } - } - - @objc private func close() { - dismiss(animated: true, completion: nil) - } - - @objc func createNewDM() { - let newDMVC = NewDMVC() - self.navigationController?.pushViewController(newDMVC, animated: true) - } - - @objc func createClosedGroup() { - let newClosedGroupVC = NewClosedGroupVC() - self.navigationController?.pushViewController(newClosedGroupVC, animated: true) - } - - @objc func joinOpenGroup() { - let joinOpenGroupVC: JoinOpenGroupVC = JoinOpenGroupVC() - self.navigationController?.pushViewController(joinOpenGroupVC, animated: true) - } -} - -// MARK: - NewConversationButton - -private final class NewConversationButton: UIView { - private let icon: UIImage - private let title: String - private let shouldShowSeparator: Bool - private var didTouchDownInside: Bool = false - - public static let height: CGFloat = 56 - private static let iconSize: CGFloat = 38 - - private let selectedBackgroundView: UIView = { - let result: UIView = UIView() - result.themeBackgroundColor = .highlighted(.settings_tabBackground) - result.isHidden = true - - return result - }() - - init(icon: UIImage, title: String, shouldShowSeparator: Bool = true) { - self.icon = icon.withRenderingMode(.alwaysTemplate) - self.title = title - self.shouldShowSeparator = shouldShowSeparator - - super.init(frame: .zero) - - self.themeBackgroundColor = .settings_tabBackground - setUpViewHierarchy() - } - - override init(frame: CGRect) { - preconditionFailure("Use init(icon:title:) instead.") - } - - required init?(coder: NSCoder) { - preconditionFailure("Use init(icon:title:) instead.") - } - - private func setUpViewHierarchy() { - addSubview(selectedBackgroundView) - selectedBackgroundView.pin(to: self) - - let iconImageView = UIImageView(image: self.icon) - iconImageView.contentMode = .center - iconImageView.themeTintColor = .textPrimary - iconImageView.set(.width, to: NewConversationButton.iconSize) - - let titleLable: UILabel = UILabel() - titleLable.font = .systemFont(ofSize: Values.mediumFontSize) - titleLable.text = self.title - titleLable.themeTextColor = .textPrimary - - let stackView = UIStackView( - arrangedSubviews: [ - iconImageView, - UIView.hSpacer(Values.mediumSpacing), - titleLable, - UIView.hStretchingSpacer() - ] - ) - stackView.axis = .horizontal - stackView.alignment = .center - stackView.isLayoutMarginsRelativeArrangement = true - stackView.layoutMargins = UIEdgeInsets(uniform: Values.mediumSpacing) - addSubview(stackView) - stackView.pin(to: self) - stackView.set(.width, to: UIScreen.main.bounds.width) - stackView.set(.height, to: NewConversationButton.height) - - let line: UIView = UIView() - line.themeBackgroundColor = .borderSeparator - addSubview(line) - - line.pin([UIView.VerticalEdge.bottom, UIView.HorizontalEdge.trailing], to: self) - line.pin( - .leading, - to: .leading, - of: self, - withInset: (NewConversationButton.iconSize + 2 * Values.mediumSpacing) - ) - line.set(.height, to: Values.separatorThickness) - - line.isHidden = !shouldShowSeparator - } - - // MARK: - Interaction - - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - guard - isUserInteractionEnabled, - let location: CGPoint = touches.first?.location(in: self), - bounds.contains(location) - else { return } - - didTouchDownInside = true - selectedBackgroundView.isHidden = false - } - - override func touchesMoved(_ touches: Set, with event: UIEvent?) { - guard - isUserInteractionEnabled, - let location: CGPoint = touches.first?.location(in: self), - bounds.contains(location), - didTouchDownInside - else { - selectedBackgroundView.isHidden = true - return - } - - selectedBackgroundView.isHidden = false - } - - override func touchesEnded(_ touches: Set, with event: UIEvent?) { - selectedBackgroundView.isHidden = true - didTouchDownInside = false - } - - override func touchesCancelled(_ touches: Set, with event: UIEvent?) { - selectedBackgroundView.isHidden = true - didTouchDownInside = false - } -} diff --git a/Session/Home/New Conversation/NewDMVC.swift b/Session/Home/New Conversation/NewDMVC.swift deleted file mode 100644 index 850c2ae8b..000000000 --- a/Session/Home/New Conversation/NewDMVC.swift +++ /dev/null @@ -1,737 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import UIKit -import AVFoundation -import GRDB -import SessionUIKit -import SessionMessagingKit -import SessionUtilitiesKit -import SignalUtilitiesKit -import SessionSnodeKit - -final class NewDMVC: BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, QRScannerDelegate { - private var shouldShowBackButton: Bool = true - private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) - private var pages: [UIViewController] = [] - private var targetVCIndex: Int? - - // MARK: - Components - - private lazy var tabBar: TabBar = { - let tabs = [ - TabBar.Tab(title: "vc_create_private_chat_enter_session_id_tab_title".localized()) { [weak self] in - guard let self = self else { return } - self.pageVC.setViewControllers([ self.pages[0] ], direction: .forward, animated: false, completion: nil) - }, - TabBar.Tab(title: "vc_create_private_chat_scan_qr_code_tab_title".localized()) { [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 enterPublicKeyVC: EnterPublicKeyVC = { - let result = EnterPublicKeyVC() - result.NewDMVC = self - - return result - }() - - private lazy var scanQRCodePlaceholderVC: ScanQRCodePlaceholderVC = { - let result: ScanQRCodePlaceholderVC = ScanQRCodePlaceholderVC() - result.newDMVC = self - - return result - }() - - private lazy var scanQRCodeWrapperVC: ScanQRCodeWrapperVC = { - let result: ScanQRCodeWrapperVC = ScanQRCodeWrapperVC(message: nil) - result.delegate = self - - return result - }() - - // MARK: - Initialization - - init(sessionId: String? = nil, shouldShowBackButton: Bool = true) { - self.shouldShowBackButton = shouldShowBackButton - - super.init(nibName: nil, bundle: nil) - - if let sessionId: String = sessionId { - enterPublicKeyVC.setSessionId(to: sessionId) - } - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - } - - override init(nibName: String?, bundle: Bundle?) { - super.init(nibName: nibName, bundle: bundle) - } - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - - setNavBarTitle("vc_create_private_chat_title".localized()) - view.themeBackgroundColor = .newConversation_background - - // Set up navigation bar buttons - let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close)) - closeButton.themeTintColor = .textPrimary - - if shouldShowBackButton { - navigationItem.rightBarButtonItem = closeButton - } - else { - navigationItem.leftBarButtonItem = closeButton - } - - // Page VC - let hasCameraAccess = (AVCaptureDevice.authorizationStatus(for: .video) == .authorized) - pages = [ enterPublicKeyVC, (hasCameraAccess ? scanQRCodeWrapperVC : scanQRCodePlaceholderVC) ] - pageVC.dataSource = self - pageVC.delegate = self - pageVC.setViewControllers([ enterPublicKeyVC ], direction: .forward, animated: false, completion: nil) - - // Tab bar - view.addSubview(tabBar) - tabBar.pin(.top, to: .top, of: view) - tabBar.pin(.leading, to: .leading, of: view) - tabBar.pin(.trailing, to: .trailing, of: view) - - // Page VC constraints - let pageVCView = pageVC.view! - view.addSubview(pageVCView) - pageVCView.pin(.leading, to: .leading, of: view) - pageVCView.pin(.top, to: .bottom, of: tabBar) - pageVCView.pin(.trailing, to: .trailing, of: view) - pageVCView.pin(.bottom, to: .bottom, of: view) - - let navBarHeight: CGFloat = (navigationController?.navigationBar.frame.size.height ?? 0) - let statusBarHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height - let height: CGFloat = ((navigationController?.view.bounds.height ?? 0) - navBarHeight - TabBar.snHeight - statusBarHeight) - let size: CGSize = CGSize(width: UIScreen.main.bounds.width, height: height) - enterPublicKeyVC.constrainSize(to: size) - scanQRCodePlaceholderVC.constrainSize(to: size) - } - - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransition(to: size, with: coordinator) - let height: CGFloat = (size.height - TabBar.snHeight) - let size: CGSize = CGSize(width: size.width, height: height) - enterPublicKeyVC.constrainSize(to: size) - scanQRCodePlaceholderVC.constrainSize(to: size) - } - - // 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() { - DispatchQueue.main.async { - self.pages[1] = self.scanQRCodeWrapperVC - self.pageVC.setViewControllers([ self.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: QRCodeScanningViewController, didDetectQRCodeWith string: String, onError: (() -> ())?) { - let hexEncodedPublicKey = string - startNewDMIfPossible(with: hexEncodedPublicKey, onError: onError) - } - - fileprivate func startNewDMIfPossible(with onsNameOrPublicKey: String, onError: (() -> ())?) { - let maybeSessionId: SessionId? = SessionId(from: onsNameOrPublicKey) - - if KeyPair.isValidHexEncodedPublicKey(candidate: onsNameOrPublicKey) { - switch maybeSessionId?.prefix { - case .standard: - startNewDM(with: onsNameOrPublicKey) - - case .blinded15, .blinded25: - let modal: ConfirmationModal = ConfirmationModal( - targetView: self.view, - info: ConfirmationModal.Info( - title: "ALERT_ERROR_TITLE".localized(), - body: .text("DM_ERROR_DIRECT_BLINDED_ID".localized()), - cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text, - afterClosed: onError - ) - ) - self.present(modal, animated: true) - - default: - let modal: ConfirmationModal = ConfirmationModal( - targetView: self.view, - info: ConfirmationModal.Info( - title: "ALERT_ERROR_TITLE".localized(), - body: .text("DM_ERROR_INVALID".localized()), - cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text, - afterClosed: onError - ) - ) - self.present(modal, animated: true) - } - return - } - - // This could be an ONS name - ModalActivityIndicatorViewController - .present(fromViewController: navigationController!, canCancel: false) { [weak self] modalActivityIndicator in - SnodeAPI - .getSessionID(for: onsNameOrPublicKey) - .subscribe(on: DispatchQueue.global(qos: .userInitiated)) - .receive(on: DispatchQueue.main) - .sinkUntilComplete( - receiveCompletion: { result in - switch result { - case .finished: break - case .failure(let error): - modalActivityIndicator.dismiss { - var messageOrNil: String? - if let error = error as? SnodeAPIError { - switch error { - case .decryptionFailed, .hashingFailed, .validationFailed: - messageOrNil = error.errorDescription - default: break - } - } - let message: String = { - if let messageOrNil: String = messageOrNil { - return messageOrNil - } - - return (maybeSessionId?.prefix == .blinded15 || maybeSessionId?.prefix == .blinded25 ? - "DM_ERROR_DIRECT_BLINDED_ID".localized() : - "DM_ERROR_INVALID".localized() - ) - }() - - let modal: ConfirmationModal = ConfirmationModal( - targetView: self?.view, - info: ConfirmationModal.Info( - title: "ALERT_ERROR_TITLE".localized(), - body: .text(message), - cancelTitle: "BUTTON_OK".localized(), - cancelStyle: .alert_text, - afterClosed: onError - ) - ) - self?.present(modal, animated: true) - } - } - }, - receiveValue: { sessionId in - modalActivityIndicator.dismiss { - self?.startNewDM(with: sessionId) - } - } - ) - } - } - - private func startNewDM(with sessionId: String) { - SessionApp.presentConversationCreatingIfNeeded( - for: sessionId, - variant: .contact, - dismissing: presentingViewController, - animated: false - ) - } -} - -// MARK: - EnterPublicKeyVC - -private final class EnterPublicKeyVC: UIViewController { - weak var NewDMVC: NewDMVC! - private var isKeyboardShowing = false - private var simulatorWillResignFirstResponder = false - private var bottomConstraint: NSLayoutConstraint! - private let bottomMargin: CGFloat = UIDevice.current.isIPad ? Values.largeSpacing : 0 - - // MARK: - Components - - private lazy var publicKeyTextView: TextView = { - let result = TextView(placeholder: "vc_enter_public_key_text_field_hint".localized()) { [weak self] text in - self?.nextButton.isEnabled = !text.isEmpty - } - result.accessibilityLabel = "Session id input box" - result.autocapitalizationType = .none - - return result - }() - - private lazy var explanationLabel: UILabel = { - let result: UILabel = UILabel() - result.setContentHuggingPriority(.required, for: .vertical) - result.setContentCompressionResistancePriority(.required, for: .vertical) - result.font = .systemFont(ofSize: Values.verySmallFontSize) - result.text = "vc_enter_public_key_explanation".localized() - result.themeTextColor = .textSecondary - result.textAlignment = .center - result.lineBreakMode = .byWordWrapping - result.numberOfLines = 0 - - return result - }() - - private lazy var spacer1 = UIView.spacer(withHeight: Values.largeSpacing) - private lazy var spacer2 = UIView.spacer(withHeight: Values.largeSpacing) - private lazy var spacer3 = UIView.spacer(withHeight: Values.largeSpacing) - private lazy var spacer4 = UIView.spacer(withHeight: Values.largeSpacing) - - private lazy var separator = Separator(title: "your_session_id".localized()) - - private lazy var qrCodeView: UIView = { - let result: UIView = UIView() - result.layer.cornerRadius = 8 - - let qrCodeImageView: UIImageView = UIImageView() - qrCodeImageView.setContentCompressionResistancePriority(.defaultLow, for: .vertical) - qrCodeImageView.image = QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: false) - .withRenderingMode(.alwaysTemplate) - qrCodeImageView.set(.width, to: .height, of: qrCodeImageView) - qrCodeImageView.heightAnchor - .constraint(lessThanOrEqualToConstant: (isIPhone5OrSmaller ? 160 : 220)) - .isActive = true - -#if targetEnvironment(simulator) -#else - // Note: For some reason setting this seems to stop the QRCode from rendering on the - // simulator so only doing it on device - qrCodeImageView.contentMode = .scaleAspectFit -#endif - - result.addSubview(qrCodeImageView) - qrCodeImageView.pin( - to: result, - withInset: 5 // The QRCode image has about 6pt of padding and we want 11 in total - ) - - ThemeManager.onThemeChange(observer: qrCodeImageView) { [weak qrCodeImageView, weak result] theme, _ in - switch theme.interfaceStyle { - case .light: - qrCodeImageView?.themeTintColorForced = .theme(theme, color: .textPrimary) - result?.themeBackgroundColorForced = nil - - default: - qrCodeImageView?.themeTintColorForced = .theme(theme, color: .backgroundPrimary) - result?.themeBackgroundColorForced = .color(.white) - } - - } - - return result - }() - - private lazy var qrCodeImageViewContainer: UIView = { - let result: UIView = UIView() - result.accessibilityLabel = "Your QR code" - result.isAccessibilityElement = true - result.addSubview(qrCodeView) - qrCodeView.center(.horizontal, in: result) - qrCodeView.pin(.top, to: .top, of: result) - qrCodeView.pin(.bottom, to: .bottom, of: result) - - return result - }() - - private lazy var userPublicKeyLabel: SRCopyableLabel = { - let result: SRCopyableLabel = SRCopyableLabel() - result.setContentCompressionResistancePriority(.required, for: .vertical) - result.font = Fonts.spaceMono(ofSize: Values.mediumFontSize) - result.text = getUserHexEncodedPublicKey() - result.themeTextColor = .textPrimary - result.textAlignment = .center - result.lineBreakMode = .byCharWrapping - result.numberOfLines = 0 - - return result - }() - - private lazy var userPublicKeyContainer: UIView = { - let result: UIView = UIView( - wrapping: userPublicKeyLabel, - withInsets: .zero, - shouldAdaptForIPadWithWidth: Values.iPadUserSessionIdContainerWidth - ) - - return result - }() - - private lazy var buttonContainer: UIStackView = { - let result = UIStackView(arrangedSubviews: [ copyButton, shareButton ]) - result.axis = .horizontal - result.spacing = UIDevice.current.isIPad ? Values.iPadButtonSpacing : Values.mediumSpacing - result.distribution = .fillEqually - - return result - }() - - private lazy var copyButton: SessionButton = { - let result = SessionButton(style: .bordered, size: .small) - result.setTitle("copy".localized(), for: .normal) - result.addTarget(self, action: #selector(copyPublicKey), for: .touchUpInside) - - return result - }() - - private lazy var shareButton: SessionButton = { - let result = SessionButton(style: .bordered, size: .small) - result.setTitle("share".localized(), for: .normal) - result.addTarget(self, action: #selector(sharePublicKey), for: .touchUpInside) - - return result - }() - - private lazy var nextButtonContainer: UIView = { - let result = UIView( - wrapping: nextButton, - withInsets: UIEdgeInsets(top: 0, leading: 80, bottom: 0, trailing: 80), - shouldAdaptForIPadWithWidth: Values.iPadButtonWidth - ) - result.alpha = (isKeyboardShowing ? 1 : 0) - result.isHidden = !isKeyboardShowing - result.accessibilityLabel = "Next" - result.isAccessibilityElement = true - return result - }() - - private lazy var nextButton: SessionButton = { - let result = SessionButton(style: .bordered, size: .large) - result.accessibilityLabel = "Next" - result.isAccessibilityElement = true - result.setTitle("next".localized(), for: .normal) - result.isEnabled = false - result.addTarget(self, action: #selector(startNewDMIfPossible), for: .touchUpInside) - - return result - }() - - private var viewWidth: NSLayoutConstraint? - private var viewHeight: NSLayoutConstraint? - - // MARK: - Lifecycle - - override func viewDidLoad() { - // Remove background color - view.themeBackgroundColor = .clear - - // Main stack view - let mainStackView = UIStackView(arrangedSubviews: [ - publicKeyTextView, - UIView.spacer(withHeight: Values.smallSpacing), - explanationLabel, - spacer1, - separator, - spacer2, - qrCodeImageViewContainer, - spacer3, - userPublicKeyContainer, - spacer4, - buttonContainer, - UIView.vStretchingSpacer(), - nextButtonContainer - ]) - mainStackView.axis = .vertical - mainStackView.alignment = .fill - mainStackView.layoutMargins = UIEdgeInsets( - top: Values.largeSpacing, - left: Values.largeSpacing, - bottom: Values.smallSpacing, - right: Values.largeSpacing - ) - mainStackView.isLayoutMarginsRelativeArrangement = true - view.addSubview(mainStackView) - - mainStackView.pin([ UIView.HorizontalEdge.leading, UIView.HorizontalEdge.trailing, UIView.VerticalEdge.top ], to: view) - bottomConstraint = mainStackView.pin(.bottom, to: .bottom, of: view, withInset: bottomMargin) - - // 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 constrainSize(to size: CGSize) { - if viewWidth == nil { - viewWidth = view.set(.width, to: size.width) - } else { - viewWidth?.constant = size.width - } - - if viewHeight == nil { - viewHeight = view.set(.height, to: size.height) - } else { - viewHeight?.constant = size.height - } - - if (UIDevice.current.isIPad) { - let iPadButtonContainerMargin: CGFloat = (size.width - Values.iPadButtonSpacing) / 2 - Values.iPadButtonWidth - Values.largeSpacing - buttonContainer.layoutMargins = UIEdgeInsets(top: 0, left: iPadButtonContainerMargin, bottom: 0, right: iPadButtonContainerMargin) - buttonContainer.isLayoutMarginsRelativeArrangement = true - } - } - - - func setSessionId(to sessionId: String) { - publicKeyTextView.insertText(sessionId) - } - - @objc private func dismissKeyboard() { - simulatorWillResignFirstResponder = true - publicKeyTextView.resignFirstResponder() - simulatorWillResignFirstResponder = false - } - - @objc private func enableCopyButton() { - copyButton.isUserInteractionEnabled = true - - UIView.transition(with: copyButton, duration: 0.25, options: .transitionCrossDissolve, animations: { - self.copyButton.setTitle("copy".localized(), for: .normal) - }, completion: nil) - } - - // MARK: - Updating - - @objc private func handleKeyboardWillChangeFrameNotification(_ notification: Notification) { - #if targetEnvironment(simulator) - // Note: See 'handleKeyboardWillHideNotification' for the explanation - guard !simulatorWillResignFirstResponder else { return } - #else - guard !isKeyboardShowing else { return } - #endif - - isKeyboardShowing = true - - guard let newHeight = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size.height else { return } - - let duration = max(0.25, ((notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval) ?? 0)) - let viewsToHide: [UIView] = [ self.spacer1, self.separator, self.spacer2, self.qrCodeImageViewContainer, self.spacer3, self.userPublicKeyContainer, self.spacer4, self.buttonContainer ] - - // We dispatch to the next run loop to prevent the animation getting stuck within the - // keyboard appearance animation (which would make the second animation start once the - // keyboard finishes appearing) - DispatchQueue.main.async { - UIView.animate( - withDuration: (duration / 2), - delay: 0, - options: .curveEaseOut, - animations: { - viewsToHide.forEach { $0.alpha = 0 } - }, - completion: { [weak self] _ in - UIView.performWithoutAnimation { - viewsToHide.forEach { $0.isHidden = true } - - self?.nextButtonContainer.alpha = 0 - self?.nextButtonContainer.isHidden = false - self?.bottomConstraint.constant = -(newHeight + (self?.bottomMargin ?? 0)) - self?.view.layoutIfNeeded() - } - - UIView.animate( - withDuration: (duration / 2), - delay: 0, - options: .curveEaseIn, - animations: { - self?.nextButtonContainer.alpha = 1 - }, - completion: nil - ) - } - ) - } - } - - @objc private func handleKeyboardWillHideNotification(_ notification: Notification) { - #if targetEnvironment(simulator) - // Note: On the simulator the keyboard won't appear by default (unless you enable - // it) this results in the "keyboard will hide" notification incorrectly getting - // triggered immediately - the 'simulatorWillResignFirstResponder' value is a workaround - // to make this behave more like a real device when testing - guard isKeyboardShowing && simulatorWillResignFirstResponder else { return } - #else - guard isKeyboardShowing else { return } - #endif - - let duration = max(0.25, ((notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval) ?? 0)) - let viewsToShow: [UIView] = [ self.spacer1, self.separator, self.spacer2, self.qrCodeImageViewContainer, self.spacer3, self.userPublicKeyContainer, self.spacer4, self.buttonContainer ] - isKeyboardShowing = false - - // We dispatch to the next run loop to prevent the animation getting stuck within the - // keyboard hide animation (which would make the second animation start once the keyboard - // finishes disappearing) - DispatchQueue.main.async { - UIView.animate( - withDuration: (duration / 2), - delay: 0, - options: .curveEaseOut, - animations: { [weak self] in - self?.nextButtonContainer.alpha = 0 - }, - completion: { [weak self] _ in - UIView.performWithoutAnimation { - viewsToShow.forEach { - $0.alpha = 0 - $0.isHidden = false - } - - self?.nextButtonContainer.isHidden = true - self?.bottomConstraint.constant = -(self?.bottomMargin ?? 0) - self?.view.layoutIfNeeded() - } - - UIView.animate( - withDuration: (duration / 2), - delay: 0, - options: .curveEaseIn, - animations: { - viewsToShow.forEach { $0.alpha = 1 } - }, - completion: nil - ) - } - ) - } - } - - // MARK: - Interaction - - @objc private func copyPublicKey() { - UIPasteboard.general.string = getUserHexEncodedPublicKey() - - copyButton.isUserInteractionEnabled = false - - UIView.transition(with: copyButton, duration: 0.25, options: .transitionCrossDissolve, animations: { - self.copyButton.setTitle("copied".localized(), for: .normal) - }, completion: nil) - Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(enableCopyButton), userInfo: nil, repeats: false) - } - - @objc private func sharePublicKey() { - let shareVC = UIActivityViewController(activityItems: [ getUserHexEncodedPublicKey() ], applicationActivities: nil) - - if UIDevice.current.isIPad { - shareVC.excludedActivityTypes = [] - shareVC.popoverPresentationController?.permittedArrowDirections = [] - shareVC.popoverPresentationController?.sourceView = self.view - shareVC.popoverPresentationController?.sourceRect = self.view.bounds - } - - NewDMVC.navigationController!.present(shareVC, animated: true, completion: nil) - } - - @objc fileprivate func startNewDMIfPossible() { - let text = publicKeyTextView.text?.trimmingCharacters(in: .whitespaces) ?? "" - NewDMVC.startNewDMIfPossible(with: text, onError: nil) - } -} - -// MARK: - ScanQRCodePlaceholderVC - -private final class ScanQRCodePlaceholderVC: UIViewController { - weak var newDMVC: NewDMVC! - - private var viewWidth: NSLayoutConstraint? - private var viewHeight: NSLayoutConstraint? - - override func viewDidLoad() { - // Remove background color - view.themeBackgroundColor = .clear - - // Set up explanation label - let explanationLabel = UILabel() - explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) - explanationLabel.text = "vc_scan_qr_code_camera_access_explanation".localized() - explanationLabel.themeTextColor = .textPrimary - explanationLabel.textAlignment = .center - explanationLabel.lineBreakMode = .byWordWrapping - explanationLabel.numberOfLines = 0 - - // Set up call to action button - let callToActionButton = UIButton() - callToActionButton.titleLabel?.font = .boldSystemFont(ofSize: Values.mediumFontSize) - callToActionButton.setTitle("continue_2".localized(), for: .normal) - callToActionButton.setThemeTitleColor(.primary, for: .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.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 constrainSize(to size: CGSize) { - if viewWidth == nil { - viewWidth = view.set(.width, to: size.width) - } else { - viewWidth?.constant = size.width - } - - if viewHeight == nil { - viewHeight = view.set(.height, to: size.height) - } else { - viewHeight?.constant = size.height - } - } - - - @objc private func requestCameraAccess() { - Permissions.requestCameraPermissionIfNeeded { [weak self] in - self?.newDMVC.handleCameraAccessGranted() - } - } -} diff --git a/Session/Home/New Conversation/NewMessageScreen.swift b/Session/Home/New Conversation/NewMessageScreen.swift index a79591f45..dea911519 100644 --- a/Session/Home/New Conversation/NewMessageScreen.swift +++ b/Session/Home/New Conversation/NewMessageScreen.swift @@ -11,9 +11,13 @@ struct NewMessageScreen: View { @EnvironmentObject var host: HostWrapper @State var tabIndex = 0 - @State private var accountIdOrONS: String = "" + @State private var accountIdOrONS: String @State private var errorString: String? = nil + init(accountId: String = "") { + self.accountIdOrONS = accountId + } + var body: some View { ZStack(alignment: .topLeading) { VStack( diff --git a/Session/Home/New Conversation/StartConversationScreen.swift b/Session/Home/New Conversation/StartConversationScreen.swift index 979f7c8ad..b866088c6 100644 --- a/Session/Home/New Conversation/StartConversationScreen.swift +++ b/Session/Home/New Conversation/StartConversationScreen.swift @@ -37,7 +37,8 @@ struct StartConversationScreen: View { image: "Group", title: "vc_create_closed_group_title".localized() ) { - + let viewController = NewClosedGroupVC() + self.host.controller?.navigationController?.pushViewController(viewController, animated: true) } Line(color: .borderSeparator) @@ -48,7 +49,8 @@ struct StartConversationScreen: View { image: "Globe", title: "vc_join_public_chat_title".localized() ) { - + let viewController = JoinOpenGroupVC() + self.host.controller?.navigationController?.pushViewController(viewController, animated: true) } Line(color: .borderSeparator)