use action sheet to show options for deleting a message

This commit is contained in:
Ryan Zhao 2021-08-05 15:59:23 +10:00
parent 33bd74338d
commit 8c897dcc3d
4 changed files with 46 additions and 82 deletions

View file

@ -2,82 +2,45 @@
extension ContextMenuVC { extension ContextMenuVC {
struct Action { struct Action {
let icon: UIImage? let icon: UIImage
let title: String let title: String
let tag: String
let work: () -> Void let work: () -> Void
static func reply(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action { static func reply(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
let title = "Reply" let title = "Reply"
let tag = "reply" return Action(icon: UIImage(named: "ic_reply")!, title: title) { delegate?.reply(viewItem) }
return Action(icon: UIImage(named: "ic_reply")!, title: title, tag: tag) { delegate?.reply(viewItem) }
} }
static func copy(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action { static func copy(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
let title = "Copy" let title = "Copy"
let tag = "copy" return Action(icon: UIImage(named: "ic_copy")!, title: title) { delegate?.copy(viewItem) }
return Action(icon: UIImage(named: "ic_copy")!, title: title, tag: tag) { delegate?.copy(viewItem) }
} }
static func copySessionID(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action { static func copySessionID(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
let title = "Copy Session ID" let title = "Copy Session ID"
let tag = "copySessionID" return Action(icon: UIImage(named: "ic_copy")!, title: title) { delegate?.copySessionID(viewItem) }
return Action(icon: UIImage(named: "ic_copy")!, title: title, tag: tag) { delegate?.copySessionID(viewItem) }
} }
static func delete(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action { static func delete(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
let title = "Delete" let title = "Delete"
let tag = "delete" return Action(icon: UIImage(named: "ic_trash")!, title: title) { delegate?.delete(viewItem) }
return Action(icon: UIImage(named: "ic_trash")!, title: title, tag: tag) { delegate?.delete(viewItem) }
}
static func deleteLocally(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
let title = "Delete for me"
let tag = "deleteforme"
return Action(icon: nil, title: title, tag: tag) { delegate?.deleteLocally(viewItem) }
}
static func deleteForEveryone(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
let tag = "deleteforeveryone"
var title = "Delete for everyone"
if !viewItem.isGroupThread {
title = "Delete for me and \(viewItem.interaction.thread.name())"
}
return Action(icon: nil, title: title, tag: tag) { delegate?.deleteForEveryone(viewItem) }
} }
static func save(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action { static func save(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
let title = "Save" let title = "Save"
let tag = "save" return Action(icon: UIImage(named: "ic_download")!, title: title) { delegate?.save(viewItem) }
return Action(icon: UIImage(named: "ic_download")!, title: title, tag: tag) { delegate?.save(viewItem) }
} }
static func ban(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action { static func ban(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
let title = "Ban User" let title = "Ban User"
let tag = "banUser" return Action(icon: UIImage(named: "ic_block")!, title: title) { delegate?.ban(viewItem) }
return Action(icon: UIImage(named: "ic_block")!, title: title, tag: tag) { delegate?.ban(viewItem) }
} }
static func banAndDeleteAllMessages(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action { static func banAndDeleteAllMessages(_ viewItem: ConversationViewItem, _ delegate: ContextMenuActionDelegate?) -> Action {
let title = "Ban and Delete All" let title = "Ban and Delete All"
let tag = "banAndDeleteAll" return Action(icon: UIImage(named: "ic_block")!, title: title) { delegate?.banAndDeleteAllMessages(viewItem) }
return Action(icon: UIImage(named: "ic_block")!, title: title, tag: tag) { delegate?.banAndDeleteAllMessages(viewItem) }
} }
} }
static func deleteActions(for viewItem: ConversationViewItem, delegate: ContextMenuActionDelegate?) -> [Action] {
switch viewItem.interaction.interactionType() {
case .outgoingMessage:
if let message = viewItem.interaction as? TSMessage, let _ = message.serverHash {
return [Action.deleteForEveryone(viewItem, delegate), Action.deleteLocally(viewItem, delegate)]
}
return [Action.deleteLocally(viewItem, delegate)]
case .incomingMessage:
return [Action.deleteLocally(viewItem, delegate)]
default: return [] // Should never occur
}
}
static func actions(for viewItem: ConversationViewItem, delegate: ContextMenuActionDelegate?) -> [Action] { static func actions(for viewItem: ConversationViewItem, delegate: ContextMenuActionDelegate?) -> [Action] {
func isReplyingAllowed() -> Bool { func isReplyingAllowed() -> Bool {
@ -119,14 +82,12 @@ extension ContextMenuVC {
} }
// MARK: Delegate // MARK: Delegate
protocol ContextMenuActionDelegate : class { protocol ContextMenuActionDelegate : AnyObject {
func reply(_ viewItem: ConversationViewItem) func reply(_ viewItem: ConversationViewItem)
func copy(_ viewItem: ConversationViewItem) func copy(_ viewItem: ConversationViewItem)
func copySessionID(_ viewItem: ConversationViewItem) func copySessionID(_ viewItem: ConversationViewItem)
func delete(_ viewItem: ConversationViewItem) func delete(_ viewItem: ConversationViewItem)
func deleteLocally(_ viewItem: ConversationViewItem)
func deleteForEveryone(_ viewItem: ConversationViewItem)
func save(_ viewItem: ConversationViewItem) func save(_ viewItem: ConversationViewItem)
func ban(_ viewItem: ConversationViewItem) func ban(_ viewItem: ConversationViewItem)
func banAndDeleteAllMessages(_ viewItem: ConversationViewItem) func banAndDeleteAllMessages(_ viewItem: ConversationViewItem)

View file

@ -26,25 +26,20 @@ extension ContextMenuVC {
} }
private func setUpViewHierarchy() { private func setUpViewHierarchy() {
var subviews: [UIView] = []
// Icon // Icon
if let icon = action.icon { let iconSize = ActionView.iconSize
let iconSize = ActionView.iconSize let iconImageView = UIImageView(image: action.icon.resizedImage(to: CGSize(width: iconSize, height: iconSize))!.withTint(Colors.text))
let iconImageView = UIImageView(image: icon.resizedImage(to: CGSize(width: iconSize, height: iconSize))!.withTint(Colors.text)) let iconImageViewSize = ActionView.iconImageViewSize
let iconImageViewSize = ActionView.iconImageViewSize iconImageView.set(.width, to: iconImageViewSize)
iconImageView.set(.width, to: iconImageViewSize) iconImageView.set(.height, to: iconImageViewSize)
iconImageView.set(.height, to: iconImageViewSize) iconImageView.contentMode = .center
iconImageView.contentMode = .center
subviews.append(iconImageView)
}
// Title // Title
let titleLabel = UILabel() let titleLabel = UILabel()
titleLabel.text = action.title titleLabel.text = action.title
titleLabel.textColor = Colors.text titleLabel.textColor = Colors.text
titleLabel.font = .systemFont(ofSize: Values.mediumFontSize) titleLabel.font = .systemFont(ofSize: Values.mediumFontSize)
subviews.append(titleLabel)
// Stack view // Stack view
let stackView = UIStackView(arrangedSubviews: subviews) let stackView = UIStackView(arrangedSubviews: [ iconImageView, titleLabel ])
stackView.axis = .horizontal stackView.axis = .horizontal
stackView.spacing = Values.smallSpacing stackView.spacing = Values.smallSpacing
stackView.alignment = .center stackView.alignment = .center
@ -61,7 +56,6 @@ extension ContextMenuVC {
// MARK: Interaction // MARK: Interaction
@objc private func handleTap() { @objc private func handleTap() {
action.work() action.work()
guard action.tag != "delete" else { return }
dismiss() dismiss()
} }
} }

View file

@ -75,7 +75,20 @@ final class ContextMenuVC : UIViewController {
} else { } else {
timestampLabel.pin(.left, to: .right, of: snapshot, withInset: Values.smallSpacing) timestampLabel.pin(.left, to: .right, of: snapshot, withInset: Values.smallSpacing)
} }
let menuHeight = self.updateMenu() // Menu
let menuBackgroundView = UIView()
menuBackgroundView.backgroundColor = Colors.receivedMessageBackground
menuBackgroundView.layer.cornerRadius = ContextMenuVC.menuCornerRadius
menuBackgroundView.layer.masksToBounds = true
menuView.addSubview(menuBackgroundView)
menuBackgroundView.pin(to: menuView)
let actionViews = ContextMenuVC.actions(for: viewItem, delegate: delegate).map { ActionView(for: $0, dismiss: snDismiss) }
let menuStackView = UIStackView(arrangedSubviews: actionViews)
menuStackView.axis = .vertical
menuView.addSubview(menuStackView)
menuStackView.pin(to: menuView)
view.addSubview(menuView)
let menuHeight = CGFloat(actionViews.count) * ContextMenuVC.actionViewHeight
let spacing = Values.smallSpacing let spacing = Values.smallSpacing
let margin = max(UIApplication.shared.keyWindow!.safeAreaInsets.bottom, Values.mediumSpacing) let margin = max(UIApplication.shared.keyWindow!.safeAreaInsets.bottom, Values.mediumSpacing)
if frame.maxY + spacing + menuHeight > UIScreen.main.bounds.height - margin { if frame.maxY + spacing + menuHeight > UIScreen.main.bounds.height - margin {
@ -106,25 +119,6 @@ final class ContextMenuVC : UIViewController {
super.viewDidLayoutSubviews() super.viewDidLayoutSubviews()
menuView.layer.shadowPath = UIBezierPath(roundedRect: menuView.bounds, cornerRadius: ContextMenuVC.menuCornerRadius).cgPath menuView.layer.shadowPath = UIBezierPath(roundedRect: menuView.bounds, cornerRadius: ContextMenuVC.menuCornerRadius).cgPath
} }
func updateMenu(forDelete: Bool = false) -> CGFloat {
// Menu: return the menuHeight
menuView.subviews.forEach({ $0.removeFromSuperview() })
let menuBackgroundView = UIView()
menuBackgroundView.backgroundColor = Colors.receivedMessageBackground
menuBackgroundView.layer.cornerRadius = ContextMenuVC.menuCornerRadius
menuBackgroundView.layer.masksToBounds = true
menuView.addSubview(menuBackgroundView)
menuBackgroundView.pin(to: menuView)
let actions = forDelete ? ContextMenuVC.deleteActions(for: viewItem, delegate: delegate) : ContextMenuVC.actions(for: viewItem, delegate: delegate)
let actionViews = actions.map { ActionView(for: $0, dismiss: snDismiss) }
let menuStackView = UIStackView(arrangedSubviews: actionViews)
menuStackView.axis = .vertical
menuView.addSubview(menuStackView)
menuStackView.pin(to: menuView)
view.addSubview(menuView)
return CGFloat(actionViews.count) * ContextMenuVC.actionViewHeight
}
// MARK: Interaction // MARK: Interaction
@objc private func handleTap() { @objc private func handleTap() {

View file

@ -546,7 +546,22 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
} }
func delete(_ viewItem: ConversationViewItem) { func delete(_ viewItem: ConversationViewItem) {
let _ = self.contextMenuVC?.updateMenu(forDelete: true) let alertVC = UIAlertController.init(title: nil, message: nil, preferredStyle: .actionSheet)
let deleteLocallyAction = UIAlertAction.init(title: "Delete just for me", style: .destructive) { _ in
self.deleteLocally(viewItem)
}
var title = "Delete for everyone"
if !viewItem.isGroupThread {
title = "Delete for me and \(viewItem.interaction.thread.name())"
}
let deleteRemotelyAction = UIAlertAction.init(title: title, style: .destructive) { _ in
self.deleteForEveryone(viewItem)
}
let cancelAction = UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil)
alertVC.addAction(deleteLocallyAction)
alertVC.addAction(deleteRemotelyAction)
alertVC.addAction(cancelAction)
self.navigationController?.presentAlert(alertVC)
} }
private func buildUsendRequest(_ viewItem: ConversationViewItem) -> UnsendRequest? { private func buildUsendRequest(_ viewItem: ConversationViewItem) -> UnsendRequest? {