mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			406 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			406 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Swift
		
	
| 
 | |
| final class SettingsVC : BaseVC, AvatarViewHelperDelegate {
 | |
|     private var profilePictureToBeUploaded: UIImage?
 | |
|     private var displayNameToBeUploaded: String?
 | |
|     private var isEditingDisplayName = false { didSet { handleIsEditingDisplayNameChanged() } }
 | |
|     
 | |
|     // MARK: Components
 | |
|     private lazy var profilePictureView: ProfilePictureView = {
 | |
|         let result = ProfilePictureView()
 | |
|         let size = Values.largeProfilePictureSize
 | |
|         result.size = size
 | |
|         result.set(.width, to: size)
 | |
|         result.set(.height, to: size)
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var profilePictureUtilities: AvatarViewHelper = {
 | |
|         let result = AvatarViewHelper()
 | |
|         result.delegate = self
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var displayNameLabel: UILabel = {
 | |
|         let result = UILabel()
 | |
|         result.textColor = Colors.text
 | |
|         result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
 | |
|         result.lineBreakMode = .byTruncatingTail
 | |
|         result.textAlignment = .center
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var displayNameTextField: TextField = {
 | |
|         let result = TextField(placeholder: NSLocalizedString("vc_settings_display_name_text_field_hint", comment: ""), usesDefaultHeight: false)
 | |
|         result.textAlignment = .center
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var copyButton: Button = {
 | |
|         let result = Button(style: .prominentOutline, size: .medium)
 | |
|         result.setTitle(NSLocalizedString("copy", comment: ""), for: UIControl.State.normal)
 | |
|         result.addTarget(self, action: #selector(copyPublicKey), for: UIControl.Event.touchUpInside)
 | |
|         return result
 | |
|     }()
 | |
| 
 | |
|     private lazy var settingButtonsStackView: UIStackView = {
 | |
|         let result = UIStackView()
 | |
|         result.axis = .vertical
 | |
|         result.alignment = .fill
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     // MARK: Lifecycle
 | |
|     override func viewDidLoad() {
 | |
|         super.viewDidLoad()
 | |
|         setUpGradientBackground()
 | |
|         setUpNavBarStyle()
 | |
|         setNavBarTitle(NSLocalizedString("vc_settings_title", comment: ""))
 | |
|         // Set up navigation bar buttons
 | |
|         let backButton = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
 | |
|         backButton.tintColor = Colors.text
 | |
|         navigationItem.backBarButtonItem = backButton
 | |
|         updateNavigationBarButtons()
 | |
|         // Set up profile picture view
 | |
|         let profilePictureTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(showEditProfilePictureUI))
 | |
|         profilePictureView.addGestureRecognizer(profilePictureTapGestureRecognizer)
 | |
|         profilePictureView.hexEncodedPublicKey = getUserHexEncodedPublicKey()
 | |
|         profilePictureView.update()
 | |
|         // Set up display name label
 | |
|         displayNameLabel.text = OWSProfileManager.shared().profileNameForRecipient(withID: getUserHexEncodedPublicKey())
 | |
|         // Set up display name container
 | |
|         let displayNameContainer = UIView()
 | |
|         displayNameContainer.addSubview(displayNameLabel)
 | |
|         displayNameLabel.pin(to: displayNameContainer)
 | |
|         displayNameContainer.addSubview(displayNameTextField)
 | |
|         displayNameTextField.pin(to: displayNameContainer)
 | |
|         displayNameContainer.set(.height, to: 40)
 | |
|         displayNameTextField.alpha = 0
 | |
|         let displayNameContainerTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(showEditDisplayNameUI))
 | |
|         displayNameContainer.addGestureRecognizer(displayNameContainerTapGestureRecognizer)
 | |
|         // Set up header view
 | |
|         let headerStackView = UIStackView(arrangedSubviews: [ profilePictureView, displayNameContainer ])
 | |
|         headerStackView.axis = .vertical
 | |
|         headerStackView.spacing = Values.smallSpacing
 | |
|         headerStackView.alignment = .center
 | |
|         // Set up separator
 | |
|         let separator = Separator(title: NSLocalizedString("your_session_id", comment: ""))
 | |
|         // Set up public key label
 | |
|         let publicKeyLabel = UILabel()
 | |
|         publicKeyLabel.textColor = Colors.text
 | |
|         publicKeyLabel.font = Fonts.spaceMono(ofSize: isIPhone5OrSmaller ? Values.mediumFontSize : Values.largeFontSize)
 | |
|         publicKeyLabel.numberOfLines = 0
 | |
|         publicKeyLabel.textAlignment = .center
 | |
|         publicKeyLabel.lineBreakMode = .byCharWrapping
 | |
|         publicKeyLabel.text = getUserHexEncodedPublicKey()
 | |
|         // Set up share button
 | |
|         let shareButton = Button(style: .regular, size: .medium)
 | |
|         shareButton.setTitle(NSLocalizedString("share", comment: ""), for: UIControl.State.normal)
 | |
|         shareButton.addTarget(self, action: #selector(sharePublicKey), for: UIControl.Event.touchUpInside)
 | |
|         // Set up button container
 | |
|         let buttonContainer = UIStackView(arrangedSubviews: [ copyButton, shareButton ])
 | |
|         buttonContainer.axis = .horizontal
 | |
|         buttonContainer.spacing = Values.mediumSpacing
 | |
|         buttonContainer.distribution = .fillEqually
 | |
|         // Set up top stack view
 | |
|         let topStackView = UIStackView(arrangedSubviews: [ headerStackView, separator, publicKeyLabel, buttonContainer ])
 | |
|         topStackView.axis = .vertical
 | |
|         topStackView.spacing = Values.largeSpacing
 | |
|         topStackView.alignment = .fill
 | |
|         topStackView.layoutMargins = UIEdgeInsets(top: 0, left: Values.largeSpacing, bottom: 0, right: Values.largeSpacing)
 | |
|         topStackView.isLayoutMarginsRelativeArrangement = true
 | |
|         // Set up setting buttons stack view
 | |
|         getSettingButtons().forEach { settingButtonOrSeparator in
 | |
|             settingButtonsStackView.addArrangedSubview(settingButtonOrSeparator)
 | |
|         }
 | |
|         // Set up version label
 | |
|         let versionLabel = UILabel()
 | |
|         versionLabel.textColor = Colors.text.withAlphaComponent(Values.unimportantElementOpacity)
 | |
|         versionLabel.font = .systemFont(ofSize: Values.verySmallFontSize)
 | |
|         versionLabel.numberOfLines = 0
 | |
|         versionLabel.textAlignment = .center
 | |
|         versionLabel.lineBreakMode = .byCharWrapping
 | |
|         let version = Bundle.main.infoDictionary!["CFBundleShortVersionString"]!
 | |
|         let buildNumber = Bundle.main.infoDictionary!["CFBundleVersion"]!
 | |
|         versionLabel.text = "Version \(version) (\(buildNumber))"
 | |
|         // Set up stack view
 | |
|         let stackView = UIStackView(arrangedSubviews: [ topStackView, settingButtonsStackView, versionLabel ])
 | |
|         stackView.axis = .vertical
 | |
|         stackView.spacing = Values.largeSpacing
 | |
|         stackView.alignment = .fill
 | |
|         stackView.layoutMargins = UIEdgeInsets(top: Values.mediumSpacing, left: 0, bottom: Values.mediumSpacing, right: 0)
 | |
|         stackView.isLayoutMarginsRelativeArrangement = true
 | |
|         stackView.set(.width, to: UIScreen.main.bounds.width)
 | |
|         // Set up scroll view
 | |
|         let scrollView = UIScrollView()
 | |
|         scrollView.showsVerticalScrollIndicator = false
 | |
|         scrollView.addSubview(stackView)
 | |
|         stackView.pin(to: scrollView)
 | |
|         view.addSubview(scrollView)
 | |
|         scrollView.pin(to: view)
 | |
|     }
 | |
|     
 | |
|     private func getSettingButtons() -> [UIView] {
 | |
|         func getSeparator() -> UIView {
 | |
|             let result = UIView()
 | |
|             result.backgroundColor = Colors.separator
 | |
|             result.set(.height, to: Values.separatorThickness)
 | |
|             return result
 | |
|         }
 | |
|         func getSettingButton(withTitle title: String, color: UIColor, action selector: Selector) -> UIButton {
 | |
|             let button = UIButton()
 | |
|             button.setTitle(title, for: UIControl.State.normal)
 | |
|             button.setTitleColor(color, for: UIControl.State.normal)
 | |
|             button.titleLabel!.font = .boldSystemFont(ofSize: Values.mediumFontSize)
 | |
|             button.titleLabel!.textAlignment = .center
 | |
|             func getImage(withColor color: UIColor) -> UIImage {
 | |
|                 let rect = CGRect(origin: CGPoint.zero, size: CGSize(width: 1, height: 1))
 | |
|                 UIGraphicsBeginImageContext(rect.size)
 | |
|                 let context = UIGraphicsGetCurrentContext()!
 | |
|                 context.setFillColor(color.cgColor)
 | |
|                 context.fill(rect)
 | |
|                 let image = UIGraphicsGetImageFromCurrentImageContext()
 | |
|                 UIGraphicsEndImageContext()
 | |
|                 return image!
 | |
|             }
 | |
|             let backgroundColor = isLightMode ? UIColor(hex: 0xFCFCFC) : UIColor(hex: 0x1B1B1B)
 | |
|             button.setBackgroundImage(getImage(withColor: backgroundColor), for: UIControl.State.normal)
 | |
|             let selectedColor = isLightMode ? UIColor(hex: 0xDFDFDF) : UIColor(hex: 0x0C0C0C)
 | |
|             button.setBackgroundImage(getImage(withColor: selectedColor), for: UIControl.State.highlighted)
 | |
|             button.addTarget(self, action: selector, for: UIControl.Event.touchUpInside)
 | |
|             button.set(.height, to: Values.settingButtonHeight)
 | |
|             return button
 | |
|         }
 | |
|         return [
 | |
|             getSeparator(),
 | |
|             getSettingButton(withTitle: NSLocalizedString("vc_settings_privacy_button_title", comment: ""), color: Colors.text, action: #selector(showPrivacySettings)),
 | |
|             getSeparator(),
 | |
|             getSettingButton(withTitle: NSLocalizedString("vc_settings_notifications_button_title", comment: ""), color: Colors.text, action: #selector(showNotificationSettings)),
 | |
|             getSeparator(),
 | |
|             getSettingButton(withTitle: "Invite", color: Colors.text, action: #selector(sendInvitation)),
 | |
|             getSeparator(),
 | |
|             getSettingButton(withTitle: NSLocalizedString("vc_settings_recovery_phrase_button_title", comment: ""), color: Colors.text, action: #selector(showSeed)),
 | |
|             getSeparator(),
 | |
|             getSettingButton(withTitle: NSLocalizedString("vc_settings_clear_all_data_button_title", comment: ""), color: Colors.destructive, action: #selector(clearAllData)),
 | |
|             getSeparator()
 | |
|         ]
 | |
|     }
 | |
|     
 | |
|     // MARK: General
 | |
|     @objc private func enableCopyButton() {
 | |
|         copyButton.isUserInteractionEnabled = true
 | |
|         UIView.transition(with: copyButton, duration: 0.25, options: .transitionCrossDissolve, animations: {
 | |
|             self.copyButton.setTitle(NSLocalizedString("copy", comment: ""), for: UIControl.State.normal)
 | |
|         }, completion: nil)
 | |
|     }
 | |
|     
 | |
|     func avatarActionSheetTitle() -> String? {
 | |
|         return "Update Profile Picture"
 | |
|     }
 | |
|     
 | |
|     func fromViewController() -> UIViewController {
 | |
|         return self
 | |
|     }
 | |
|     
 | |
|     func hasClearAvatarAction() -> Bool {
 | |
|         return false
 | |
|     }
 | |
|     
 | |
|     func clearAvatarActionLabel() -> String {
 | |
|         return "Clear"
 | |
|     }
 | |
|     
 | |
|     // MARK: Updating
 | |
|     private func handleIsEditingDisplayNameChanged() {
 | |
|         updateNavigationBarButtons()
 | |
|         UIView.animate(withDuration: 0.25) {
 | |
|             self.displayNameLabel.alpha = self.isEditingDisplayName ? 0 : 1
 | |
|             self.displayNameTextField.alpha = self.isEditingDisplayName ? 1 : 0
 | |
|         }
 | |
|         if isEditingDisplayName {
 | |
|             displayNameTextField.becomeFirstResponder()
 | |
|         } else {
 | |
|             displayNameTextField.resignFirstResponder()
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private func updateNavigationBarButtons() {
 | |
|         if isEditingDisplayName {
 | |
|             let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(handleCancelDisplayNameEditingButtonTapped))
 | |
|             cancelButton.tintColor = Colors.text
 | |
|             navigationItem.leftBarButtonItem = cancelButton
 | |
|             let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(handleSaveDisplayNameButtonTapped))
 | |
|             doneButton.tintColor = Colors.text
 | |
|             navigationItem.rightBarButtonItem = doneButton
 | |
|         } else {
 | |
|             let closeButton = UIBarButtonItem(image: #imageLiteral(resourceName: "X"), style: .plain, target: self, action: #selector(close))
 | |
|             closeButton.tintColor = Colors.text
 | |
|             navigationItem.leftBarButtonItem = closeButton
 | |
|             if #available(iOS 13, *) { // Pre iOS 13 the user can't switch actively but the app still responds to system changes
 | |
|                 let appModeIcon = isDarkMode ? #imageLiteral(resourceName: "ic_dark_theme_on").withTintColor(.white) : #imageLiteral(resourceName: "ic_dark_theme_off").withTintColor(.black)
 | |
|                 let appModeButton = UIButton()
 | |
|                 appModeButton.setImage(appModeIcon, for: UIControl.State.normal)
 | |
|                 appModeButton.tintColor = Colors.text
 | |
|                 appModeButton.addTarget(self, action: #selector(switchAppMode), for: UIControl.Event.touchUpInside)
 | |
|                 let qrCodeIcon = isDarkMode ? #imageLiteral(resourceName: "QRCode").withTintColor(.white) : #imageLiteral(resourceName: "QRCode").withTintColor(.black)
 | |
|                 let qrCodeButton = UIButton()
 | |
|                 qrCodeButton.setImage(qrCodeIcon, for: UIControl.State.normal)
 | |
|                 qrCodeButton.tintColor = Colors.text
 | |
|                 qrCodeButton.addTarget(self, action: #selector(showQRCode), for: UIControl.Event.touchUpInside)
 | |
|                 let stackView = UIStackView(arrangedSubviews: [ appModeButton, qrCodeButton ])
 | |
|                 stackView.axis = .horizontal
 | |
|                 stackView.spacing = Values.mediumSpacing
 | |
|                 navigationItem.rightBarButtonItem = UIBarButtonItem(customView: stackView)
 | |
|             } else {
 | |
|                 let qrCodeIcon = isDarkMode ? #imageLiteral(resourceName: "QRCode").asTintedImage(color: .white) : #imageLiteral(resourceName: "QRCode").asTintedImage(color: .black)
 | |
|                 let qrCodeButton = UIBarButtonItem(image: qrCodeIcon, style: .plain, target: self, action: #selector(showQRCode))
 | |
|                 qrCodeButton.tintColor = Colors.text
 | |
|                 navigationItem.rightBarButtonItem = qrCodeButton
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     func avatarDidChange(_ image: UIImage) {
 | |
|         let maxSize = Int(kOWSProfileManager_MaxAvatarDiameter)
 | |
|         profilePictureToBeUploaded = image.resizedImage(toFillPixelSize: CGSize(width: maxSize, height: maxSize))
 | |
|         updateProfile(isUpdatingDisplayName: false, isUpdatingProfilePicture: true)
 | |
|     }
 | |
|     
 | |
|     func clearAvatar() {
 | |
|         profilePictureToBeUploaded = nil
 | |
|         updateProfile(isUpdatingDisplayName: false, isUpdatingProfilePicture: true)
 | |
|     }
 | |
|     
 | |
|     private func updateProfile(isUpdatingDisplayName: Bool, isUpdatingProfilePicture: Bool) {
 | |
|         let displayName = displayNameToBeUploaded ?? OWSProfileManager.shared().profileNameForRecipient(withID: getUserHexEncodedPublicKey())
 | |
|         let profilePicture = profilePictureToBeUploaded ?? OWSProfileManager.shared().profileAvatar(forRecipientId: getUserHexEncodedPublicKey())
 | |
|         ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] modalActivityIndicator in
 | |
|             OWSProfileManager.shared().updateLocalProfileName(displayName, avatarImage: profilePicture, success: {
 | |
|                 DispatchQueue.main.async {
 | |
|                     modalActivityIndicator.dismiss {
 | |
|                         guard let self = self else { return }
 | |
|                         self.profilePictureView.update()
 | |
|                         self.displayNameLabel.text = displayName
 | |
|                         self.profilePictureToBeUploaded = nil
 | |
|                         self.displayNameToBeUploaded = nil
 | |
|                     }
 | |
|                 }
 | |
|             }, failure: { error in
 | |
|                 DispatchQueue.main.async {
 | |
|                     modalActivityIndicator.dismiss {
 | |
|                         var isMaxFileSizeExceeded = false
 | |
|                         if let error = error as? DotNetAPI.Error {
 | |
|                             isMaxFileSizeExceeded = (error == .maxFileSizeExceeded)
 | |
|                         }
 | |
|                         let title = isMaxFileSizeExceeded ? "Maximum File Size Exceeded" : "Couldn't Update Profile"
 | |
|                         let message = isMaxFileSizeExceeded ? "Please select a smaller photo and try again" : "Please check your internet connection and try again"
 | |
|                         let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
 | |
|                         alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
 | |
|                         self?.present(alert, animated: true, completion: nil)
 | |
|                     }
 | |
|                 }
 | |
|             }, requiresSync: true)
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @objc override internal func handleAppModeChangedNotification(_ notification: Notification) {
 | |
|         super.handleAppModeChangedNotification(notification)
 | |
|         updateNavigationBarButtons()
 | |
|         settingButtonsStackView.arrangedSubviews.forEach { settingButton in
 | |
|             settingButtonsStackView.removeArrangedSubview(settingButton)
 | |
|             settingButton.removeFromSuperview()
 | |
|         }
 | |
|         getSettingButtons().forEach { settingButtonOrSeparator in
 | |
|             settingButtonsStackView.addArrangedSubview(settingButtonOrSeparator) // Re-do the setting buttons
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     // MARK: Interaction
 | |
|     @objc private func close() {
 | |
|         dismiss(animated: true, completion: nil)
 | |
|     }
 | |
| 
 | |
|     @objc private func switchAppMode() {
 | |
|         let newAppMode: AppMode = isLightMode ? .dark : .light
 | |
|         AppModeManager.shared.setCurrentAppMode(to: newAppMode)
 | |
|     }
 | |
| 
 | |
|     @objc private func showQRCode() {
 | |
|         let qrCodeVC = QRCodeVC()
 | |
|         navigationController!.pushViewController(qrCodeVC, animated: true)
 | |
|     }
 | |
|     
 | |
|     @objc private func handleCancelDisplayNameEditingButtonTapped() {
 | |
|         isEditingDisplayName = false
 | |
|     }
 | |
|     
 | |
|     @objc private func handleSaveDisplayNameButtonTapped() {
 | |
|         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)
 | |
|         }
 | |
|         let displayName = displayNameTextField.text!.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
 | |
|         guard !displayName.isEmpty else {
 | |
|             return showError(title: NSLocalizedString("vc_settings_display_name_missing_error", comment: ""))
 | |
|         }
 | |
|         guard !OWSProfileManager.shared().isProfileNameTooLong(displayName) else {
 | |
|             return showError(title: NSLocalizedString("vc_settings_display_name_too_long_error", comment: ""))
 | |
|         }
 | |
|         isEditingDisplayName = false
 | |
|         displayNameToBeUploaded = displayName
 | |
|         updateProfile(isUpdatingDisplayName: true, isUpdatingProfilePicture: false)
 | |
|     }
 | |
|     
 | |
|     @objc private func showEditProfilePictureUI() {
 | |
|         profilePictureUtilities.showChangeAvatarUI()
 | |
|     }
 | |
|     
 | |
|     @objc private func showEditDisplayNameUI() {
 | |
|         isEditingDisplayName = true
 | |
|     }
 | |
|     
 | |
|     @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", for: UIControl.State.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)
 | |
|         navigationController!.present(shareVC, animated: true, completion: nil)
 | |
|     }
 | |
|     
 | |
|     @objc private func showPrivacySettings() {
 | |
|         let privacySettingsVC = PrivacySettingsTableViewController()
 | |
|         navigationController!.pushViewController(privacySettingsVC, animated: true)
 | |
|     }
 | |
|     
 | |
|     @objc private func showNotificationSettings() {
 | |
|         let notificationSettingsVC = NotificationSettingsViewController()
 | |
|         navigationController!.pushViewController(notificationSettingsVC, animated: true)
 | |
|     }
 | |
| 
 | |
|     @objc private func sendInvitation() {
 | |
|         let invitation = "Hey, I've been using Session to chat with complete privacy and security. Come join me! Download it at https://getsession.org/. My Session ID is \(getUserHexEncodedPublicKey())!"
 | |
|         let shareVC = UIActivityViewController(activityItems: [ invitation ], applicationActivities: nil)
 | |
|         navigationController!.present(shareVC, animated: true, completion: nil)
 | |
|     }
 | |
|     
 | |
|     @objc private func showSeed() {
 | |
|         let seedModal = SeedModal()
 | |
|         seedModal.modalPresentationStyle = .overFullScreen
 | |
|         seedModal.modalTransitionStyle = .crossDissolve
 | |
|         present(seedModal, animated: true, completion: nil)
 | |
|     }
 | |
|     
 | |
|     @objc private func clearAllData() {
 | |
|         let nukeDataModal = NukeDataModal()
 | |
|         nukeDataModal.modalPresentationStyle = .overFullScreen
 | |
|         nukeDataModal.modalTransitionStyle = .crossDissolve
 | |
|         present(nukeDataModal, animated: true, completion: nil)
 | |
|     }
 | |
| }
 |