// // CredentialsController.swift // PrivadoVPN // // Created by Zhandos Bolatbekov on 13.10.2021. // Copyright © 2021 Privado LLC. All rights reserved. // import UIKit final class CredentialsController: UIViewController { private enum Constants { enum Font { static let headerTitle = UIFont.primary(size: 30, weight: .semibold) static let headerSubtitle = UIFont.primary(size: 21.3, weight: .semibold) static let email = UIFont.primary(size: 20.7, weight: .medium) static let credentials = UIFont.primary(size: 20, weight: .medium) static let prompt = UIFont.primary(size: 14.7, weight: .medium) static let buttonTitle = UIFont.primary(size: 21.3, weight: .bold) } enum LocalizedString { static let headerTitle = "credentials.header.title" static let headerSubtitle = "credentials.header.subtitle" static let username = "credentials.username" static let password = "credentials.password" static let prompt = "credentials.prompt" static let buttonTutorial = "credentials.button.tutorial" static let buttonUseApp = "credentials.button.useApp" } enum Color { static let headerTitle = Colors.basicText.color static let headerSubtitle = Colors.SystemCTA.lightPurple.color static let emailLabel = Colors.SystemCTA.lightPurple.color static let credentialName = Colors.basicText.color static let credentialValue = Colors.SystemCTA.lightPurple.color static let copyButton = Colors.SystemCTA.lightPurple.color static let prompt = Colors.SystemCTA.lightPurple.color static let background = UIColor(rgb: 0x202350) static let credentialItemBackground = UIColor(rgb: 0x202864) static let buttonBackground = UIColor(rgb: 0x3E449C) static let buttonsContainerBackground = UIColor(rgb: 0x202864) static let buttonTitle = UIColor.white } enum Geometry { static let credentialLabelIndentX: CGFloat = 21 static let credentialLabelTop: CGFloat = 26.7 static let credentialLabelBottom: CGFloat = 22 static let credentialItemsIndentY: CGFloat = 6 static let credentialsSeparatorHeight: CGFloat = 3.3 static let credentialsContainerCornerRadius: CGFloat = 16.7 static let headerTitleTop: CGFloat = 66.7 static let commonIndentX: CGFloat = 30 static let headerSubtitleTop: CGFloat = 10 static let emailTop: CGFloat = 36.7 static let credentialsTop: CGFloat = 20 static let promptTop: CGFloat = 25 static let promptIndentX: CGFloat = 40 static let promptBottom: CGFloat = 40 static let promptLineSpacing: CGFloat = 5 static let copyButtonSize: CGFloat = 32 static let emailMinScaleFactor: CGFloat = 0.8 static let credentialMinScaleFactor: CGFloat = 0.8 static let buttonsIndentY: CGFloat = 36 static let buttonsIndentX: CGFloat = 31 static let buttonsSpacing: CGFloat = 20 static let buttonHeight: CGFloat = 60 static let buttonsContainerShadowOffset = CGSize(width: 0, height: -1.7) static let buttonsContainerShadowRadius: CGFloat = 10 } static let buttonsContainerShadowOpacity: Float = 0.33 static let copyButtonImageName = "copy" static let credentialSeparator = " " } // MARK: - Properties private let output: CredentialsControllerOutput private var buttonsContainer: UIView? override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent } private var credentialLabels = [UILabel]() // MARK: - Init init(output: CredentialsControllerOutput) { self.output = output super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { nil } // MARK: - Life-cycle override func viewDidLoad() { super.viewDidLoad() self.createUI() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() self.buttonsContainer?.dropShadow(shadowColor: .black, fillColor: Constants.Color.buttonsContainerBackground, opacity: Constants.buttonsContainerShadowOpacity, offset: Constants.Geometry.buttonsContainerShadowOffset, shadowRadius: Constants.Geometry.buttonsContainerShadowRadius) self.syncCredentialsFontSize() } // MARK: - Private private func syncCredentialsFontSize() { let minFontSize: CGFloat = self.credentialLabels.reduce(into: Constants.Font.credentials.pointSize) { result, label in if let size = label.getFontSizeForLabel() { result = min(result, size) } } for i in 0.. UIView { let view = UIView() view.layer.cornerRadius = Constants.Geometry.credentialsContainerCornerRadius view.translatesAutoresizingMaskIntoConstraints = false view.clipsToBounds = true let stackView = UIStackView(arrangedSubviews: [ self.createCredentialItemContainer(name: NSLocalizedString(Constants.LocalizedString.username, comment: "") + Constants.credentialSeparator, value: self.output.username), self.createCredentialItemContainer(name: NSLocalizedString(Constants.LocalizedString.password, comment: "") + Constants.credentialSeparator, value: self.output.password) ]) stackView.translatesAutoresizingMaskIntoConstraints = false stackView.distribution = .fillEqually stackView.axis = .vertical stackView.spacing = Constants.Geometry.credentialsSeparatorHeight view.addSubview(stackView) NSLayoutConstraint.activate([ stackView.topAnchor.constraint(equalTo: view.topAnchor), stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor), stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor), stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor) ]) return view } private func createCredentialItemContainer(name: String, value: String?) -> UIView { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = Constants.Color.credentialItemBackground let label = self.createCredentialLabel(name: name, value: value) self.credentialLabels.append(label) view.addSubview(label) let copyButton = self.createCopyButton(for: label) view.addSubview(copyButton) NSLayoutConstraint.activate([ label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: Constants.Geometry.credentialLabelIndentX), label.trailingAnchor.constraint(equalTo: copyButton.leadingAnchor, constant: -Constants.Geometry.credentialLabelIndentX / 2), label.topAnchor.constraint(equalTo: view.topAnchor, constant: Constants.Geometry.credentialLabelTop), label.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -Constants.Geometry.credentialLabelBottom), copyButton.centerYAnchor.constraint(equalTo: label.centerYAnchor), copyButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -Constants.Geometry.credentialLabelIndentX), copyButton.heightAnchor.constraint(equalToConstant: Constants.Geometry.copyButtonSize), copyButton.widthAnchor.constraint(equalToConstant: Constants.Geometry.copyButtonSize) ]) return view } private func createHeaderTitle() -> UILabel { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = Constants.Font.headerTitle label.textAlignment = .center label.textColor = Constants.Color.headerTitle label.numberOfLines = 0 label.text = NSLocalizedString(Constants.LocalizedString.headerTitle, comment: "") return label } private func createHeaderSubtitle() -> UILabel { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = Constants.Font.headerSubtitle label.textAlignment = .center label.textColor = Constants.Color.headerSubtitle label.numberOfLines = 0 label.text = NSLocalizedString(Constants.LocalizedString.headerSubtitle, comment: "") return label } private func createEmailLabel() -> UILabel { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = Constants.Font.email label.textAlignment = .center label.textColor = Constants.Color.emailLabel label.numberOfLines = 1 label.adjustsFontSizeToFitWidth = true label.minimumScaleFactor = Constants.Geometry.emailMinScaleFactor label.lineBreakMode = .byTruncatingTail label.text = self.output.email return label } private func createCredentialLabel(name: String, value: String?) -> UILabel { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = Constants.Font.credentials label.numberOfLines = 1 label.adjustsFontSizeToFitWidth = true label.minimumScaleFactor = Constants.Geometry.credentialMinScaleFactor label.lineBreakMode = .byTruncatingTail label.tag = self.credentialLabels.count let attributedString = NSMutableAttributedString(string: name, attributes: [.foregroundColor: Constants.Color.credentialName]) let suffix = NSAttributedString(string: value ?? "", attributes: [.foregroundColor: Constants.Color.credentialValue]) attributedString.append(suffix) label.attributedText = attributedString return label } private func createCopyButton(for label: UILabel) -> UIButton { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.tintColor = Constants.Color.copyButton button.setImage(UIImage(imageLiteralResourceName: Constants.copyButtonImageName), for: .normal) button.tag = label.tag button.addTarget(self, action: #selector(self.didTapCopyButton(sender:)), for: .touchUpInside) return button } private func createPromptLabel() -> UILabel { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.font = Constants.Font.prompt label.textColor = Constants.Color.prompt label.numberOfLines = 0 let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineSpacing = Constants.Geometry.promptLineSpacing label.attributedText = NSAttributedString( string: NSLocalizedString(Constants.LocalizedString.prompt, comment: ""), attributes: [.paragraphStyle: paragraphStyle] ) return label } private func createButtons() -> UIView { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = Constants.Color.buttonsContainerBackground let tutorialButton = self.createActionButton(title: NSLocalizedString(Constants.LocalizedString.buttonTutorial, comment: "")) tutorialButton.addTarget(self, action: #selector(self.didTapTutorialButton), for: .touchUpInside) let skipButton = self.createActionButton(title: NSLocalizedString(Constants.LocalizedString.buttonUseApp, comment: "")) skipButton.addTarget(self, action: #selector(self.didTapSkipButton), for: .touchUpInside) let stackView = UIStackView(arrangedSubviews: [skipButton]) stackView.translatesAutoresizingMaskIntoConstraints = false stackView.spacing = Constants.Geometry.buttonsSpacing stackView.axis = .vertical stackView.distribution = .fillEqually view.addSubview(stackView) NSLayoutConstraint.activate([ stackView.topAnchor.constraint(equalTo: view.topAnchor, constant: Constants.Geometry.buttonsIndentY), stackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -Constants.Geometry.buttonsIndentY), stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: Constants.Geometry.buttonsIndentX), stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -Constants.Geometry.buttonsIndentX) ]) return view } private func createActionButton(title: String) -> UIButton { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.backgroundColor = Constants.Color.buttonBackground button.layer.cornerRadius = Constants.Geometry.buttonHeight / 2 let title = NSAttributedString( string: title, attributes: [.font: Constants.Font.buttonTitle, .foregroundColor: Constants.Color.buttonTitle] ) button.setAttributedTitle(title, for: .normal) button.heightAnchor.constraint(equalToConstant: Constants.Geometry.buttonHeight).isActive = true return button } @objc private func didTapTutorialButton() { self.output.didTapTutorial() } @objc private func didTapSkipButton() { self.output.didTapSkip() } @objc private func didTapCopyButton(sender: UIButton) { guard let label = self.credentialLabels.first(where: { $0.tag == sender.tag }), let text = label.attributedText?.string, let value = text.split(separator: String.Element(Constants.credentialSeparator)).last else { return } UINotificationFeedbackGenerator().notificationOccurred(.success) UIPasteboard.general.string = String(value) } }