Signal-iOS/Signal/src/ViewControllers/ConversationView/ConversationViewController+BottomBar.swift
Matthew Chen df34b8122c Migrate CVC "bottom view" logic to Swift.
Migrate CVC "bottom view" logic to Swift.
2020-10-30 09:07:37 -03:00

237 lines
6.9 KiB
Swift

//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import Foundation
public extension ConversationViewController {
@objc
func updateInputToolbar() {
AssertIsOnMainThread()
let existingDraft = inputToolbar.messageBody()
let inputToolbar = buildInputToolbar(conversationStyle: conversationStyle)
inputToolbar.setMessageBody(existingDraft, animated: false)
self.inputToolbar = inputToolbar
// reloadBottomBar is expensive and we need to avoid it while
// initially configuring the view. viewWillAppear() will call
// reloadBottomBar(). After viewWillAppear(), we need to call
// reloadBottomBar() to reflect changes in the theme.
if hasViewWillAppearOccurred {
reloadBottomBar()
}
}
@objc
func reloadBottomBar() {
AssertIsOnMainThread()
let bottomView: UIView
if let requestView = self.requestView {
bottomView = requestView
} else {
switch uiMode {
case .search:
bottomView = searchController.resultsBar
case .selection:
bottomView = selectionToolbar
case .normal:
bottomView = inputToolbar
}
}
if bottomView.superview == bottomBar && viewHasEverAppeared {
// Do nothing, the view has not changed.
return
}
for subView in bottomBar.subviews {
subView.removeFromSuperview()
}
bottomBar.addSubview(bottomView)
// The message requests view expects to extend into the safe area
if let requestView = self.requestView {
bottomView.autoPinEdgesToSuperviewEdges()
} else {
bottomView.autoPinEdgesToSuperviewMargins()
}
updateInputAccessoryPlaceholderHeight()
updateContentInsets(animated: viewHasEverAppeared)
}
@objc
func updateBottomBarPosition() {
AssertIsOnMainThread()
if let interactivePopGestureRecognizer = navigationController?.interactivePopGestureRecognizer {
// Don't update the bottom bar position if an interactive pop is in progress
switch interactivePopGestureRecognizer.state {
case .possible, .failed:
break
default:
return
}
}
bottomBarBottomConstraint?.constant = -inputAccessoryPlaceholder.keyboardOverlap
// We always want to apply the new bottom bar position immediately,
// as this only happens during animations (interactive or otherwise)
bottomBar.superview?.layoutIfNeeded()
}
@objc
func updateInputAccessoryPlaceholderHeight() {
AssertIsOnMainThread()
// If we're currently dismissing interactively, skip updating the
// input accessory height. Changing it while dismissing can lead to
// an infinite loop of keyboard frame changes as the listeners in
// InputAcessoryViewPlaceholder will end up calling back here if
// a dismissal is in progress.
if isDismissingInteractively {
return
}
// Apply any pending layout changes to ensure we're measuring the up-to-date height.
bottomBar.superview?.layoutIfNeeded()
inputAccessoryPlaceholder.desiredHeight = bottomBar.height
}
// MARK: - Message Request
@objc
func showMessageRequestDialogIfRequiredAsync() {
AssertIsOnMainThread()
DispatchQueue.main.async { [weak self] in
self?.showMessageRequestDialogIfRequired()
}
}
@objc
func showMessageRequestDialogIfRequired() {
AssertIsOnMainThread()
if threadViewModel.hasPendingMessageRequest || isLocalUserRequestingMember {
requestView?.removeFromSuperview()
if self.isLocalUserRequestingMember {
let memberRequestView = MemberRequestView(threadViewModel: threadViewModel,
fromViewController: self)
memberRequestView.delegate = self
requestView = memberRequestView
} else {
let messageRequestView = MessageRequestView(threadViewModel: threadViewModel)
messageRequestView.delegate = self
requestView = messageRequestView
}
reloadBottomBar()
} else {
if requestView != nil {
dismissMessageRequestView()
} else {
reloadBottomBar()
updateInputVisibility()
}
}
}
@objc
func updateInputVisibility() {
AssertIsOnMainThread()
if viewState.isInPreviewPlatter {
inputToolbar.isHidden = true
dismissKeyBoard()
return
}
if self.userLeftGroup {
// user has requested they leave the group. further sends disallowed
inputToolbar.isHidden = true
dismissKeyBoard()
} else {
inputToolbar.isHidden = false
}
}
@objc
func updateInputToolbarLayout() {
AssertIsOnMainThread()
inputToolbar.updateLayout(withSafeAreaInsets: view.safeAreaInsets)
}
@objc
func popKeyBoard() {
AssertIsOnMainThread()
inputToolbar.beginEditingMessage()
}
@objc
func dismissKeyBoard() {
AssertIsOnMainThread()
inputToolbar.endEditingMessage()
inputToolbar.clearDesiredKeyboard()
}
@objc
func dismissMessageRequestView() {
AssertIsOnMainThread()
guard let requestView = self.requestView else {
return
}
// Slide the request view off the bottom of the screen.
let bottomInset: CGFloat = view.safeAreaInsets.bottom
let dismissingView = requestView
self.requestView = nil
reloadBottomBar()
updateInputVisibility()
// Add the view on top of the new bottom bar (if there is one),
// and then slide it off screen to reveal the new input view.
view.addSubview(dismissingView)
dismissingView.autoPinWidthToSuperview()
dismissingView.autoPinEdge(toSuperviewEdge: .bottom)
var endFrame = dismissingView.bounds
endFrame.origin.y -= endFrame.size.height + bottomInset
UIView.animate(withDuration: 0.2, delay: 0, options: []) {
dismissingView.bounds = endFrame
} completion: { (_) in
dismissingView.removeFromSuperview()
}
}
@objc
var isLocalUserRequestingMember: Bool {
guard let groupThread = thread as? TSGroupThread else {
return false
}
return groupThread.isLocalUserRequestingMember
}
@objc
var userLeftGroup: Bool {
guard let groupThread = thread as? TSGroupThread else {
return false
}
return !groupThread.isLocalUserFullMember
}
}