2021-01-28 09:10:30 +01:00
//
// T i m e l i n e S e c t i o n . s w i f t
// M a s t o d o n
//
// C r e a t e d b y s x i a o j i a n o n 2 0 2 1 / 1 / 2 7 .
//
import Combine
import CoreData
import CoreDataStack
import os . log
import UIKit
enum TimelineSection : Equatable , Hashable {
case main
}
extension TimelineSection {
static func tableViewDiffableDataSource (
for tableView : UITableView ,
dependency : NeedsDependency ,
managedObjectContext : NSManagedObjectContext ,
timestampUpdatePublisher : AnyPublisher < Date , Never > ,
2021-02-23 08:16:55 +01:00
timelinePostTableViewCellDelegate : StatusTableViewCellDelegate ,
2021-02-05 10:08:51 +01:00
timelineMiddleLoaderTableViewCellDelegate : TimelineMiddleLoaderTableViewCellDelegate ?
2021-01-28 09:10:30 +01:00
) -> UITableViewDiffableDataSource < TimelineSection , Item > {
2021-02-04 08:09:58 +01:00
UITableViewDiffableDataSource ( tableView : tableView ) { [ weak timelinePostTableViewCellDelegate , weak timelineMiddleLoaderTableViewCellDelegate ] tableView , indexPath , item -> UITableViewCell ? in
2021-01-28 09:10:30 +01:00
guard let timelinePostTableViewCellDelegate = timelinePostTableViewCellDelegate else { return UITableViewCell ( ) }
switch item {
2021-02-07 07:42:50 +01:00
case . homeTimelineIndex ( objectID : let objectID , attribute : _ ) :
2021-02-23 08:16:55 +01:00
let cell = tableView . dequeueReusableCell ( withIdentifier : String ( describing : StatusTableViewCell . self ) , for : indexPath ) as ! StatusTableViewCell
2021-02-07 07:42:50 +01:00
// c o n f i g u r e c e l l
managedObjectContext . performAndWait {
let timelineIndex = managedObjectContext . object ( with : objectID ) as ! HomeTimelineIndex
2021-02-23 12:18:34 +01:00
TimelineSection . configure ( cell : cell , readableLayoutFrame : tableView . readableContentGuide . layoutFrame , timestampUpdatePublisher : timestampUpdatePublisher , toot : timelineIndex . toot , requestUserID : timelineIndex . userID )
2021-02-07 07:42:50 +01:00
}
cell . delegate = timelinePostTableViewCellDelegate
return cell
2021-01-28 09:10:30 +01:00
case . toot ( let objectID ) :
2021-02-23 08:16:55 +01:00
let cell = tableView . dequeueReusableCell ( withIdentifier : String ( describing : StatusTableViewCell . self ) , for : indexPath ) as ! StatusTableViewCell
2021-02-08 11:29:27 +01:00
let activeMastodonAuthenticationBox = dependency . context . authenticationService . activeMastodonAuthenticationBox . value
let requestUserID = activeMastodonAuthenticationBox ? . userID ? ? " "
2021-01-28 09:10:30 +01:00
// c o n f i g u r e c e l l
managedObjectContext . performAndWait {
let toot = managedObjectContext . object ( with : objectID ) as ! Toot
2021-02-23 12:18:34 +01:00
TimelineSection . configure ( cell : cell , readableLayoutFrame : tableView . readableContentGuide . layoutFrame , timestampUpdatePublisher : timestampUpdatePublisher , toot : toot , requestUserID : requestUserID )
2021-01-28 09:10:30 +01:00
}
cell . delegate = timelinePostTableViewCellDelegate
return cell
2021-02-07 07:42:50 +01:00
case . publicMiddleLoader ( let upperTimelineTootID ) :
let cell = tableView . dequeueReusableCell ( withIdentifier : String ( describing : TimelineMiddleLoaderTableViewCell . self ) , for : indexPath ) as ! TimelineMiddleLoaderTableViewCell
cell . delegate = timelineMiddleLoaderTableViewCellDelegate
2021-02-18 05:49:24 +01:00
timelineMiddleLoaderTableViewCellDelegate ? . configure ( cell : cell , upperTimelineTootID : upperTimelineTootID , timelineIndexobjectID : nil )
2021-02-07 07:42:50 +01:00
return cell
case . homeMiddleLoader ( let upperTimelineIndexObjectID ) :
2021-02-04 08:09:58 +01:00
let cell = tableView . dequeueReusableCell ( withIdentifier : String ( describing : TimelineMiddleLoaderTableViewCell . self ) , for : indexPath ) as ! TimelineMiddleLoaderTableViewCell
cell . delegate = timelineMiddleLoaderTableViewCellDelegate
2021-02-18 05:49:24 +01:00
timelineMiddleLoaderTableViewCellDelegate ? . configure ( cell : cell , upperTimelineTootID : nil , timelineIndexobjectID : upperTimelineIndexObjectID )
2021-02-04 08:09:58 +01:00
return cell
2021-02-03 06:01:50 +01:00
case . bottomLoader :
let cell = tableView . dequeueReusableCell ( withIdentifier : String ( describing : TimelineBottomLoaderTableViewCell . self ) , for : indexPath ) as ! TimelineBottomLoaderTableViewCell
cell . activityIndicatorView . startAnimating ( )
return cell
2021-01-28 09:10:30 +01:00
}
}
}
static func configure (
2021-02-23 08:16:55 +01:00
cell : StatusTableViewCell ,
2021-02-23 12:18:34 +01:00
readableLayoutFrame : CGRect ? ,
2021-02-02 07:10:25 +01:00
timestampUpdatePublisher : AnyPublisher < Date , Never > ,
2021-02-08 11:29:27 +01:00
toot : Toot ,
requestUserID : String
2021-01-28 09:10:30 +01:00
) {
2021-02-23 08:16:55 +01:00
// s e t h e a d e r
cell . statusView . headerContainerStackView . isHidden = toot . reblog = = nil
2021-02-23 12:18:34 +01:00
cell . statusView . headerInfoLabel . text = {
let author = toot . author
let name = author . displayName . isEmpty ? author . username : author . displayName
2021-02-24 08:29:16 +01:00
return L10n . Common . Controls . Status . userBoosted ( name )
2021-02-23 12:18:34 +01:00
} ( )
2021-02-23 08:16:55 +01:00
2021-02-01 11:06:29 +01:00
// s e t n a m e u s e r n a m e a v a t a r
2021-02-23 12:18:34 +01:00
cell . statusView . nameLabel . text = {
let author = ( toot . reblog ? ? toot ) . author
return author . displayName . isEmpty ? author . username : author . displayName
} ( )
cell . statusView . usernameLabel . text = " @ " + ( toot . reblog ? ? toot ) . author . acct
cell . statusView . configure ( with : AvatarConfigurableViewConfiguration ( avatarImageURL : ( toot . reblog ? ? toot ) . author . avatarImageURL ( ) ) )
2021-02-23 08:16:55 +01:00
2021-02-01 11:06:29 +01:00
// s e t t e x t
2021-02-23 08:16:55 +01:00
cell . statusView . activeTextLabel . config ( content : ( toot . reblog ? ? toot ) . content )
2021-02-23 12:18:34 +01:00
2021-02-24 08:29:16 +01:00
// s e t c o n t e n t w a r n i n g
cell . statusView . updateContentWarningDisplay ( isHidden : ! ( toot . reblog ? ? toot ) . sensitive )
cell . statusView . contentWarningTitle . text = ( toot . reblog ? ? toot ) . spoilerText . flatMap { spoilerText in
return L10n . Common . Controls . Status . contentWarning + " : \( spoilerText ) "
} ? ? L10n . Common . Controls . Status . contentWarning
2021-02-23 12:18:34 +01:00
// p r e p a r e m e d i a a t t a c h m e n t s
let mediaAttachments = Array ( ( toot . reblog ? ? toot ) . mediaAttachments ? ? [ ] ) . sorted { $0 . index . compare ( $1 . index ) = = . orderedAscending }
// s e t i m a g e
let mosiacImageViewModel = MosaicImageViewModel ( mediaAttachments : mediaAttachments )
let imageViewMaxSize : CGSize = {
let maxWidth : CGFloat = {
// u s e t i m e l i n e P o s t V i e w w i d t h a s c o n t a i n e r w i d t h
// t h a t w i d t h f o l l o w s r e a d a b l e w i d t h a n d k e e p c o n s t a n t w i d t h a f t e r r o t a t e
let containerFrame = readableLayoutFrame ? ? cell . statusView . frame
var containerWidth = containerFrame . width
containerWidth -= 10
containerWidth -= StatusView . avatarImageSize . width
return containerWidth
} ( )
let scale : CGFloat = {
switch mosiacImageViewModel . metas . count {
case 1 : return 1.3
default : return 0.7
}
} ( )
return CGSize ( width : maxWidth , height : maxWidth * scale )
} ( )
if mosiacImageViewModel . metas . count = = 1 {
let meta = mosiacImageViewModel . metas [ 0 ]
let imageView = cell . statusView . mosaicImageView . setupImageView ( aspectRatio : meta . size , maxSize : imageViewMaxSize )
imageView . af . setImage (
withURL : meta . url ,
placeholderImage : UIImage . placeholder ( color : . systemFill ) ,
imageTransition : . crossDissolve ( 0.2 )
)
} else {
let imageViews = cell . statusView . mosaicImageView . setupImageViews ( count : mosiacImageViewModel . metas . count , maxHeight : imageViewMaxSize . height )
for ( i , imageView ) in imageViews . enumerated ( ) {
let meta = mosiacImageViewModel . metas [ i ]
imageView . af . setImage (
withURL : meta . url ,
placeholderImage : UIImage . placeholder ( color : . systemFill ) ,
imageTransition : . crossDissolve ( 0.2 )
)
}
}
cell . statusView . mosaicImageView . isHidden = mosiacImageViewModel . metas . isEmpty
2021-02-18 05:49:24 +01:00
2021-02-08 11:29:27 +01:00
// t o o l b a r
2021-02-23 08:23:18 +01:00
let replyCountTitle : String = {
let count = ( toot . reblog ? ? toot ) . repliesCount ? . intValue ? ? 0
return TimelineSection . formattedNumberTitleForActionButton ( count )
} ( )
cell . statusView . actionToolbarContainer . replyButton . setTitle ( replyCountTitle , for : . normal )
2021-02-18 05:49:24 +01:00
let isLike = ( toot . reblog ? ? toot ) . favouritedBy . flatMap { $0 . contains ( where : { $0 . id = = requestUserID } ) } ? ? false
2021-02-08 11:29:27 +01:00
let favoriteCountTitle : String = {
let count = ( toot . reblog ? ? toot ) . favouritesCount . intValue
return TimelineSection . formattedNumberTitleForActionButton ( count )
} ( )
2021-02-23 08:16:55 +01:00
cell . statusView . actionToolbarContainer . starButton . setTitle ( favoriteCountTitle , for : . normal )
cell . statusView . actionToolbarContainer . isStarButtonHighlight = isLike
2021-02-18 05:49:24 +01:00
2021-02-01 11:06:29 +01:00
// s e t d a t e
2021-02-02 07:10:25 +01:00
let createdAt = ( toot . reblog ? ? toot ) . createdAt
2021-02-23 08:16:55 +01:00
cell . statusView . dateLabel . text = createdAt . shortTimeAgoSinceNow
2021-02-02 07:10:25 +01:00
timestampUpdatePublisher
. sink { _ in
2021-02-23 08:16:55 +01:00
cell . statusView . dateLabel . text = createdAt . shortTimeAgoSinceNow
2021-02-02 07:10:25 +01:00
}
. store ( in : & cell . disposeBag )
2021-02-18 05:49:24 +01:00
2021-02-08 11:29:27 +01:00
// o b s e r v e m o d e l c h a n g e
ManagedObjectObserver . observe ( object : toot . reblog ? ? toot )
. receive ( on : DispatchQueue . main )
. sink { _ in
// d o n o t h i n g
} receiveValue : { change in
2021-02-18 05:49:24 +01:00
guard case . update ( let object ) = change . changeType ,
2021-02-08 11:29:27 +01:00
let newToot = object as ? Toot else { return }
let targetToot = newToot . reblog ? ? newToot
2021-02-18 05:49:24 +01:00
let isLike = targetToot . favouritedBy . flatMap { $0 . contains ( where : { $0 . id = = requestUserID } ) } ? ? false
2021-02-08 11:29:27 +01:00
let favoriteCount = targetToot . favouritesCount . intValue
let favoriteCountTitle = TimelineSection . formattedNumberTitleForActionButton ( favoriteCount )
2021-02-23 08:16:55 +01:00
cell . statusView . actionToolbarContainer . starButton . setTitle ( favoriteCountTitle , for : . normal )
cell . statusView . actionToolbarContainer . isStarButtonHighlight = isLike
2021-02-18 05:49:24 +01:00
os_log ( " %{public}s[%{public}ld], %{public}s: like count label for toot %s did update: %ld " , ( #file as NSString ) . lastPathComponent , #line , #function , targetToot . id , favoriteCount )
2021-02-08 11:29:27 +01:00
}
. store ( in : & cell . disposeBag )
2021-01-28 09:10:30 +01:00
}
}
extension TimelineSection {
private static func formattedNumberTitleForActionButton ( _ number : Int ? ) -> String {
guard let number = number , number > 0 else { return " " }
return String ( number )
}
}