2023-01-21 16:25:07 +00:00
//
// C o n v e r s a t i o n M a i n S t a t u s C o l l e c t i o n V i e w C e l l . s w i f t
// T u s k e r
//
// C r e a t e d b y S h a d o w f a c t s o n 1 / 2 0 / 2 3 .
// C o p y r i g h t © 2 0 2 3 S h a d o w f a c t s . A l l r i g h t s r e s e r v e d .
//
import UIKit
import Pachyderm
import Combine
class ConversationMainStatusCollectionViewCell : UICollectionViewListCell , StatusCollectionViewCell {
static let contentFont = UIFontMetrics . default . scaledFont ( for : . systemFont ( ofSize : 18 ) )
static let monospaceFont = UIFontMetrics . default . scaledFont ( for : . monospacedSystemFont ( ofSize : 18 , weight : . regular ) )
static let contentParagraphStyle = HTMLConverter . defaultParagraphStyle
static let metaFont = UIFontMetrics ( forTextStyle : . caption1 ) . scaledFont ( for : . systemFont ( ofSize : 15 ) )
static let dateFormatter : DateFormatter = {
let formatter = DateFormatter ( )
formatter . dateStyle = . medium
formatter . timeStyle = . medium
return formatter
} ( )
// MARK: S u b v i e w s
private static let avatarImageViewSize : CGFloat = 50
private ( set ) lazy var avatarImageView = CachedImageView ( cache : . avatars ) . configure {
2023-06-27 03:47:38 +00:00
$0 . contentMode = . scaleAspectFill
2023-01-21 16:25:07 +00:00
$0 . layer . masksToBounds = true
2023-05-09 18:39:15 +00:00
$0 . layer . cornerCurve = . continuous
2023-01-21 16:25:07 +00:00
NSLayoutConstraint . activate ( [
$0 . heightAnchor . constraint ( equalToConstant : ConversationMainStatusCollectionViewCell . avatarImageViewSize ) ,
$0 . widthAnchor . constraint ( equalToConstant : ConversationMainStatusCollectionViewCell . avatarImageViewSize ) ,
] )
$0 . isUserInteractionEnabled = true
$0 . addInteraction ( UIPointerInteraction ( delegate : self ) )
$0 . addGestureRecognizer ( UITapGestureRecognizer ( target : self , action : #selector ( accountPressed ) ) )
}
2023-05-14 18:41:36 +00:00
let displayNameLabel = AccountDisplayNameLabel ( ) . configure {
2023-01-21 16:25:07 +00:00
$0 . font = UIFontMetrics ( forTextStyle : . title1 ) . scaledFont ( for : . systemFont ( ofSize : 24 , weight : . semibold ) )
$0 . adjustsFontForContentSizeCategory = true
}
private let metaIndicatorsView = StatusMetaIndicatorsView ( ) . configure {
$0 . allowedIndicators = [ . visibility , . localOnly ]
$0 . squeezeHorizontal = true
$0 . primaryAxis = . horizontal
}
let usernameLabel = UILabel ( ) . configure {
$0 . textColor = . secondaryLabel
$0 . font = UIFontMetrics ( forTextStyle : . title2 ) . scaledFont ( for : . systemFont ( ofSize : 17 , weight : . light ) )
$0 . adjustsFontForContentSizeCategory = true
}
private lazy var accountDetailContainerView = UIView ( ) . configure {
$0 . backgroundColor = . clear
$0 . addSubview ( avatarImageView )
$0 . addSubview ( displayNameLabel )
$0 . addSubview ( metaIndicatorsView )
$0 . addSubview ( usernameLabel )
avatarImageView . translatesAutoresizingMaskIntoConstraints = false
displayNameLabel . translatesAutoresizingMaskIntoConstraints = false
metaIndicatorsView . translatesAutoresizingMaskIntoConstraints = false
usernameLabel . translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint . activate ( [
avatarImageView . leadingAnchor . constraint ( equalTo : $0 . leadingAnchor ) ,
avatarImageView . topAnchor . constraint ( equalTo : $0 . topAnchor ) ,
avatarImageView . bottomAnchor . constraint ( equalTo : $0 . bottomAnchor ) ,
displayNameLabel . leadingAnchor . constraint ( equalTo : avatarImageView . trailingAnchor , constant : 8 ) ,
displayNameLabel . trailingAnchor . constraint ( equalTo : metaIndicatorsView . leadingAnchor , constant : - 8 ) ,
displayNameLabel . topAnchor . constraint ( equalTo : $0 . topAnchor ) ,
metaIndicatorsView . topAnchor . constraint ( equalTo : $0 . topAnchor ) ,
metaIndicatorsView . trailingAnchor . constraint ( equalTo : $0 . trailingAnchor ) ,
usernameLabel . leadingAnchor . constraint ( equalTo : displayNameLabel . leadingAnchor ) ,
usernameLabel . trailingAnchor . constraint ( equalTo : $0 . trailingAnchor ) ,
usernameLabel . topAnchor . constraint ( equalTo : displayNameLabel . bottomAnchor ) ,
usernameLabel . bottomAnchor . constraint ( equalTo : $0 . bottomAnchor ) ,
] )
$0 . isUserInteractionEnabled = true
$0 . addInteraction ( UIContextMenuInteraction ( delegate : self ) )
$0 . addInteraction ( UIDragInteraction ( delegate : self ) )
$0 . addGestureRecognizer ( UITapGestureRecognizer ( target : self , action : #selector ( accountPressed ) ) )
}
private lazy var accountDetailAccessibilityElement = ConversationMainStatusAccountDetailAccessibilityElement ( accessibilityContainer : self )
private ( set ) lazy var contentWarningLabel = EmojiLabel ( ) . configure {
$0 . numberOfLines = 0
$0 . textColor = . secondaryLabel
$0 . font = UIFont ( descriptor : . preferredFontDescriptor ( withTextStyle : . body ) . addingAttributes ( [
. traits : [
UIFontDescriptor . TraitKey . weight : UIFont . Weight . bold . rawValue ,
]
] ) , size : 0 )
$0 . adjustsFontForContentSizeCategory = true
// t h i s n e e d s t o h a v e a h i g h e r p r i o r t y t h a n t h e c o n t e n t c o n t a i n e r ' s z e r o h e i g h t c o n s t r a i n t
$0 . setContentHuggingPriority ( . defaultHigh , for : . vertical )
$0 . isUserInteractionEnabled = true
$0 . addGestureRecognizer ( UITapGestureRecognizer ( target : self , action : #selector ( collapseButtonPressed ) ) )
}
private ( set ) lazy var collapseButton = StatusCollapseButton ( configuration : {
var config = UIButton . Configuration . filled ( )
config . image = UIImage ( systemName : " chevron.down " )
return config
} ( ) ) . configure {
// t h i s b u t t o n i s s o b i g t h a t d i m m i n g i t s b a c k g r o u n d c o l o r i s v i s u a l l y d i s t r a c t i n g
$0 . tintAdjustmentMode = . normal
$0 . setContentHuggingPriority ( . defaultHigh , for : . vertical )
$0 . addTarget ( self , action : #selector ( collapseButtonPressed ) , for : . touchUpInside )
}
2023-05-11 18:57:47 +00:00
let contentContainer = StatusContentContainer < StatusContentTextView , StatusPollView > ( useTopSpacer : true ) . configure {
2023-01-21 16:25:07 +00:00
$0 . contentTextView . defaultFont = ConversationMainStatusCollectionViewCell . contentFont
$0 . contentTextView . monospaceFont = ConversationMainStatusCollectionViewCell . monospaceFont
$0 . contentTextView . paragraphStyle = ConversationMainStatusCollectionViewCell . contentParagraphStyle
$0 . contentTextView . isSelectable = true
$0 . contentTextView . dataDetectorTypes = [ . flightNumber , . address , . shipmentTrackingNumber , . phoneNumber ]
if #available ( iOS 16.0 , * ) {
$0 . contentTextView . dataDetectorTypes . formUnion ( [ . money , . physicalValue ] )
}
$0 . setContentHuggingPriority ( . defaultLow , for : . vertical )
}
private lazy var favoritesCountButton = UIButton ( ) . configure {
$0 . titleLabel ! . adjustsFontForContentSizeCategory = true
$0 . addTarget ( self , action : #selector ( favoritesCountPressed ) , for : . touchUpInside )
$0 . isPointerInteractionEnabled = true
}
private lazy var reblogsCountButton = UIButton ( ) . configure {
$0 . titleLabel ! . adjustsFontForContentSizeCategory = true
$0 . addTarget ( self , action : #selector ( reblogsCountPressed ) , for : . touchUpInside )
$0 . isPointerInteractionEnabled = true
}
2023-07-04 17:25:32 +00:00
// u s i n g a U I S t a c k V i e w f o r t h i s d o e s n o t l a y o u t c o r r e c t l y o n t h e f i r s t p a s s
// ( e v e r y t h i n g i s s h i f t e d s l i g h t l y t o t h e r i g h t f o r s o m e r e a s o n )
// s o d o i t m a n u a l l y , s i n c e t h e r e a r e o n l y t w o s u b v v i e w s
private lazy var actionsCountHStack = UIView ( ) . configure {
reblogsCountButton . translatesAutoresizingMaskIntoConstraints = false
$0 . addSubview ( reblogsCountButton )
favoritesCountButton . translatesAutoresizingMaskIntoConstraints = false
$0 . addSubview ( favoritesCountButton )
NSLayoutConstraint . activate ( [
reblogsCountButton . leadingAnchor . constraint ( equalTo : $0 . leadingAnchor ) ,
reblogsCountButton . topAnchor . constraint ( equalTo : $0 . topAnchor ) ,
reblogsCountButton . bottomAnchor . constraint ( equalTo : $0 . bottomAnchor ) ,
favoritesCountButton . leadingAnchor . constraint ( equalTo : reblogsCountButton . trailingAnchor , constant : 8 ) ,
favoritesCountButton . topAnchor . constraint ( equalTo : $0 . topAnchor ) ,
favoritesCountButton . bottomAnchor . constraint ( equalTo : $0 . bottomAnchor ) ,
favoritesCountButton . trailingAnchor . constraint ( equalTo : $0 . trailingAnchor ) ,
] )
2023-01-21 16:25:07 +00:00
}
private let timestampAndClientLabel = UILabel ( ) . configure {
$0 . textColor = . secondaryLabel
$0 . font = ConversationMainStatusCollectionViewCell . metaFont
$0 . adjustsFontForContentSizeCategory = true
}
2023-05-11 18:57:47 +00:00
private lazy var editTimestampButton = UIButton ( ) . configure {
2023-05-11 17:10:45 +00:00
$0 . titleLabel ! . adjustsFontForContentSizeCategory = true
2023-05-11 18:57:47 +00:00
$0 . addTarget ( self , action : #selector ( editTimestampPressed ) , for : . touchUpInside )
2023-05-11 17:10:45 +00:00
$0 . isPointerInteractionEnabled = true
}
2023-01-21 16:25:07 +00:00
private let firstSeparator = UIView ( ) . configure {
$0 . backgroundColor = . separator
NSLayoutConstraint . activate ( [
$0 . heightAnchor . constraint ( equalToConstant : 0.5 ) ,
] )
}
private let secondSeparator = UIView ( ) . configure {
$0 . backgroundColor = . separator
NSLayoutConstraint . activate ( [
$0 . heightAnchor . constraint ( equalToConstant : 0.5 ) ,
] )
}
private lazy var metaVStack = UIStackView ( arrangedSubviews : [
firstSeparator ,
actionsCountHStack ,
timestampAndClientLabel ,
2023-05-11 17:10:45 +00:00
editTimestampButton ,
2023-01-21 16:25:07 +00:00
secondSeparator ,
] ) . configure {
$0 . axis = . vertical
$0 . spacing = 4
$0 . alignment = . leading
}
private ( set ) lazy var replyButton = UIButton ( ) . configure {
$0 . setImage ( UIImage ( systemName : " arrowshape.turn.up.left.fill " ) , for : . normal )
$0 . addTarget ( self , action : #selector ( replyPressed ) , for : . touchUpInside )
$0 . addInteraction ( UIPointerInteraction ( delegate : self ) )
}
2023-05-13 17:48:49 +00:00
private ( set ) lazy var favoriteButton = ToggleableButton ( activeColor : UIColor ( displayP3Red : 1 , green : 0.8 , blue : 0 , alpha : 1 ) ) . configure {
2023-01-21 16:25:07 +00:00
$0 . setImage ( UIImage ( systemName : " star.fill " ) , for : . normal )
$0 . addTarget ( self , action : #selector ( favoritePressed ) , for : . touchUpInside )
$0 . addInteraction ( UIPointerInteraction ( delegate : self ) )
}
2023-05-13 17:48:49 +00:00
private ( set ) lazy var reblogButton = ToggleableButton ( activeColor : UIColor ( displayP3Red : 1 , green : 0.8 , blue : 0 , alpha : 1 ) ) . configure {
2023-01-21 16:25:07 +00:00
$0 . setImage ( UIImage ( systemName : " repeat " ) , for : . normal )
$0 . addTarget ( self , action : #selector ( reblogPressed ) , for : . touchUpInside )
$0 . addInteraction ( UIPointerInteraction ( delegate : self ) )
}
private ( set ) lazy var moreButton = UIButton ( ) . configure {
$0 . setImage ( UIImage ( systemName : " ellipsis " ) , for : . normal )
$0 . showsMenuAsPrimaryAction = true
$0 . addInteraction ( UIPointerInteraction ( delegate : self ) )
}
private var actionButtons : [ UIButton ] {
2023-05-16 15:28:28 +00:00
[ replyButton , reblogButton , favoriteButton , moreButton ]
2023-01-21 16:25:07 +00:00
}
private lazy var actionsHStack = UIStackView ( arrangedSubviews : [
replyButton ,
reblogButton ,
2023-05-16 15:28:28 +00:00
favoriteButton ,
2023-01-21 16:25:07 +00:00
moreButton ,
] ) . configure {
$0 . axis = . horizontal
$0 . distribution = . fillEqually
NSLayoutConstraint . activate ( [
$0 . heightAnchor . constraint ( equalToConstant : 26 ) ,
] )
}
2023-01-22 01:27:20 +00:00
private let accountDetailToContentWarningSpacer = UIView ( ) . configure {
2023-01-21 16:25:07 +00:00
$0 . backgroundColor = . clear
2023-01-22 01:27:20 +00:00
$0 . heightAnchor . constraint ( equalToConstant : 4 ) . isActive = true
2023-01-21 16:25:07 +00:00
}
private let contentWarningToCollapseButtonSpacer = UIView ( ) . configure {
$0 . backgroundColor = . clear
$0 . heightAnchor . constraint ( equalToConstant : 4 ) . isActive = true
}
private let contentToMetaSpacer = UIView ( ) . configure {
$0 . backgroundColor = . clear
$0 . heightAnchor . constraint ( equalToConstant : 8 ) . isActive = true
}
private let metaToActionsSpacer = UIView ( ) . configure {
$0 . backgroundColor = . clear
$0 . heightAnchor . constraint ( equalToConstant : 8 ) . isActive = true
}
private lazy var mainVStack = UIStackView ( arrangedSubviews : [
accountDetailContainerView ,
2023-01-22 01:27:20 +00:00
accountDetailToContentWarningSpacer ,
2023-01-21 16:25:07 +00:00
contentWarningLabel ,
contentWarningToCollapseButtonSpacer ,
collapseButton ,
contentContainer ,
contentToMetaSpacer ,
metaVStack ,
metaToActionsSpacer ,
actionsHStack ,
] ) . configure {
$0 . axis = . vertical
$0 . spacing = 0
$0 . alignment = . leading
NSLayoutConstraint . activate ( [
accountDetailContainerView . widthAnchor . constraint ( equalTo : $0 . widthAnchor ) ,
contentWarningLabel . widthAnchor . constraint ( equalTo : $0 . widthAnchor ) ,
collapseButton . widthAnchor . constraint ( equalTo : $0 . widthAnchor ) ,
contentContainer . widthAnchor . constraint ( equalTo : $0 . widthAnchor ) ,
firstSeparator . widthAnchor . constraint ( equalTo : $0 . widthAnchor ) ,
secondSeparator . widthAnchor . constraint ( equalTo : $0 . widthAnchor ) ,
actionsHStack . widthAnchor . constraint ( equalTo : $0 . widthAnchor ) ,
] )
}
var prevThreadLinkView : UIView ?
var nextThreadLinkView : UIView ?
// MARK: C e l l S t a t e
var mastodonController : MastodonController ! { delegate ? . apiController }
weak var delegate : StatusCollectionViewCellDelegate ?
var showStatusAutomatically = false
var statusID : String !
var statusState : CollapseState !
var accountID : String !
var isGrayscale = false
private var hasCreatedObservers = false
var cancellables = Set < AnyCancellable > ( )
override init ( frame : CGRect ) {
super . init ( frame : frame )
// d o n ' t s h o w s e l e c t i o n b a c k g r o u n d , b e c a u s e t h i s c e l l i s n ' t s e l e c t a b l e
automaticallyUpdatesBackgroundConfiguration = false
mainVStack . translatesAutoresizingMaskIntoConstraints = false
contentView . addSubview ( mainVStack )
NSLayoutConstraint . activate ( [
mainVStack . leadingAnchor . constraint ( equalTo : contentView . leadingAnchor , constant : 16 ) ,
mainVStack . trailingAnchor . constraint ( equalTo : contentView . trailingAnchor , constant : - 16 ) ,
mainVStack . topAnchor . constraint ( equalTo : contentView . topAnchor , constant : 8 ) ,
mainVStack . bottomAnchor . constraint ( equalTo : contentView . bottomAnchor , constant : - 8 ) ,
] )
accessibilityElements = [
accountDetailAccessibilityElement ,
contentWarningLabel ,
collapseButton ,
contentContainer . contentTextView ,
contentContainer . attachmentsView ,
contentContainer . pollView ,
favoritesCountButton ,
reblogsCountButton ,
timestampAndClientLabel ,
replyButton ,
favoriteButton ,
reblogButton ,
moreButton ,
]
NotificationCenter . default . addObserver ( self , selector : #selector ( preferencesChanged ) , name : . preferencesChanged , object : nil )
}
required init ? ( coder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
2023-02-03 04:02:11 +00:00
override func updateConfiguration ( using state : UICellConfigurationState ) {
2023-05-27 21:52:59 +00:00
var config = UIBackgroundConfiguration . listPlainCell ( ) . updated ( for : state )
// c o n v m a i n s t a t u s i s n ' t s e l e c t a b l e
if ! state . isFocused {
config . backgroundColor = . appBackground
}
backgroundConfiguration = config
2023-02-03 04:02:11 +00:00
}
2023-01-21 16:25:07 +00:00
// MARK: C o n f i g u r e U I
func updateUI ( statusID : String , state : CollapseState ) {
guard let status = mastodonController . persistentContainer . status ( for : statusID ) else {
fatalError ( )
}
createObservers ( )
self . statusID = statusID
self . statusState = state
doUpdateUI ( status : status )
2023-01-22 01:27:20 +00:00
accountDetailToContentWarningSpacer . isHidden = collapseButton . isHidden
contentWarningToCollapseButtonSpacer . isHidden = collapseButton . isHidden
2023-01-21 16:25:07 +00:00
accountDetailAccessibilityElement . navigationDelegate = delegate
accountDetailAccessibilityElement . accountID = accountID
2023-05-11 14:02:45 +00:00
var timestampAndClientText = ConversationMainStatusCollectionViewCell . dateFormatter . string ( from : status . createdAt )
if let application = status . applicationName {
timestampAndClientText += " • \( application ) "
}
timestampAndClientLabel . text = timestampAndClientText
2023-05-11 17:10:45 +00:00
if let editedAt = status . editedAt {
editTimestampButton . isHidden = false
var config = UIButton . Configuration . plain ( )
config . baseForegroundColor = . secondaryLabel
2023-05-13 17:18:57 +00:00
config . attributedTitle = AttributedString ( " Edited on \( ConversationMainStatusCollectionViewCell . dateFormatter . string ( from : editedAt ) ) " , attributes : AttributeContainer ( [
. font : ConversationMainStatusCollectionViewCell . metaFont
] ) )
2023-05-11 17:10:45 +00:00
config . contentInsets = . zero
editTimestampButton . configuration = config
} else {
editTimestampButton . isHidden = true
}
2023-05-11 14:02:45 +00:00
}
private func createObservers ( ) {
guard ! hasCreatedObservers else {
return
}
hasCreatedObservers = true
baseCreateObservers ( )
2023-01-21 16:25:07 +00:00
}
2023-05-13 17:18:57 +00:00
func updateStatusState ( status : StatusMO ) {
baseUpdateStatusState ( status : status )
let attributes = AttributeContainer ( [
. font : ConversationMainStatusCollectionViewCell . metaFont
] )
let favoritesFormat = NSLocalizedString ( " favorites count " , comment : " conv main status favorites button label " )
var favoritesConfig = UIButton . Configuration . plain ( )
favoritesConfig . baseForegroundColor = . secondaryLabel
favoritesConfig . attributedTitle = AttributedString ( String . localizedStringWithFormat ( favoritesFormat , status . favouritesCount ) , attributes : attributes )
favoritesConfig . contentInsets = . zero
favoritesCountButton . configuration = favoritesConfig
let reblogsFormat = NSLocalizedString ( " reblogs count " , comment : " conv main status reblogs button label " )
var reblogsConfig = UIButton . Configuration . plain ( )
reblogsConfig . baseForegroundColor = . secondaryLabel
reblogsConfig . attributedTitle = AttributedString ( String . localizedStringWithFormat ( reblogsFormat , status . reblogsCount ) , attributes : attributes )
reblogsConfig . contentInsets = . zero
reblogsCountButton . configuration = reblogsConfig
}
2023-05-13 19:00:03 +00:00
func estimateContentHeight ( ) -> CGFloat {
let width = bounds . width - 2 * 16
return contentContainer . estimateVisibleSubviewHeight ( effectiveWidth : width )
}
2023-01-21 16:25:07 +00:00
func updateUIForPreferences ( status : StatusMO ) {
baseUpdateUIForPreferences ( status : status )
}
@objc private func preferencesChanged ( ) {
guard let mastodonController ,
let statusID ,
let status = mastodonController . persistentContainer . status ( for : statusID ) else {
return
}
updateUIForPreferences ( status : status )
if isGrayscale != Preferences . shared . grayscaleImages {
updateGrayscaleableUI ( status : status )
}
if actionsCountHStack . isHidden != ! Preferences . shared . showFavoriteAndReblogCounts {
actionsCountHStack . isHidden = ! Preferences . shared . showFavoriteAndReblogCounts
delegate ? . statusCellNeedsReconfigure ( self , animated : true , completion : nil )
}
}
// MARK: I n t e r a c t i o n
@objc private func accountPressed ( ) {
delegate ? . selected ( account : accountID )
}
@objc private func collapseButtonPressed ( ) {
toggleCollapse ( )
}
@objc private func favoritesCountPressed ( ) {
guard let delegate else {
return
}
2023-02-06 23:10:38 +00:00
let vc = StatusActionAccountListViewController ( actionType : . favorite , statusID : statusID , statusState : statusState . copy ( ) , accountIDs : nil , mastodonController : mastodonController )
2023-02-23 03:00:12 +00:00
vc . showInaccurateCountWarning = true
2023-01-21 16:25:07 +00:00
delegate . show ( vc )
}
@objc private func reblogsCountPressed ( ) {
guard let delegate else {
return
}
2023-02-15 02:11:33 +00:00
let vc = StatusActionAccountListViewController ( actionType : . reblog , statusID : statusID , statusState : statusState . copy ( ) , accountIDs : nil , mastodonController : mastodonController )
2023-02-23 03:00:12 +00:00
vc . showInaccurateCountWarning = true
2023-01-21 16:25:07 +00:00
delegate . show ( vc )
}
@objc private func replyPressed ( ) {
delegate ? . compose ( inReplyToID : statusID )
}
@objc private func favoritePressed ( ) {
toggleFavorite ( )
}
@objc private func reblogPressed ( ) {
toggleReblog ( )
}
2023-05-11 18:57:47 +00:00
@objc private func editTimestampPressed ( ) {
delegate ? . show ( StatusEditHistoryViewController ( statusID : statusID , mastodonController : mastodonController ) , sender : nil )
}
2023-01-21 16:25:07 +00:00
}
private class ConversationMainStatusAccountDetailAccessibilityElement : UIAccessibilityElement {
var navigationDelegate : TuskerNavigationDelegate !
var mastodonController : MastodonController { navigationDelegate . apiController }
var accountID : String !
override var accessibilityLabel : String ? {
get { mastodonController . persistentContainer . account ( for : accountID ) ? . displayNameWithoutCustomEmoji }
set { }
}
override var accessibilityHint : String ? {
get { " Double tap to show profile. " }
set { }
}
override func accessibilityActivate ( ) -> Bool {
navigationDelegate . selected ( account : accountID )
return true
}
}
extension ConversationMainStatusCollectionViewCell : UIContextMenuInteractionDelegate {
func contextMenuInteraction ( _ interaction : UIContextMenuInteraction , configurationForMenuAtLocation location : CGPoint ) -> UIContextMenuConfiguration ? {
return contextMenuConfigurationForAccount ( sourceView : accountDetailContainerView )
}
func contextMenuInteraction ( _ interaction : UIContextMenuInteraction , willPerformPreviewActionForMenuWith configuration : UIContextMenuConfiguration , animator : UIContextMenuInteractionCommitAnimating ) {
if let delegate {
MenuPreviewHelper . willPerformPreviewAction ( animator : animator , presenter : delegate )
}
}
}
extension ConversationMainStatusCollectionViewCell : UIDragInteractionDelegate {
func dragInteraction ( _ interaction : UIDragInteraction , itemsForBeginning session : UIDragSession ) -> [ UIDragItem ] {
return dragItemsForAccount ( )
}
}
extension ConversationMainStatusCollectionViewCell : UIPointerInteractionDelegate {
func pointerInteraction ( _ interaction : UIPointerInteraction , regionFor request : UIPointerRegionRequest , defaultRegion : UIPointerRegion ) -> UIPointerRegion ? {
if interaction . view = = avatarImageView {
return defaultRegion
} else if let button = interaction . view as ? UIButton ,
actionButtons . contains ( button ) {
var rect = button . convert ( button . imageView ! . bounds , to : button . imageView ! )
rect = rect . insetBy ( dx : - 24 , dy : - 24 )
return UIPointerRegion ( rect : rect )
}
return nil
}
func pointerInteraction ( _ interaction : UIPointerInteraction , styleFor region : UIPointerRegion ) -> UIPointerStyle ? {
if interaction . view = = avatarImageView {
let preview = UITargetedPreview ( view : avatarImageView )
return UIPointerStyle ( effect : . lift ( preview ) )
} else if let button = interaction . view as ? UIButton ,
actionButtons . contains ( button ) {
let preview = UITargetedPreview ( view : button . imageView ! )
var rect = button . convert ( button . imageView ! . bounds , to : button . imageView ! )
rect = rect . insetBy ( dx : - 24 , dy : - 24 )
return UIPointerStyle ( effect : . highlight ( preview ) , shape : . roundedRect ( rect ) )
}
return nil
}
}