Implement blocking/unblocking

This commit is contained in:
Niels Andriesse 2021-02-12 11:56:46 +11:00
parent f0ead8ac23
commit 78691c7b65
5 changed files with 160 additions and 1 deletions

View File

@ -218,6 +218,8 @@
B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */; };
B821494625D4D6FF009C0F2A /* URLModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B821494525D4D6FF009C0F2A /* URLModal.swift */; };
B821494F25D4E163009C0F2A /* BodyTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B821494E25D4E163009C0F2A /* BodyTextView.swift */; };
B82149B825D60393009C0F2A /* BlockedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82149B725D60393009C0F2A /* BlockedModal.swift */; };
B82149C125D605C6009C0F2A /* InfoBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82149C025D605C6009C0F2A /* InfoBanner.swift */; };
B8269D2925C7A4B400488AB4 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D2825C7A4B400488AB4 /* InputView.swift */; };
B8269D3325C7A8C600488AB4 /* InputViewButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D3225C7A8C600488AB4 /* InputViewButton.swift */; };
B8269D3D25C7B34D00488AB4 /* InputTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8269D3C25C7B34D00488AB4 /* InputTextView.swift */; };
@ -1265,6 +1267,8 @@
B80A579E23DFF1F300876683 /* NewClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewClosedGroupVC.swift; sourceTree = "<group>"; };
B821494525D4D6FF009C0F2A /* URLModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLModal.swift; sourceTree = "<group>"; };
B821494E25D4E163009C0F2A /* BodyTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BodyTextView.swift; sourceTree = "<group>"; };
B82149B725D60393009C0F2A /* BlockedModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedModal.swift; sourceTree = "<group>"; };
B82149C025D605C6009C0F2A /* InfoBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoBanner.swift; sourceTree = "<group>"; };
B8269D2825C7A4B400488AB4 /* InputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputView.swift; sourceTree = "<group>"; };
B8269D3225C7A8C600488AB4 /* InputViewButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputViewButton.swift; sourceTree = "<group>"; };
B8269D3C25C7B34D00488AB4 /* InputTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputTextView.swift; sourceTree = "<group>"; };
@ -2239,6 +2243,8 @@
B897621B25D201F7004F83B2 /* ScrollToBottomButton.swift */,
B821494525D4D6FF009C0F2A /* URLModal.swift */,
B821494E25D4E163009C0F2A /* BodyTextView.swift */,
B82149B725D60393009C0F2A /* BlockedModal.swift */,
B82149C025D605C6009C0F2A /* InfoBanner.swift */,
);
path = "Views & Modals";
sourceTree = "<group>";
@ -5028,6 +5034,7 @@
34D1F0521F7E8EA30066283D /* GiphyDownloader.swift in Sources */,
450DF2051E0D74AC003D14BE /* Platform.swift in Sources */,
4CC613362227A00400E21A3A /* ConversationSearch.swift in Sources */,
B82149B825D60393009C0F2A /* BlockedModal.swift in Sources */,
B82B408C239A068800A248E7 /* RegisterVC.swift in Sources */,
346129991FD1E4DA00532771 /* SignalApp.m in Sources */,
3496957121A301A100DCFE74 /* OWSBackupImportJob.m in Sources */,
@ -5064,6 +5071,7 @@
C331FFF42558FF0300070591 /* PNOptionView.swift in Sources */,
4C4AE6A1224AF35700D4AF6F /* SendMediaNavigationController.swift in Sources */,
45F32C222057297A00A300D5 /* MediaDetailViewController.m in Sources */,
B82149C125D605C6009C0F2A /* InfoBanner.swift in Sources */,
C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */,
B8B26C8F234D629C004ED98C /* MentionCandidateSelectionView.swift in Sources */,
B8CCF63F23975CFB0091D419 /* JoinOpenGroupVC.swift in Sources */,

View File

@ -24,6 +24,15 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
}
func handleSendButtonTapped() {
if let thread = thread as? TSContactThread {
let publicKey = thread.contactIdentifier()
guard !OWSBlockingManager.shared().isRecipientIdBlocked(publicKey) else {
let blockedModal = BlockedModal(publicKey: publicKey)
blockedModal.modalPresentationStyle = .overFullScreen
blockedModal.modalTransitionStyle = .crossDissolve
return present(blockedModal, animated: true, completion: nil)
}
}
// TODO: Attachments
let text = snInputView.text.trimmingCharacters(in: .whitespacesAndNewlines)
let thread = self.thread
@ -212,4 +221,14 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
func handleReplyButtonTapped(for viewItem: ConversationViewItem) {
reply(viewItem)
}
@objc func unblock() {
guard let thread = thread as? TSContactThread else { return }
let publicKey = thread.contactIdentifier()
UIView.animate(withDuration: 0.25, animations: {
self.blockedBanner.alpha = 0
}, completion: { _ in
OWSBlockingManager.shared().removeBlockedPhoneNumber(publicKey)
})
}
}

View File

