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.
593 lines
25 KiB
Swift
593 lines
25 KiB
Swift
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
import UIKit
|
|
import GRDB
|
|
import DifferenceKit
|
|
import SessionMessagingKit
|
|
import SessionUtilitiesKit
|
|
import SignalUtilitiesKit
|
|
|
|
final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConversationButtonSetDelegate, SeedReminderViewDelegate {
|
|
typealias Section = HomeViewModel.Section
|
|
typealias Item = SessionThreadViewModel
|
|
|
|
private let viewModel: HomeViewModel = HomeViewModel()
|
|
private var dataChangeObservable: DatabaseCancellable?
|
|
private var hasLoadedInitialData: Bool = false
|
|
|
|
// MARK: - Intialization
|
|
|
|
deinit {
|
|
NotificationCenter.default.removeObserver(self)
|
|
}
|
|
|
|
// MARK: - UI
|
|
|
|
private var tableViewTopConstraint: NSLayoutConstraint!
|
|
|
|
private lazy var seedReminderView: SeedReminderView = {
|
|
let result = SeedReminderView(hasContinueButton: true)
|
|
let title = "You're almost finished! 80%"
|
|
let attributedTitle = NSMutableAttributedString(string: title)
|
|
attributedTitle.addAttribute(.foregroundColor, value: Colors.accent, range: (title as NSString).range(of: "80%"))
|
|
result.title = attributedTitle
|
|
result.subtitle = NSLocalizedString("view_seed_reminder_subtitle_1", comment: "")
|
|
result.setProgress(0.8, animated: false)
|
|
result.delegate = self
|
|
result.isHidden = !self.viewModel.state.showViewedSeedBanner
|
|
|
|
return result
|
|
}()
|
|
|
|
private lazy var loadingConversationsLabel: UILabel = {
|
|
let result: UILabel = UILabel()
|
|
result.font = UIFont.systemFont(ofSize: Values.smallFontSize)
|
|
result.text = "LOADING_CONVERSATIONS".localized()
|
|
result.textColor = Colors.text
|
|
result.textAlignment = .center
|
|
result.numberOfLines = 0
|
|
|
|
return result
|
|
}()
|
|
|
|
private lazy var tableView: UITableView = {
|
|
let result = UITableView()
|
|
result.backgroundColor = .clear
|
|
result.separatorStyle = .none
|
|
result.contentInset = UIEdgeInsets(
|
|
top: 0,
|
|
left: 0,
|
|
bottom: (
|
|
Values.newConversationButtonBottomOffset +
|
|
NewConversationButtonSet.expandedButtonSize +
|
|
Values.largeSpacing +
|
|
NewConversationButtonSet.collapsedButtonSize
|
|
),
|
|
right: 0
|
|
)
|
|
result.showsVerticalScrollIndicator = false
|
|
result.register(view: MessageRequestsCell.self)
|
|
result.register(view: FullConversationCell.self)
|
|
result.dataSource = self
|
|
result.delegate = self
|
|
|
|
return result
|
|
}()
|
|
|
|
private lazy var newConversationButtonSet: NewConversationButtonSet = {
|
|
let result = NewConversationButtonSet()
|
|
result.delegate = self
|
|
return result
|
|
}()
|
|
|
|
private lazy var fadeView: UIView = {
|
|
let result = UIView()
|
|
let gradient = Gradients.homeVCFade
|
|
result.setGradient(gradient)
|
|
result.isUserInteractionEnabled = false
|
|
|
|
return result
|
|
}()
|
|
|
|
private lazy var emptyStateView: UIView = {
|
|
let explanationLabel = UILabel()
|
|
explanationLabel.textColor = Colors.text
|
|
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
|
|
explanationLabel.numberOfLines = 0
|
|
explanationLabel.lineBreakMode = .byWordWrapping
|
|
explanationLabel.textAlignment = .center
|
|
explanationLabel.text = NSLocalizedString("vc_home_empty_state_message", comment: "")
|
|
let createNewPrivateChatButton = Button(style: .prominentOutline, size: .large)
|
|
createNewPrivateChatButton.setTitle(NSLocalizedString("vc_home_empty_state_button_title", comment: ""), for: UIControl.State.normal)
|
|
createNewPrivateChatButton.addTarget(self, action: #selector(createNewDM), for: UIControl.Event.touchUpInside)
|
|
createNewPrivateChatButton.set(.width, to: Values.iPadButtonWidth)
|
|
let result = UIStackView(arrangedSubviews: [ explanationLabel, createNewPrivateChatButton ])
|
|
result.axis = .vertical
|
|
result.spacing = Values.mediumSpacing
|
|
result.alignment = .center
|
|
result.isHidden = true
|
|
|
|
return result
|
|
}()
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
// Note: This is a hack to ensure `isRTL` is initially gets run on the main thread so the value
|
|
// is cached (it gets called on background threads and if it hasn't cached the value then it can
|
|
// cause odd performance issues since it accesses UIKit)
|
|
_ = CurrentAppContext().isRTL
|
|
|
|
// Preparation
|
|
SessionApp.homeViewController.mutate { $0 = self }
|
|
|
|
// Gradient & nav bar
|
|
setUpGradientBackground()
|
|
if navigationController?.navigationBar != nil {
|
|
setUpNavBarStyle()
|
|
}
|
|
updateNavBarButtons()
|
|
setUpNavBarSessionHeading()
|
|
|
|
// Recovery phrase reminder
|
|
view.addSubview(seedReminderView)
|
|
seedReminderView.pin(.leading, to: .leading, of: view)
|
|
seedReminderView.pin(.top, to: .top, of: view)
|
|
seedReminderView.pin(.trailing, to: .trailing, of: view)
|
|
|
|
// Loading conversations label
|
|
view.addSubview(loadingConversationsLabel)
|
|
|
|
loadingConversationsLabel.pin(.top, to: .top, of: view, withInset: Values.veryLargeSpacing)
|
|
loadingConversationsLabel.pin(.leading, to: .leading, of: view, withInset: 50)
|
|
loadingConversationsLabel.pin(.trailing, to: .trailing, of: view, withInset: -50)
|
|
|
|
// Table view
|
|
view.addSubview(tableView)
|
|
tableView.pin(.leading, to: .leading, of: view)
|
|
if self.viewModel.state.showViewedSeedBanner {
|
|
tableViewTopConstraint = tableView.pin(.top, to: .bottom, of: seedReminderView)
|
|
}
|
|
else {
|
|
tableViewTopConstraint = tableView.pin(.top, to: .top, of: view, withInset: Values.smallSpacing)
|
|
}
|
|
tableView.pin(.trailing, to: .trailing, of: view)
|
|
tableView.pin(.bottom, to: .bottom, of: view)
|
|
view.addSubview(fadeView)
|
|
fadeView.pin(.leading, to: .leading, of: view)
|
|
let topInset = 0.15 * view.height()
|
|
fadeView.pin(.top, to: .top, of: view, withInset: topInset)
|
|
fadeView.pin(.trailing, to: .trailing, of: view)
|
|
fadeView.pin(.bottom, to: .bottom, of: view)
|
|
|
|
// Empty state view
|
|
view.addSubview(emptyStateView)
|
|
emptyStateView.center(.horizontal, in: view)
|
|
let verticalCenteringConstraint = emptyStateView.center(.vertical, in: view)
|
|
verticalCenteringConstraint.constant = -16 // Makes things appear centered visually
|
|
|
|
// New conversation button set
|
|
view.addSubview(newConversationButtonSet)
|
|
newConversationButtonSet.center(.horizontal, in: view)
|
|
newConversationButtonSet.pin(.bottom, to: .bottom, of: view, withInset: -Values.newConversationButtonBottomOffset) // Negative due to how the constraint is set up
|
|
|
|
// Notifications
|
|
NotificationCenter.default.addObserver(
|
|
self,
|
|
selector: #selector(applicationDidBecomeActive(_:)),
|
|
name: UIApplication.didBecomeActiveNotification,
|
|
object: nil
|
|
)
|
|
NotificationCenter.default.addObserver(
|
|
self,
|
|
selector: #selector(applicationDidResignActive(_:)),
|
|
name: UIApplication.didEnterBackgroundNotification, object: nil
|
|
)
|
|
|
|
// Start polling if needed (i.e. if the user just created or restored their Session ID)
|
|
if Identity.userExists(), let appDelegate: AppDelegate = UIApplication.shared.delegate as? AppDelegate {
|
|
appDelegate.startPollersIfNeeded()
|
|
|
|
// Do this only if we created a new Session ID, or if we already received the initial configuration message
|
|
if UserDefaults.standard[.hasSyncedInitialConfiguration] {
|
|
appDelegate.syncConfigurationIfNeeded()
|
|
}
|
|
}
|
|
|
|
// Onion request path countries cache
|
|
DispatchQueue.global(qos: .utility).sync {
|
|
let _ = IP2Country.shared.populateCacheIfNeeded()
|
|
}
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
|
|
startObservingChanges()
|
|
}
|
|
|
|
override func viewWillDisappear(_ animated: Bool) {
|
|
super.viewWillDisappear(animated)
|
|
|
|
// Stop observing database changes
|
|
dataChangeObservable?.cancel()
|
|
}
|
|
|
|
@objc func applicationDidBecomeActive(_ notification: Notification) {
|
|
startObservingChanges()
|
|
}
|
|
|
|
@objc func applicationDidResignActive(_ notification: Notification) {
|
|
// Stop observing database changes
|
|
dataChangeObservable?.cancel()
|
|
}
|
|
|
|
// MARK: - Updating
|
|
|
|
private func startObservingChanges() {
|
|
// Start observing for data changes
|
|
dataChangeObservable = GRDBStorage.shared.start(
|
|
viewModel.observableState,
|
|
// If we haven't done the initial load the trigger it immediately (blocking the main
|
|
// thread so we remain on the launch screen until it completes to be consistent with
|
|
// the old behaviour)
|
|
scheduling: (hasLoadedInitialData ?
|
|
.async(onQueue: .main) :
|
|
.immediate
|
|
),
|
|
onError: { _ in },
|
|
onChange: { [weak self] state in
|
|
// The default scheduler emits changes on the main thread
|
|
self?.handleUpdates(state)
|
|
}
|
|
)
|
|
}
|
|
|
|
private func handleUpdates(_ updatedState: HomeViewModel.State) {
|
|
// Ensure the first load runs without animations (if we don't do this the cells will animate
|
|
// in from a frame of CGRect.zero)
|
|
guard hasLoadedInitialData else {
|
|
hasLoadedInitialData = true
|
|
UIView.performWithoutAnimation { handleUpdates(updatedState) }
|
|
return
|
|
}
|
|
|
|
// Hide the 'loading conversations' label (now that we have received conversation data)
|
|
loadingConversationsLabel.isHidden = true
|
|
|
|
// Show the empty state if there is no data
|
|
emptyStateView.isHidden = (
|
|
!updatedState.sections.isEmpty &&
|
|
updatedState.sections.contains(where: { !$0.elements.isEmpty })
|
|
)
|
|
|
|
if updatedState.userProfile != self.viewModel.state.userProfile {
|
|
updateNavBarButtons()
|
|
}
|
|
|
|
// Update the 'view seed' UI
|
|
if updatedState.showViewedSeedBanner != self.viewModel.state.showViewedSeedBanner {
|
|
tableViewTopConstraint.isActive = false
|
|
seedReminderView.isHidden = !updatedState.showViewedSeedBanner
|
|
|
|
if updatedState.showViewedSeedBanner {
|
|
tableViewTopConstraint = tableView.pin(.top, to: .bottom, of: seedReminderView)
|
|
}
|
|
else {
|
|
tableViewTopConstraint = tableView.pin(.top, to: .top, of: view, withInset: Values.smallSpacing)
|
|
}
|
|
}
|
|
|
|
// Reload the table content (animate changes after the first load)
|
|
tableView.reload(
|
|
using: StagedChangeset(source: viewModel.state.sections, target: updatedState.sections),
|
|
deleteSectionsAnimation: .none,
|
|
insertSectionsAnimation: .none,
|
|
reloadSectionsAnimation: .none,
|
|
deleteRowsAnimation: .bottom,
|
|
insertRowsAnimation: .top,
|
|
reloadRowsAnimation: .none,
|
|
interrupt: { $0.changeCount > 100 } // Prevent too many changes from causing performance issues
|
|
) { [weak self] updatedSections in
|
|
guard let currentState: HomeViewModel.State = self?.viewModel.state else { return }
|
|
|
|
self?.viewModel.updateState(currentState.with(sections: updatedSections))
|
|
}
|
|
|
|
self.viewModel.updateState(
|
|
self.viewModel.state.with(showViewedSeedBanner: updatedState.showViewedSeedBanner)
|
|
)
|
|
}
|
|
|
|
private func updateNavBarButtons() {
|
|
// Profile picture view
|
|
let profilePictureSize = Values.verySmallProfilePictureSize
|
|
let profilePictureView = ProfilePictureView()
|
|
profilePictureView.accessibilityLabel = "Settings button"
|
|
profilePictureView.size = profilePictureSize
|
|
profilePictureView.update(
|
|
publicKey: getUserHexEncodedPublicKey(),
|
|
profile: Profile.fetchOrCreateCurrentUser(),
|
|
threadVariant: .contact
|
|
)
|
|
profilePictureView.set(.width, to: profilePictureSize)
|
|
profilePictureView.set(.height, to: profilePictureSize)
|
|
|
|
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings))
|
|
profilePictureView.addGestureRecognizer(tapGestureRecognizer)
|
|
|
|
// Path status indicator
|
|
let pathStatusView = PathStatusView()
|
|
pathStatusView.accessibilityLabel = "Current onion routing path indicator"
|
|
pathStatusView.set(.width, to: PathStatusView.size)
|
|
pathStatusView.set(.height, to: PathStatusView.size)
|
|
|
|
// Container view
|
|
let profilePictureViewContainer = UIView()
|
|
profilePictureViewContainer.accessibilityLabel = "Settings button"
|
|
profilePictureViewContainer.addSubview(profilePictureView)
|
|
profilePictureView.autoPinEdgesToSuperviewEdges()
|
|
profilePictureViewContainer.addSubview(pathStatusView)
|
|
pathStatusView.pin(.trailing, to: .trailing, of: profilePictureViewContainer)
|
|
pathStatusView.pin(.bottom, to: .bottom, of: profilePictureViewContainer)
|
|
|
|
// Left bar button item
|
|
let leftBarButtonItem = UIBarButtonItem(customView: profilePictureViewContainer)
|
|
leftBarButtonItem.accessibilityLabel = "Settings button"
|
|
leftBarButtonItem.isAccessibilityElement = true
|
|
navigationItem.leftBarButtonItem = leftBarButtonItem
|
|
|
|
// Right bar button item - search button
|
|
let rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(showSearchUI))
|
|
rightBarButtonItem.accessibilityLabel = "Search button"
|
|
rightBarButtonItem.isAccessibilityElement = true
|
|
navigationItem.rightBarButtonItem = rightBarButtonItem
|
|
}
|
|
|
|
@objc override internal func handleAppModeChangedNotification(_ notification: Notification) {
|
|
super.handleAppModeChangedNotification(notification)
|
|
|
|
let gradient = Gradients.homeVCFade
|
|
fadeView.setGradient(gradient) // Re-do the gradient
|
|
tableView.reloadData()
|
|
}
|
|
|
|
// MARK: - UITableViewDataSource
|
|
|
|
func numberOfSections(in tableView: UITableView) -> Int {
|
|
return viewModel.state.sections.count
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
return viewModel.state.sections[section].elements.count
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
let section: ArraySection<Section, Item> = viewModel.state.sections[indexPath.section]
|
|
|
|
switch section.model {
|
|
case .messageRequests:
|
|
let cell: MessageRequestsCell = tableView.dequeue(type: MessageRequestsCell.self, for: indexPath)
|
|
cell.update(with: Int(section.elements[indexPath.row].threadUnreadCount ?? 0))
|
|
return cell
|
|
|
|
case .threads:
|
|
let cell: FullConversationCell = tableView.dequeue(type: FullConversationCell.self, for: indexPath)
|
|
cell.update(with: section.elements[indexPath.row])
|
|
return cell
|
|
}
|
|
}
|
|
|
|
// MARK: - UITableViewDelegate
|
|
|
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
|
|
let section: ArraySection<Section, Item> = viewModel.state.sections[indexPath.section]
|
|
|
|
switch section.model {
|
|
case .messageRequests:
|
|
let viewController: MessageRequestsViewController = MessageRequestsViewController()
|
|
self.navigationController?.pushViewController(viewController, animated: true)
|
|
|
|
case .threads:
|
|
show(
|
|
section.elements[indexPath.row].threadId,
|
|
variant: section.elements[indexPath.row].threadVariant,
|
|
with: .none,
|
|
focusedInteractionId: nil,
|
|
animated: true
|
|
)
|
|
}
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
|
return true
|
|
}
|
|
|
|
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
|
|
let section: ArraySection<Section, Item> = viewModel.state.sections[indexPath.section]
|
|
|
|
switch section.model {
|
|
case .messageRequests:
|
|
let hide = UITableViewRowAction(style: .destructive, title: "TXT_HIDE_TITLE".localized()) { _, _ in
|
|
GRDBStorage.shared.write { db in db[.hasHiddenMessageRequests] = true }
|
|
}
|
|
hide.backgroundColor = Colors.destructive
|
|
|
|
return [hide]
|
|
|
|
case .threads:
|
|
let cellViewModel: SessionThreadViewModel = section.elements[indexPath.row]
|
|
let delete: UITableViewRowAction = UITableViewRowAction(
|
|
style: .destructive,
|
|
title: "TXT_DELETE_TITLE".localized()
|
|
) { [weak self] _, _ in
|
|
let message = (cellViewModel.currentUserIsClosedGroupAdmin == true ?
|
|
"admin_group_leave_warning".localized() :
|
|
"CONVERSATION_DELETE_CONFIRMATION_ALERT_MESSAGE".localized()
|
|
)
|
|
|
|
let alert = UIAlertController(
|
|
title: "CONVERSATION_DELETE_CONFIRMATION_ALERT_TITLE".localized(),
|
|
message: message,
|
|
preferredStyle: .alert
|
|
)
|
|
alert.addAction(UIAlertAction(
|
|
title: "TXT_DELETE_TITLE".localized(),
|
|
style: .destructive
|
|
) { _ in
|
|
GRDBStorage.shared.writeAsync { db in
|
|
switch cellViewModel.threadVariant {
|
|
case .closedGroup:
|
|
try MessageSender
|
|
.leave(db, groupPublicKey: cellViewModel.threadId)
|
|
.retainUntilComplete()
|
|
|
|
case .openGroup:
|
|
OpenGroupManager.shared.delete(db, openGroupId: cellViewModel.threadId)
|
|
|
|
default: break
|
|
}
|
|
|
|
_ = try SessionThread
|
|
.filter(id: cellViewModel.threadId)
|
|
.deleteAll(db)
|
|
}
|
|
})
|
|
alert.addAction(UIAlertAction(
|
|
title: "TXT_CANCEL_TITLE".localized(),
|
|
style: .default
|
|
))
|
|
|
|
self?.present(alert, animated: true, completion: nil)
|
|
}
|
|
delete.backgroundColor = Colors.destructive
|
|
|
|
let pin: UITableViewRowAction = UITableViewRowAction(
|
|
style: .normal,
|
|
title: (cellViewModel.threadIsPinned ?
|
|
"UNPIN_BUTTON_TEXT".localized() :
|
|
"PIN_BUTTON_TEXT".localized()
|
|
)
|
|
) { _, _ in
|
|
GRDBStorage.shared.writeAsync { db in
|
|
try SessionThread
|
|
.filter(id: cellViewModel.threadId)
|
|
.updateAll(db, SessionThread.Columns.isPinned.set(to: !cellViewModel.threadIsPinned))
|
|
}
|
|
}
|
|
|
|
guard cellViewModel.threadVariant == .contact && !cellViewModel.threadIsNoteToSelf else {
|
|
return [ delete, pin ]
|
|
}
|
|
|
|
let block: UITableViewRowAction = UITableViewRowAction(
|
|
style: .normal,
|
|
title: (cellViewModel.threadIsBlocked == true ?
|
|
"BLOCK_LIST_UNBLOCK_BUTTON".localized() :
|
|
"BLOCK_LIST_BLOCK_BUTTON".localized()
|
|
)
|
|
) { _, _ in
|
|
GRDBStorage.shared.writeAsync { db in
|
|
try Contact
|
|
.filter(id: cellViewModel.threadId)
|
|
.updateAll(
|
|
db,
|
|
Contact.Columns.isBlocked.set(
|
|
to: (cellViewModel.threadIsBlocked == false ?
|
|
true:
|
|
false
|
|
)
|
|
)
|
|
)
|
|
try MessageSender.syncConfiguration(db, forceSyncNow: true)
|
|
.retainUntilComplete()
|
|
}
|
|
}
|
|
block.backgroundColor = Colors.unimportant
|
|
|
|
return [ delete, block, pin ]
|
|
}
|
|
}
|
|
|
|
// MARK: - Interaction
|
|
|
|
func handleContinueButtonTapped(from seedReminderView: SeedReminderView) {
|
|
let seedVC = SeedVC()
|
|
let navigationController = OWSNavigationController(rootViewController: seedVC)
|
|
present(navigationController, animated: true, completion: nil)
|
|
}
|
|
|
|
func show(
|
|
_ threadId: String,
|
|
variant: SessionThread.Variant,
|
|
with action: ConversationViewModel.Action,
|
|
focusedInteractionId: Int64?,
|
|
animated: Bool
|
|
) {
|
|
if let presentedVC = self.presentedViewController {
|
|
presentedVC.dismiss(animated: false, completion: nil)
|
|
}
|
|
|
|
let conversationVC: ConversationVC = ConversationVC(threadId: threadId, threadVariant: variant, focusedInteractionId: focusedInteractionId)
|
|
self.navigationController?.setViewControllers([ self, conversationVC ], animated: true)
|
|
}
|
|
|
|
@objc private func openSettings() {
|
|
let settingsVC = SettingsVC()
|
|
let navigationController = OWSNavigationController(rootViewController: settingsVC)
|
|
navigationController.modalPresentationStyle = .fullScreen
|
|
present(navigationController, animated: true, completion: nil)
|
|
}
|
|
|
|
@objc private func showSearchUI() {
|
|
if let presentedVC = self.presentedViewController {
|
|
presentedVC.dismiss(animated: false, completion: nil)
|
|
}
|
|
let searchController = GlobalSearchViewController()
|
|
self.navigationController?.setViewControllers([ self, searchController ], animated: true)
|
|
}
|
|
|
|
@objc func joinOpenGroup() {
|
|
let joinOpenGroupVC: JoinOpenGroupVC = JoinOpenGroupVC()
|
|
let navigationController: OWSNavigationController = OWSNavigationController(rootViewController: joinOpenGroupVC)
|
|
|
|
if UIDevice.current.isIPad {
|
|
navigationController.modalPresentationStyle = .fullScreen
|
|
}
|
|
|
|
present(navigationController, animated: true, completion: nil)
|
|
}
|
|
|
|
@objc func createNewDM() {
|
|
let newDMVC = NewDMVC()
|
|
let navigationController = OWSNavigationController(rootViewController: newDMVC)
|
|
if UIDevice.current.isIPad {
|
|
navigationController.modalPresentationStyle = .fullScreen
|
|
}
|
|
present(navigationController, animated: true, completion: nil)
|
|
}
|
|
|
|
@objc(createNewDMFromDeepLink:)
|
|
func createNewDMFromDeepLink(sessionID: String) {
|
|
let newDMVC = NewDMVC(sessionID: sessionID)
|
|
let navigationController = OWSNavigationController(rootViewController: newDMVC)
|
|
if UIDevice.current.isIPad {
|
|
navigationController.modalPresentationStyle = .fullScreen
|
|
}
|
|
present(navigationController, animated: true, completion: nil)
|
|
}
|
|
|
|
@objc func createClosedGroup() {
|
|
let newClosedGroupVC = NewClosedGroupVC()
|
|
let navigationController = OWSNavigationController(rootViewController: newClosedGroupVC)
|
|
if UIDevice.current.isIPad {
|
|
navigationController.modalPresentationStyle = .fullScreen
|
|
}
|
|
present(navigationController, animated: true, completion: nil)
|
|
}
|
|
}
|