2018-08-17 02:39:16 +00:00
//
2019-11-19 17:08:11 +00:00
// B a s e S t a t u s T a b l e V i e w C e l l . s w i f t
2018-08-17 02:39:16 +00:00
// T u s k e r
//
2019-11-19 17:08:11 +00:00
// C r e a t e d b y S h a d o w f a c t s o n 1 1 / 1 9 / 1 9 .
// C o p y r i g h t © 2 0 1 9 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 .
2018-08-17 02:39:16 +00:00
//
import UIKit
2018-09-11 14:52:21 +00:00
import Pachyderm
2019-11-19 17:08:11 +00:00
import Combine
2020-10-17 16:56:13 +00:00
import AVKit
2018-08-17 02:39:16 +00:00
2019-09-09 22:40:23 +00:00
protocol StatusTableViewCellDelegate : TuskerNavigationDelegate {
2019-11-28 23:36:58 +00:00
func statusCellCollapsedStateChanged ( _ cell : BaseStatusTableViewCell )
2018-08-27 19:23:59 +00:00
}
2019-11-19 17:08:11 +00:00
class BaseStatusTableViewCell : UITableViewCell {
2020-01-05 20:25:07 +00:00
2020-01-20 04:02:07 +00:00
weak var delegate : StatusTableViewCellDelegate ? {
2018-10-12 01:20:58 +00:00
didSet {
2020-01-18 21:00:38 +00:00
contentTextView . navigationDelegate = delegate
2018-10-12 01:20:58 +00:00
}
}
2020-01-06 00:39:37 +00:00
var overrideMastodonController : MastodonController ?
var mastodonController : MastodonController ! { overrideMastodonController ? ? delegate ? . apiController }
2019-12-14 18:36:05 +00:00
2019-11-19 17:08:11 +00:00
@IBOutlet weak var avatarImageView : UIImageView !
2020-03-02 00:40:32 +00:00
@IBOutlet weak var displayNameLabel : EmojiLabel !
2018-08-17 02:39:16 +00:00
@IBOutlet weak var usernameLabel : UILabel !
2020-06-17 03:00:39 +00:00
@IBOutlet weak var visibilityImageView : UIImageView !
2020-03-12 01:56:35 +00:00
@IBOutlet weak var contentWarningLabel : EmojiLabel !
2019-09-09 22:40:23 +00:00
@IBOutlet weak var collapseButton : UIButton !
2020-01-18 21:00:38 +00:00
@IBOutlet weak var contentTextView : StatusContentTextView !
2020-10-25 20:05:28 +00:00
@IBOutlet weak var cardView : StatusCardView !
2019-06-16 23:02:18 +00:00
@IBOutlet weak var attachmentsView : AttachmentsContainerView !
2021-04-28 23:00:17 +00:00
@IBOutlet weak var pollView : StatusPollView !
2019-09-27 00:53:22 +00:00
@IBOutlet weak var replyButton : UIButton !
2018-09-09 01:35:40 +00:00
@IBOutlet weak var favoriteButton : UIButton !
@IBOutlet weak var reblogButton : UIButton !
2019-09-27 00:53:22 +00:00
@IBOutlet weak var moreButton : UIButton !
2021-01-31 22:42:29 +00:00
private ( set ) var prevThreadLinkView : UIView ?
private ( set ) var nextThreadLinkView : UIView ?
2018-08-17 02:39:16 +00:00
2018-09-18 01:57:46 +00:00
var statusID : String !
2018-09-18 16:59:07 +00:00
var accountID : String !
2019-11-19 17:08:11 +00:00
var favorited = false {
2018-09-09 01:35:40 +00:00
didSet {
favoriteButton . tintColor = favorited ? UIColor ( displayP3Red : 1 , green : 0.8 , blue : 0 , alpha : 1 ) : tintColor
}
}
2019-11-19 17:08:11 +00:00
var reblogged = false {
2018-09-09 01:35:40 +00:00
didSet {
reblogButton . tintColor = reblogged ? UIColor ( displayP3Red : 1 , green : 0.8 , blue : 0 , alpha : 1 ) : tintColor
}
}
2019-11-28 23:36:58 +00:00
var statusState : StatusState !
2019-09-09 22:40:23 +00:00
var collapsible = false {
didSet {
collapseButton . isHidden = ! collapsible
2019-11-28 23:36:58 +00:00
statusState ? . collapsible = collapsible
}
}
var collapsed = false {
didSet {
statusState ? . collapsed = collapsed
2019-09-09 22:40:23 +00:00
}
}
2019-11-19 03:23:15 +00:00
var showStatusAutomatically = false
2020-01-25 15:06:27 +00:00
private var avatarRequest : ImageCache . Request ?
2019-11-19 17:08:11 +00:00
private var statusUpdater : Cancellable ?
private var accountUpdater : Cancellable ?
2020-10-17 16:56:13 +00:00
private var currentPictureInPictureVideoStatusID : String ?
2020-11-01 18:59:58 +00:00
private var isGrayscale = false
2018-08-28 01:27:34 +00:00
override func awakeFromNib ( ) {
2019-11-19 17:08:11 +00:00
super . awakeFromNib ( )
2018-08-28 01:27:34 +00:00
displayNameLabel . addGestureRecognizer ( UITapGestureRecognizer ( target : self , action : #selector ( accountPressed ) ) )
usernameLabel . addGestureRecognizer ( UITapGestureRecognizer ( target : self , action : #selector ( accountPressed ) ) )
2020-12-14 03:37:37 +00:00
2018-08-28 01:27:34 +00:00
avatarImageView . addGestureRecognizer ( UITapGestureRecognizer ( target : self , action : #selector ( accountPressed ) ) )
avatarImageView . layer . masksToBounds = true
2020-12-14 03:37:37 +00:00
avatarImageView . addInteraction ( UIDragInteraction ( delegate : self ) )
2019-11-19 17:08:11 +00:00
2019-06-16 23:02:18 +00:00
attachmentsView . delegate = self
2019-11-19 17:08:11 +00:00
2019-09-09 22:40:23 +00:00
collapseButton . layer . masksToBounds = true
collapseButton . layer . cornerRadius = 5
2019-08-02 03:01:15 +00:00
2020-01-18 21:00:38 +00:00
accessibilityElements = [ displayNameLabel ! , contentWarningLabel ! , collapseButton ! , contentTextView ! , attachmentsView ! ]
2019-09-27 00:53:22 +00:00
attachmentsView . isAccessibilityElement = true
2021-02-06 18:47:45 +00:00
moreButton . showsMenuAsPrimaryAction = true
2020-06-27 17:13:04 +00:00
2020-06-16 03:22:34 +00:00
NotificationCenter . default . addObserver ( self , selector : #selector ( preferencesChanged ) , name : . preferencesChanged , object : nil )
2020-11-03 20:58:08 +00:00
contentWarningLabel . addGestureRecognizer ( UITapGestureRecognizer ( target : self , action : #selector ( collapseButtonPressed ) ) )
2020-01-06 00:54:28 +00:00
}
open func createObserversIfNecessary ( ) {
2020-05-02 16:45:28 +00:00
if statusUpdater = = nil {
statusUpdater = mastodonController . persistentContainer . statusSubject
. filter { [ unowned self ] in $0 = = self . statusID }
. receive ( on : DispatchQueue . main )
. sink { [ unowned self ] in
2020-08-15 21:37:56 +00:00
if let mastodonController = mastodonController ,
let status = mastodonController . persistentContainer . status ( for : $0 ) {
2020-05-02 16:45:28 +00:00
self . updateStatusState ( status : status )
}
}
}
if accountUpdater = = nil {
accountUpdater = mastodonController . persistentContainer . accountSubject
. filter { [ unowned self ] in $0 = = self . accountID }
. receive ( on : DispatchQueue . main )
. sink { [ unowned self ] in
2020-08-15 21:37:56 +00:00
if let mastodonController = mastodonController ,
let account = mastodonController . persistentContainer . account ( for : $0 ) {
2020-05-02 16:45:28 +00:00
self . updateUI ( account : account )
}
}
}
2018-08-28 01:27:34 +00:00
}
2019-11-19 17:08:11 +00:00
2020-09-17 23:38:49 +00:00
final func updateUI ( statusID : String , state : StatusState ) {
2020-01-06 00:54:28 +00:00
createObserversIfNecessary ( )
2020-04-12 16:54:27 +00:00
guard let status = mastodonController . persistentContainer . status ( for : statusID ) else {
2019-11-19 17:08:11 +00:00
fatalError ( " Missing cached status " )
}
2020-09-17 23:38:49 +00:00
2019-08-02 03:01:15 +00:00
self . statusID = statusID
2020-09-17 23:38:49 +00:00
doUpdateUI ( status : status , state : state )
}
func doUpdateUI ( status : StatusMO , state : StatusState ) {
2019-11-28 23:36:58 +00:00
self . statusState = state
2018-08-18 03:09:59 +00:00
2018-10-31 02:07:54 +00:00
let account = status . account
2018-09-18 16:59:07 +00:00
self . accountID = account . id
2019-08-02 03:01:15 +00:00
updateUI ( account : account )
2020-12-29 16:56:40 +00:00
contentTextView . setTextFrom ( status : status )
2020-11-01 18:59:58 +00:00
updateGrayscaleableUI ( account : account , status : status )
2020-09-17 23:38:49 +00:00
updateUIForPreferences ( account : account , status : status )
2019-11-19 17:08:11 +00:00
2020-10-25 20:05:28 +00:00
cardView . card = status . card
cardView . isHidden = status . card = = nil
cardView . navigationDelegate = navigationDelegate
2019-08-02 03:01:15 +00:00
attachmentsView . updateUI ( status : status )
2019-09-27 00:53:22 +00:00
attachmentsView . isAccessibilityElement = status . attachments . count > 0
attachmentsView . accessibilityLabel = String ( format : NSLocalizedString ( " %d attachments " , comment : " status attachments count accessibility label " ) , status . attachments . count )
2019-11-19 17:08:11 +00:00
updateStatusState ( status : status )
2019-11-29 02:22:13 +00:00
contentWarningLabel . text = status . spoilerText
contentWarningLabel . isHidden = status . spoilerText . isEmpty
2020-03-12 01:56:35 +00:00
if ! contentWarningLabel . isHidden {
contentWarningLabel . setEmojis ( status . emojis , identifier : statusID )
}
2019-11-29 02:22:13 +00:00
2020-06-17 02:47:30 +00:00
let reblogDisabled : Bool
2020-06-24 20:41:01 +00:00
switch mastodonController . instance ? . instanceType {
case nil :
// t o d o : t h i s h a n d l e a r a c e c o n d i t i o n i n i n s t a n c e p u b l i c t i m e l i n e s
// a s o m e w h a t b e t t e r s o l u t i o n w o u l d b e w a i t i n g t o l o a d t h e t i m e l i n e u n t i l a f t e r t h e i n s t a n c e i s l o a d e d
reblogDisabled = true
2020-06-17 02:47:30 +00:00
case . mastodon :
reblogDisabled = status . visibility = = . private || status . visibility = = . direct
case . pleroma :
// P l e r o m a a l l o w s ' B o o s t t o o r i g i n a l a u d i e n c e ' f o r y o u r o w n p r i v a t e p o s t s
reblogDisabled = status . visibility = = . direct || ( status . visibility = = . private && status . account . id != mastodonController . account . id )
}
2020-09-13 19:51:06 +00:00
reblogButton . isEnabled = ! reblogDisabled && mastodonController . loggedIn
favoriteButton . isEnabled = mastodonController . loggedIn
replyButton . isEnabled = mastodonController . loggedIn
2020-06-17 22:00:13 +00:00
updateStatusIconsForPreferences ( status )
2020-06-17 02:47:30 +00:00
2019-11-28 23:36:58 +00:00
if state . unknown {
2020-09-16 01:37:08 +00:00
state . resolveFor ( status : status , text : contentTextView . text )
if state . collapsible ! && showStatusAutomatically {
state . collapsed = false
2019-11-28 23:36:58 +00:00
}
2019-11-17 23:36:19 +00:00
}
2020-09-16 01:37:08 +00:00
collapsible = state . collapsible !
setCollapsed ( state . collapsed ! , animated : false )
2019-08-02 03:01:15 +00:00
}
2020-04-12 16:54:27 +00:00
func updateStatusState ( status : StatusMO ) {
favorited = status . favourited
reblogged = status . reblogged
2019-09-27 00:53:22 +00:00
if favorited {
favoriteButton . accessibilityLabel = NSLocalizedString ( " Undo Favorite " , comment : " undo favorite button accessibility label " )
} else {
favoriteButton . accessibilityLabel = NSLocalizedString ( " Favorite " , comment : " favorite button accessibility label " )
}
if reblogged {
reblogButton . accessibilityLabel = NSLocalizedString ( " Undo Reblog " , comment : " undo reblog button accessibility label " )
} else {
reblogButton . accessibilityLabel = NSLocalizedString ( " Reblog " , comment : " reblog button accessibility label " )
}
2020-06-27 17:13:04 +00:00
2021-02-06 18:47:45 +00:00
// k e e p m e n u i n s y n c w i t h c h a n g e d s t a t e s e . g . b o o k m a r k e d , m u t e d
moreButton . menu = UIMenu ( title : " " , image : nil , identifier : nil , options : [ ] , children : actionsForStatus ( status , sourceView : moreButton ) )
2021-04-28 23:00:17 +00:00
if let poll = status . poll {
pollView . isHidden = false
pollView . mastodonController = mastodonController
pollView . updateUI ( status : status , poll : poll )
} else {
pollView . isHidden = true
}
2019-08-02 03:01:15 +00:00
}
2020-04-12 16:54:27 +00:00
func updateUI ( account : AccountMO ) {
2018-08-18 03:09:59 +00:00
usernameLabel . text = " @ \( account . acct ) "
2018-08-21 23:23:27 +00:00
avatarImageView . image = nil
2018-08-18 03:09:59 +00:00
}
2019-11-19 17:08:11 +00:00
2020-09-17 23:38:49 +00:00
@objc private func preferencesChanged ( ) {
2020-06-17 22:00:13 +00:00
guard let mastodonController = mastodonController ,
let account = mastodonController . persistentContainer . account ( for : accountID ) ,
let status = mastodonController . persistentContainer . status ( for : statusID ) else { return }
2020-09-17 23:38:49 +00:00
updateUIForPreferences ( account : account , status : status )
2020-06-16 03:22:34 +00:00
}
2020-09-17 23:38:49 +00:00
func updateUIForPreferences ( account : AccountMO , status : StatusMO ) {
2019-08-03 00:05:47 +00:00
avatarImageView . layer . cornerRadius = Preferences . shared . avatarStyle . cornerRadius ( for : avatarImageView )
2020-12-24 22:13:44 +00:00
attachmentsView . contentHidden = Preferences . shared . blurAllMedia || status . sensitive
2020-09-17 23:38:49 +00:00
updateStatusIconsForPreferences ( status )
2020-11-01 18:59:58 +00:00
if isGrayscale != Preferences . shared . grayscaleImages {
updateGrayscaleableUI ( account : account , status : status )
}
2019-08-03 00:05:47 +00:00
}
2018-08-21 23:23:27 +00:00
2020-06-17 22:00:13 +00:00
func updateStatusIconsForPreferences ( _ status : StatusMO ) {
visibilityImageView . isHidden = ! Preferences . shared . alwaysShowStatusVisibilityIcon
if Preferences . shared . alwaysShowStatusVisibilityIcon {
visibilityImageView . image = UIImage ( systemName : status . visibility . unfilledImageName )
visibilityImageView . accessibilityLabel = String ( format : NSLocalizedString ( " Visibility: %@ " , comment : " status visibility indicator accessibility label " ) , status . visibility . displayName )
}
let reblogButtonImage : UIImage
if Preferences . shared . alwaysShowStatusVisibilityIcon || reblogButton . isEnabled {
reblogButtonImage = UIImage ( systemName : " repeat " ) !
} else {
reblogButtonImage = UIImage ( systemName : status . visibility . imageName ) !
}
reblogButton . setImage ( reblogButtonImage , for : . normal )
}
2020-11-01 18:59:58 +00:00
func updateGrayscaleableUI ( account : AccountMO , status : StatusMO ) {
isGrayscale = Preferences . shared . grayscaleImages
let avatarURL = account . avatar
let accountID = account . id
2021-01-16 20:24:15 +00:00
avatarRequest = ImageCache . avatars . get ( avatarURL ) { [ weak self ] ( _ , image ) in
guard let self = self ,
let image = image ,
self . accountID = = accountID ,
let transformedImage = ImageGrayscalifier . convertIfNecessary ( url : avatarURL , image : image ) else { return }
2020-11-01 18:59:58 +00:00
DispatchQueue . main . async {
2021-01-16 20:24:15 +00:00
self . avatarImageView . image = transformedImage
2020-11-01 18:59:58 +00:00
}
}
2020-12-29 16:56:40 +00:00
if contentTextView . hasEmojis {
contentTextView . setTextFrom ( status : status )
}
2020-11-01 18:59:58 +00:00
displayNameLabel . updateForAccountDisplayName ( account : account )
}
2021-01-31 22:42:29 +00:00
func setShowThreadLinks ( prev : Bool , next : Bool ) {
if prev {
if let prevThreadLinkView = prevThreadLinkView {
prevThreadLinkView . isHidden = false
} else {
let view = UIView ( )
view . translatesAutoresizingMaskIntoConstraints = false
view . backgroundColor = tintColor . withAlphaComponent ( 0.5 )
view . layer . cornerRadius = 2.5
view . layer . maskedCorners = [ . layerMinXMaxYCorner , . layerMaxXMaxYCorner ]
prevThreadLinkView = view
addSubview ( view )
NSLayoutConstraint . activate ( [
view . widthAnchor . constraint ( equalToConstant : 5 ) ,
view . centerXAnchor . constraint ( equalTo : avatarImageView . centerXAnchor ) ,
view . topAnchor . constraint ( equalTo : topAnchor ) ,
view . bottomAnchor . constraint ( equalTo : avatarImageView . topAnchor , constant : - 2 ) ,
] )
}
} else {
prevThreadLinkView ? . isHidden = true
}
if next {
if let nextThreadLinkView = nextThreadLinkView {
nextThreadLinkView . isHidden = false
} else {
let view = UIView ( )
view . translatesAutoresizingMaskIntoConstraints = false
view . backgroundColor = tintColor . withAlphaComponent ( 0.5 )
view . layer . cornerRadius = 2.5
view . layer . maskedCorners = [ . layerMinXMinYCorner , . layerMaxXMinYCorner ]
nextThreadLinkView = view
addSubview ( view )
NSLayoutConstraint . activate ( [
view . widthAnchor . constraint ( equalToConstant : 5 ) ,
view . centerXAnchor . constraint ( equalTo : avatarImageView . centerXAnchor ) ,
view . topAnchor . constraint ( equalTo : avatarImageView . bottomAnchor , constant : 2 ) ,
view . bottomAnchor . constraint ( equalTo : bottomAnchor ) ,
] )
}
} else {
nextThreadLinkView ? . isHidden = true
}
}
2018-08-21 23:23:27 +00:00
override func prepareForReuse ( ) {
2019-11-19 17:08:11 +00:00
super . prepareForReuse ( )
2020-01-25 15:06:27 +00:00
avatarRequest ? . cancel ( )
2019-11-17 17:52:42 +00:00
attachmentsView . attachmentViews . allObjects . forEach { $0 . removeFromSuperview ( ) }
2019-11-19 03:23:15 +00:00
showStatusAutomatically = false
2018-08-21 23:23:27 +00:00
}
2018-08-17 02:39:16 +00:00
2019-11-19 17:08:11 +00:00
@IBAction func collapseButtonPressed ( ) {
2019-09-09 22:40:23 +00:00
setCollapsed ( ! collapsed , animated : true )
2019-11-28 23:36:58 +00:00
delegate ? . statusCellCollapsedStateChanged ( self )
2019-09-09 22:40:23 +00:00
}
func setCollapsed ( _ collapsed : Bool , animated : Bool ) {
self . collapsed = collapsed
2019-11-19 17:08:11 +00:00
2020-01-18 21:00:38 +00:00
contentTextView . isHidden = collapsed
2020-10-25 20:05:28 +00:00
cardView . isHidden = cardView . card = = nil || collapsed
2019-09-09 22:40:23 +00:00
attachmentsView . isHidden = attachmentsView . attachments . count = = 0 || collapsed
2019-11-19 17:08:11 +00:00
let buttonImage = UIImage ( systemName : collapsed ? " chevron.down " : " chevron.up " ) !
2019-09-09 22:40:23 +00:00
if animated , let buttonImageView = collapseButton . imageView {
// w e n e e d t o u s e a k e y f r a m e a n i m a t i o n f o r t h i s , b e c a u s e w e w a n t t o c o n t r o l t h e d i r e c t i o n t h e c h e v r o n r o t a t e s
// w h e n r o t a t i n g ± π , U I K i t w i l l a l w a y s r o t a t e i n t h e s a m e d i r e c t i o n
// u s i n g a k e y f r a m e t o s e t a n i n t e r m e d i a t e p o i n t i n t h e a n i m a t i o n a l l o w s u s t o f o r c e a s p e c i f i c d i r e c t i o n
UIView . animateKeyframes ( withDuration : 0.2 , delay : 0 , options : . calculationModeLinear , animations : {
UIView . addKeyframe ( withRelativeStartTime : 0 , relativeDuration : 0.5 ) {
buttonImageView . transform = CGAffineTransform ( rotationAngle : collapsed ? . pi / 2 : - . pi / 2 )
}
UIView . addKeyframe ( withRelativeStartTime : 0.5 , relativeDuration : 0.5 ) {
buttonImageView . transform = CGAffineTransform ( rotationAngle : . pi )
}
} , completion : { ( finished ) in
buttonImageView . transform = . identity
self . collapseButton . setImage ( buttonImage , for : . normal )
} )
} else {
collapseButton . setImage ( buttonImage , for : . normal )
}
2019-09-27 00:53:22 +00:00
if collapsed {
collapseButton . accessibilityLabel = NSLocalizedString ( " Expand Status " , comment : " expand status button accessibility label " )
} else {
collapseButton . accessibilityLabel = NSLocalizedString ( " Collapse Status " , comment : " collapse status button accessibility label " )
}
2019-11-19 17:08:11 +00:00
2019-09-09 22:40:23 +00:00
}
2019-11-19 17:08:11 +00:00
@IBAction func replyPressed ( ) {
2020-08-31 23:28:50 +00:00
delegate ? . compose ( inReplyToID : statusID )
2018-08-31 02:30:19 +00:00
}
2019-11-19 17:08:11 +00:00
@IBAction func favoritePressed ( ) {
2020-05-02 23:52:35 +00:00
guard let status = mastodonController . persistentContainer . status ( for : statusID ) else { fatalError ( " Missing cached status \( statusID ! ) " ) }
2018-09-18 01:57:46 +00:00
2018-09-17 23:22:37 +00:00
let oldValue = favorited
2018-09-09 01:35:40 +00:00
favorited = ! favorited
2020-05-02 23:52:35 +00:00
let realStatus = status . reblog ? ? status
2020-04-27 23:32:16 +00:00
let request = ( favorited ? Status . favourite : Status . unfavourite ) ( realStatus . id )
2020-01-06 00:39:37 +00:00
mastodonController . run ( request ) { response in
2018-09-09 01:35:40 +00:00
DispatchQueue . main . async {
2019-08-02 00:03:49 +00:00
if case let . success ( newStatus , _ ) = response {
2020-05-11 21:57:50 +00:00
self . favorited = newStatus . favourited ? ? false
2020-05-02 23:52:35 +00:00
self . mastodonController . persistentContainer . addOrUpdate ( status : newStatus , incrementReferenceCount : false )
2018-09-11 14:52:21 +00:00
UIImpactFeedbackGenerator ( style : . light ) . impactOccurred ( )
} else {
2018-09-17 23:22:37 +00:00
self . favorited = oldValue
2018-09-11 14:52:21 +00:00
print ( " Couldn't favorite status \( realStatus . id ) " )
// t o d o : d i s p l a y e r r o r m e s s a g e
UINotificationFeedbackGenerator ( ) . notificationOccurred ( . error )
return
}
2018-09-09 01:35:40 +00:00
}
}
}
2019-11-19 17:08:11 +00:00
@IBAction func reblogPressed ( ) {
2020-05-02 23:52:35 +00:00
guard let status = mastodonController . persistentContainer . status ( for : statusID ) else { fatalError ( " Missing cached status \( statusID ! ) " ) }
2018-09-18 01:57:46 +00:00
2021-04-05 21:48:03 +00:00
// i f w e a r e a b o u t t o r e b l o g a n d t h e u s e r h a s c o n f i r m a t i o n e n a b l e d
if ! reblogged ,
Preferences . shared . confirmBeforeReblog {
let alert = UIAlertController ( title : " Confirm Reblog " , message : " Are you sure you want to reblog this post by @ \( status . account . acct ) ? " , preferredStyle : . alert )
alert . addAction ( UIAlertAction ( title : " Cancel " , style : . cancel , handler : nil ) )
alert . addAction ( UIAlertAction ( title : " Reblog " , style : . default ) { ( _ ) in
self . toggleReblogInternal ( )
} )
delegate ? . present ( alert , animated : true )
} else {
toggleReblogInternal ( )
}
}
private func toggleReblogInternal ( ) {
guard let status = mastodonController . persistentContainer . status ( for : statusID ) else { fatalError ( " Missing cached status \( statusID ! ) " ) }
2018-09-18 01:57:46 +00:00
let oldValue = reblogged
2018-09-09 01:35:40 +00:00
reblogged = ! reblogged
2020-05-02 23:52:35 +00:00
let realStatus = status . reblog ? ? status
2020-04-27 23:32:16 +00:00
let request = ( reblogged ? Status . reblog : Status . unreblog ) ( realStatus . id )
2020-01-06 00:39:37 +00:00
mastodonController . run ( request ) { response in
2018-09-09 01:35:40 +00:00
DispatchQueue . main . async {
2019-08-02 00:03:49 +00:00
if case let . success ( newStatus , _ ) = response {
2020-05-11 21:57:50 +00:00
self . reblogged = newStatus . reblogged ? ? false
2020-05-02 23:52:35 +00:00
self . mastodonController . persistentContainer . addOrUpdate ( status : newStatus , incrementReferenceCount : false )
2018-09-11 14:52:21 +00:00
UIImpactFeedbackGenerator ( style : . light ) . impactOccurred ( )
} else {
2018-09-18 01:57:46 +00:00
self . reblogged = oldValue
2018-09-11 14:52:21 +00:00
print ( " Couldn't reblog status \( realStatus . id ) " )
// t o d o : d i s p l a y e r r o r m e s s a g e
UINotificationFeedbackGenerator ( ) . notificationOccurred ( . error )
}
2018-09-09 01:35:40 +00:00
}
}
}
2019-11-19 17:08:11 +00:00
@IBAction func morePressed ( ) {
2020-01-18 02:29:53 +00:00
delegate ? . showMoreOptions ( forStatus : statusID , sourceView : moreButton )
2018-09-11 22:17:48 +00:00
}
2019-11-19 17:08:11 +00:00
@objc func accountPressed ( ) {
delegate ? . selected ( account : accountID )
2018-09-15 17:01:13 +00:00
}
2019-11-19 17:08:11 +00:00
func getStatusCellPreviewProviders ( for location : CGPoint , sourceViewController : UIViewController ) -> PreviewProviders ? {
return nil
2018-09-15 17:01:13 +00:00
}
}
2019-11-19 17:08:11 +00:00
extension BaseStatusTableViewCell : AttachmentViewDelegate {
2020-10-17 16:56:13 +00:00
func attachmentViewGallery ( startingAt index : Int ) -> GalleryViewController ? {
2020-09-13 17:55:33 +00:00
guard let delegate = delegate ,
let status = mastodonController . persistentContainer . status ( for : statusID ) else { return nil }
2019-06-17 02:39:46 +00:00
let sourceViews = status . attachments . map ( attachmentsView . getAttachmentView ( for : ) )
2020-10-17 16:56:13 +00:00
let gallery = delegate . gallery ( attachments : status . attachments , sourceViews : sourceViews , startIndex : index )
gallery . avPlayerViewControllerDelegate = self
return gallery
2020-03-21 02:48:28 +00:00
}
func attachmentViewPresent ( _ vc : UIViewController , animated : Bool ) {
2020-10-17 16:56:13 +00:00
delegate ? . present ( vc , animated : animated )
}
}
// t o d o : T h i s i s n o t i d e a l . I t w o r k s w h e n t h e o r i g i n a l c e l l r e m a i n s v i s i b l e a n d w h e n t h e c e l l i s r e u s e d , b u t i f t h e c e l l i s d e a l l o c ' d
// r e s u m i n g f r o m P i P w o n ' t w o r k b e c a u s e A V P l a y e r V i e w C o n t r o l l e r . d e l e g a t e i s a w e a k r e f e r e n c e .
extension BaseStatusTableViewCell : AVPlayerViewControllerDelegate {
func playerViewControllerWillStartPictureInPicture ( _ playerViewController : AVPlayerViewController ) {
// W e n e e d t o s a v e t h e c u r r e n t s t a t u s I D w h e n P i P i s i n i t i a t e d , b e c a u s e i f t h e u s e r r e s t o r e s f r o m P i P a f t e r t h i s c e l l h a s
// b e e n r e u s e d , t h e c u r r e n t v a l u e o f s t a t u s I D w i l l n o t b e c o r r e c t .
currentPictureInPictureVideoStatusID = statusID
}
func playerViewControllerDidStopPictureInPicture ( _ playerViewController : AVPlayerViewController ) {
currentPictureInPictureVideoStatusID = nil
}
func playerViewControllerShouldAutomaticallyDismissAtPictureInPictureStart ( _ playerViewController : AVPlayerViewController ) -> Bool {
// I d e a l l y , w h e n P i P i s a u t o m a t i c a l l y i n i t i a t e d b y a p p c l o s i n g t h e g a l l e r y s h o u l d n o t b e d i s m i s s e d
// a n d w h e n P i P i s s t a r t e d b e c a u s e t h e u s e r h a s t a p p e d t h e b u t t o n i n t h e p l a y e r c o n t r o l s t h e g a l l e r y
// g a l l e r y s h o u l d b e d i s m i s s e d . U n f o r t u n a t e l y , t h i s d o e s n ' t s e e m t o b e p o s s i b l e . I n s t e a d , t h e g a l l e r y i s
// a l w a y s d i s m i s s e d a n d i s r e c r e a t e d w h e n r e s t o r i n g t h e i n t e r f a c e f r o m P i P .
return true
}
func playerViewController ( _ playerViewController : AVPlayerViewController , restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler : @ escaping ( Bool ) -> Void ) {
guard let delegate = delegate ,
let playerViewController = playerViewController as ? GalleryPlayerViewController ,
let id = currentPictureInPictureVideoStatusID ,
let status = mastodonController . persistentContainer . status ( for : id ) ,
let index = status . attachments . firstIndex ( where : { $0 . id = = playerViewController . attachment ? . id } ) else {
// r e t u r n i n g w i t h o u t i n v o k i n g c o m p l e t i o n H a n d l e r w i l l d i s m i s s t h e P i P w i n d o w
return
}
// W e c r e a t e a n e w g a l l e r y v i e w c o n t r o l l e r s t a r t i n g a t t h e a p p r o p r i a t e i n d e x a n d s w a p t h e
// a l r e a d y - p l a y i n g V C i n t o t h e a p p r o p r i a t e i n d e x s o i t s m o o t h l y c o n t i n u e s p l a y i n g .
let sourceViews : [ UIImageView ? ]
if self . statusID = = id {
sourceViews = status . attachments . map ( attachmentsView . getAttachmentView ( for : ) )
} else {
sourceViews = status . attachments . map { ( _ ) in nil }
}
let gallery = delegate . gallery ( attachments : status . attachments , sourceViews : sourceViews , startIndex : index )
gallery . avPlayerViewControllerDelegate = self
// e n s u r e t h a t a l l o t h e r p a g e V C s a r e c r e a t e d
gallery . loadViewIfNeeded ( )
// r e p l a c e t h e n e w l y c r e a t e d p l a y e r f o r t h e s a m e a t t a c h m e n t w i t h t h e a l r e a d y - p l a y i n g o n e
gallery . pages [ index ] = playerViewController
gallery . setViewControllers ( [ playerViewController ] , direction : . forward , animated : false , completion : nil )
// t h i s i s n ' t a n i m a t e d , o t h e r w i s e t h e a n i m a t i o n p l a y s f i r s t a n d t h e n t h e P i P w i n d o w e x p a n d s
// w h i c h l o o k s e v e n w e i r d e r t h a n t h e b l a c k b a c k g r o u n d a p p e a r i n g i n s t a n t l y a n d t h e n t h e P i P w i n d o w a n i m a t i n g
delegate . present ( gallery , animated : false ) {
completionHandler ( false )
}
2018-09-02 20:59:20 +00:00
}
}
2018-10-12 01:20:58 +00:00
2019-11-19 17:08:11 +00:00
extension BaseStatusTableViewCell : MenuPreviewProvider {
2019-12-14 18:36:05 +00:00
var navigationDelegate : TuskerNavigationDelegate ? { return delegate }
2019-06-04 21:04:37 +00:00
func getPreviewProviders ( for location : CGPoint , sourceViewController : UIViewController ) -> PreviewProviders ? {
2020-01-05 20:25:07 +00:00
guard let mastodonController = mastodonController else { return nil }
2018-10-12 01:20:58 +00:00
if avatarImageView . frame . contains ( location ) {
2020-01-18 02:29:53 +00:00
return (
2020-07-05 20:17:56 +00:00
content : { ProfileViewController ( accountID : self . accountID , mastodonController : mastodonController ) } ,
2020-01-18 02:29:53 +00:00
actions : { self . actionsForProfile ( accountID : self . accountID , sourceView : self . avatarImageView ) }
)
2020-03-03 03:31:37 +00:00
}
2019-11-19 17:08:11 +00:00
return self . getStatusCellPreviewProviders ( for : location , sourceViewController : sourceViewController )
2018-10-12 01:20:58 +00:00
}
}
2020-12-14 03:37:37 +00:00
extension BaseStatusTableViewCell : UIDragInteractionDelegate {
func dragInteraction ( _ interaction : UIDragInteraction , itemsForBeginning session : UIDragSession ) -> [ UIDragItem ] {
2020-12-14 23:44:41 +00:00
guard let currentAccountID = mastodonController . accountInfo ? . id ,
let account = mastodonController . persistentContainer . account ( for : accountID ) else {
2020-12-14 03:37:37 +00:00
return [ ]
}
2020-12-14 23:44:41 +00:00
let provider = NSItemProvider ( object : account . url as NSURL )
let activity = UserActivityManager . showProfileActivity ( id : accountID , accountID : currentAccountID )
provider . registerObject ( activity , visibility : . all )
2020-12-14 03:37:37 +00:00
return [ UIDragItem ( itemProvider : provider ) ]
}
}