@ -3,7 +3,6 @@
// Tapping replies
// Mentions
// Remaining send logic
// Blocking
// Subtitle
// Resending failed messages
// Tapping links in link previews
@ -69,6 +68,21 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, UITableViewD
private lazy var scrollButton = ScrollToBottomButton(delegate: self)
lazy var blockedBanner: InfoBanner = {
let name: String
if let thread = thread as? TSContactThread {
let publicKey = thread.contactIdentifier()
name = OWSProfileManager.shared().profileNameForRecipient(withID: publicKey, avoidingWriteTransaction: true) ?? publicKey
} else {
name = "Thread"
}
let message = "\(name) is blocked. Unblock them?"
let result = InfoBanner(message: message, backgroundColor: Colors.destructive)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(unblock))
result.addGestureRecognizer(tapGestureRecognizer)
return result
}()
// MARK: Settings
private static let bottomInset = Values.mediumSpacing
private static let loadMoreThreshold: CGFloat = 120
@ -101,11 +115,14 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, UITableViewD
messagesTableView.pin(to: view)
view.addSubview(scrollButton)
scrollButton.pin(.right, to: .right, of: view, withInset: -Values.mediumSpacing)
// Blocked banner
addOrRemoveBlockedBanner()
// 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)
notificationCenter.addObserver(self, selector: #selector(handleAudioDidFinishPlayingNotification(_:)), name: .SNAudioDidFinishPlaying, object: nil)
notificationCenter.addObserver(self, selector: #selector(addOrRemoveBlockedBanner), name: NSNotification.Name(rawValue: kNSNotificationName_BlockListDidChange), object: nil)
}
override func viewDidLayoutSubviews() {
@ -275,6 +292,19 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, UITableViewD
}
@objc private func addOrRemoveBlockedBanner() {
func detach() {
blockedBanner.removeFromSuperview()
}
guard let thread = thread as? TSContactThread else { return detach() }
if OWSBlockingManager.shared().isRecipientIdBlocked(thread.contactIdentifier()) {
view.addSubview(blockedBanner)
blockedBanner.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view)
} else {
detach()
}
}
// MARK: General
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension

View File

@ -0,0 +1,69 @@
final class BlockedModal : Modal {
private let publicKey: String
// MARK: Lifecycle
init(publicKey: String) {
self.publicKey = publicKey
super.init(nibName: nil, bundle: nil)
}
override init(nibName: String?, bundle: Bundle?) {
preconditionFailure("Use init(publicKey:) instead.")
}
required init?(coder: NSCoder) {
preconditionFailure("Use init(publicKey:) instead.")
}
override func populateContentView() {
// Name
let name = OWSProfileManager.shared().profileNameForRecipient(withID: publicKey, avoidingWriteTransaction: true) ?? publicKey
// Title
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.largeFontSize)
titleLabel.text = "Unblock \(name)?"
titleLabel.textAlignment = .center
// Message
let messageLabel = UILabel()
messageLabel.textColor = Colors.text
messageLabel.font = .systemFont(ofSize: Values.smallFontSize)
let message = "Are you sure you want to unblock \(name)?"
let attributedMessage = NSMutableAttributedString(string: message)
attributedMessage.addAttributes([ .font : UIFont.boldSystemFont(ofSize: Values.smallFontSize) ], range: (message as NSString).range(of: name))
messageLabel.attributedText = attributedMessage
messageLabel.numberOfLines = 0
messageLabel.lineBreakMode = .byWordWrapping
messageLabel.textAlignment = .center
// Unblock button
let unblockButton = UIButton()
unblockButton.set(.height, to: Values.mediumButtonHeight)
unblockButton.layer.cornerRadius = Values.modalButtonCornerRadius
unblockButton.backgroundColor = Colors.buttonBackground
unblockButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
unblockButton.setTitleColor(Colors.text, for: UIControl.State.normal)
unblockButton.setTitle("Unblock", for: UIControl.State.normal)
unblockButton.addTarget(self, action: #selector(unblock), for: UIControl.Event.touchUpInside)
// Button stack view
let buttonStackView = UIStackView(arrangedSubviews: [ cancelButton, unblockButton ])
buttonStackView.axis = .horizontal
buttonStackView.spacing = Values.mediumSpacing
buttonStackView.distribution = .fillEqually
// Main stack view
let mainStackView = UIStackView(arrangedSubviews: [ titleLabel, messageLabel, buttonStackView ])
mainStackView.axis = .vertical
mainStackView.spacing = Values.largeSpacing
contentView.addSubview(mainStackView)
mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing)
mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing)
contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.largeSpacing)
contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.largeSpacing)
}
// MARK: Interaction
@objc private func unblock() {
OWSBlockingManager.shared().removeBlockedPhoneNumber(publicKey)
presentingViewController?.dismiss(animated: true, completion: nil)
}
}

View File

@ -0,0 +1,33 @@
final class InfoBanner : UIView {
private let message: String
private let snBackgroundColor: UIColor
init(message: String, backgroundColor: UIColor) {
self.message = message
self.snBackgroundColor = backgroundColor
super.init(frame: CGRect.zero)
setUpViewHierarchy()
}
override init(frame: CGRect) {
preconditionFailure("Use init(message:) instead.")
}
required init?(coder: NSCoder) {
preconditionFailure("Use init(coder:) instead.")
}
private func setUpViewHierarchy() {
backgroundColor = snBackgroundColor
let label = UILabel()
label.text = message
label.font = .boldSystemFont(ofSize: Values.smallFontSize)
label.textColor = .white
label.numberOfLines = 0
label.textAlignment = .center
label.lineBreakMode = .byWordWrapping
addSubview(label)
label.pin(to: self, withInset: Values.mediumSpacing)
}
}