2021-02-08 11:29:27 +01:00
//
// S t a t u s P r o v i d e r F a c a d e . 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 / 2 / 8 .
//
import os . log
import UIKit
import Combine
import CoreData
import CoreDataStack
import MastodonSDK
import ActiveLabel
2021-06-28 13:41:41 +02:00
import Meta
import MetaTextView
2021-06-22 07:41:40 +02:00
#if ASDK
2021-06-20 18:14:47 +02:00
import AsyncDisplayKit
2021-06-22 07:41:40 +02:00
#endif
2021-02-08 11:29:27 +01:00
2021-04-01 08:39:15 +02:00
enum StatusProviderFacade { }
2021-02-08 11:29:27 +01:00
2021-04-01 08:39:15 +02:00
extension StatusProviderFacade {
static func coordinateToStatusAuthorProfileScene ( for target : Target , provider : StatusProvider ) {
_coordinateToStatusAuthorProfileScene (
for : target ,
provider : provider ,
status : provider . status ( )
)
}
2021-05-21 09:23:02 +02:00
static func coordinateToStatusAuthorProfileScene ( for target : Target , provider : StatusProvider , indexPath : IndexPath ) {
_coordinateToStatusAuthorProfileScene (
for : target ,
provider : provider ,
status : provider . status ( for : nil , indexPath : indexPath )
)
}
2021-04-01 08:39:15 +02:00
static func coordinateToStatusAuthorProfileScene ( for target : Target , provider : StatusProvider , cell : UITableViewCell ) {
_coordinateToStatusAuthorProfileScene (
for : target ,
provider : provider ,
status : provider . status ( for : cell , indexPath : nil )
)
}
private static func _coordinateToStatusAuthorProfileScene ( for target : Target , provider : StatusProvider , status : Future < Status ? , Never > ) {
status
. sink { [ weak provider ] status in
guard let provider = provider else { return }
let _status : Status ? = {
switch target {
case . primary : return status ? . reblog ? ? status // o r i g i n a l s t a t u s
case . secondary : return status ? . replyTo ? ? status // r e b l o g o r r e p l y t o s t a t u s
}
} ( )
guard let status = _status else { return }
let mastodonUser = status . author
let profileViewModel = CachedProfileViewModel ( context : provider . context , mastodonUser : mastodonUser )
DispatchQueue . main . async {
if provider . navigationController = = nil {
let from = provider . presentingViewController ? ? provider
provider . dismiss ( animated : true ) {
provider . coordinator . present ( scene : . profile ( viewModel : profileViewModel ) , from : from , transition : . show )
}
} else {
provider . coordinator . present ( scene : . profile ( viewModel : profileViewModel ) , from : provider , transition : . show )
}
}
}
. store ( in : & provider . disposeBag )
}
2021-04-13 13:46:42 +02:00
}
extension StatusProviderFacade {
static func coordinateToStatusThreadScene ( for target : Target , provider : StatusProvider , indexPath : IndexPath ) {
_coordinateToStatusThreadScene (
for : target ,
provider : provider ,
status : provider . status ( for : nil , indexPath : indexPath )
)
}
static func coordinateToStatusThreadScene ( for target : Target , provider : StatusProvider , cell : UITableViewCell ) {
_coordinateToStatusThreadScene (
for : target ,
provider : provider ,
status : provider . status ( for : cell , indexPath : nil )
)
}
private static func _coordinateToStatusThreadScene ( for target : Target , provider : StatusProvider , status : Future < Status ? , Never > ) {
status
. sink { [ weak provider ] status in
guard let provider = provider else { return }
let _status : Status ? = {
switch target {
case . primary : return status ? . reblog ? ? status // o r i g i n a l s t a t u s
case . secondary : return status // r e b l o g o r s t a t u s
}
} ( )
guard let status = _status else { return }
let threadViewModel = CachedThreadViewModel ( context : provider . context , status : status )
DispatchQueue . main . async {
if provider . navigationController = = nil {
let from = provider . presentingViewController ? ? provider
provider . dismiss ( animated : true ) {
provider . coordinator . present ( scene : . thread ( viewModel : threadViewModel ) , from : from , transition : . show )
}
} else {
provider . coordinator . present ( scene : . thread ( viewModel : threadViewModel ) , from : provider , transition : . show )
}
}
}
. store ( in : & provider . disposeBag )
}
2021-02-08 11:29:27 +01:00
}
2021-03-09 08:18:43 +01:00
2021-04-02 13:33:29 +02:00
extension StatusProviderFacade {
static func responseToStatusActiveLabelAction ( provider : StatusProvider , cell : UITableViewCell , activeLabel : ActiveLabel , didTapEntity entity : ActiveEntity ) {
switch entity . type {
2021-07-08 10:55:46 +02:00
case . url ( _ , _ , let url , _ ) ,
. mention ( let url , _ ) where url . lowercased ( ) . hasPrefix ( " http " ) :
// n o t e :
// s o m e s e r v e r m a r k t h e n o r m a l u r l a s " u - u r l " c l a s s . :
2021-04-02 13:33:29 +02:00
guard let url = URL ( string : url ) else { return }
2021-05-27 09:55:37 +02:00
if let domain = provider . context . authenticationService . activeMastodonAuthenticationBox . value ? . domain , url . host = = domain ,
url . pathComponents . count >= 4 ,
url . pathComponents [ 0 ] = = " / " ,
url . pathComponents [ 1 ] = = " web " ,
url . pathComponents [ 2 ] = = " statuses " {
let statusID = url . pathComponents [ 3 ]
let threadViewModel = RemoteThreadViewModel ( context : provider . context , statusID : statusID )
provider . coordinator . present ( scene : . thread ( viewModel : threadViewModel ) , from : nil , transition : . show )
} else {
provider . coordinator . present ( scene : . safari ( url : url ) , from : nil , transition : . safariPresent ( animated : true , completion : nil ) )
}
2021-07-08 10:55:46 +02:00
case . hashtag ( let text , _ ) :
2021-07-13 11:39:38 +02:00
let hashtagTimelineViewModel = HashtagTimelineViewModel ( context : provider . context , hashtag : text )
provider . coordinator . present ( scene : . hashtagTimeline ( viewModel : hashtagTimelineViewModel ) , from : provider , transition : . show )
2021-07-08 10:55:46 +02:00
case . mention ( let text , let userInfo ) :
let href = userInfo ? [ " href " ] as ? String
coordinateToStatusMentionProfileScene ( for : . primary , provider : provider , cell : cell , mention : text , href : href )
2021-04-02 13:33:29 +02:00
default :
break
}
}
2021-06-20 18:14:47 +02:00
2021-06-28 13:41:41 +02:00
static func responseToStatusMetaTextAction ( provider : StatusProvider , cell : UITableViewCell , metaText : MetaText , didSelectMeta meta : Meta ) {
switch meta {
2021-07-08 10:55:46 +02:00
case . url ( _ , _ , let url , _ ) ,
. mention ( _ , let url , _ ) where url . lowercased ( ) . hasPrefix ( " http " ) :
// n o t e :
// s o m e s e r v e r m a r k t h e n o r m a l u r l a s " u - u r l " c l a s s . h i g h l i g h t e d c o n t e n t i s a U R L
2021-06-28 13:41:41 +02:00
guard let url = URL ( string : url ) else { return }
if let domain = provider . context . authenticationService . activeMastodonAuthenticationBox . value ? . domain , url . host = = domain ,
url . pathComponents . count >= 4 ,
url . pathComponents [ 0 ] = = " / " ,
url . pathComponents [ 1 ] = = " web " ,
url . pathComponents [ 2 ] = = " statuses " {
let statusID = url . pathComponents [ 3 ]
let threadViewModel = RemoteThreadViewModel ( context : provider . context , statusID : statusID )
provider . coordinator . present ( scene : . thread ( viewModel : threadViewModel ) , from : nil , transition : . show )
} else {
provider . coordinator . present ( scene : . safari ( url : url ) , from : nil , transition : . safariPresent ( animated : true , completion : nil ) )
}
case . hashtag ( _ , let hashtag , _ ) :
let hashtagTimelineViewModel = HashtagTimelineViewModel ( context : provider . context , hashtag : hashtag )
provider . coordinator . present ( scene : . hashtagTimeline ( viewModel : hashtagTimelineViewModel ) , from : provider , transition : . show )
2021-07-08 10:55:46 +02:00
case . mention ( _ , let mention , let userInfo ) :
let href = userInfo ? [ " href " ] as ? String
coordinateToStatusMentionProfileScene ( for : . primary , provider : provider , cell : cell , mention : mention , href : href )
2021-06-28 13:41:41 +02:00
default :
break
}
}
2021-06-22 07:41:40 +02:00
#if ASDK
2021-06-20 18:14:47 +02:00
static func responseToStatusActiveLabelAction ( provider : StatusProvider , node : ASCellNode , didSelectActiveEntityType type : ActiveEntityType ) {
switch type {
case . hashtag ( let text , _ ) :
2021-07-12 14:00:50 +02:00
let hashtagTimelineViewModel = HashtagTimelineViewModel ( context : provider . context , hashtag : text )
provider . coordinator . present ( scene : . hashtagTimeline ( viewModel : hashtagTimelineViewModel ) , from : provider , transition : . show )
2021-06-20 18:14:47 +02:00
case . mention ( let text , _ ) :
coordinateToStatusMentionProfileScene ( for : . primary , provider : provider , node : node , mention : text )
case . url ( _ , _ , let url , _ ) :
guard let url = URL ( string : url ) else { return }
if let domain = provider . context . authenticationService . activeMastodonAuthenticationBox . value ? . domain , url . host = = domain ,
url . pathComponents . count >= 4 ,
url . pathComponents [ 0 ] = = " / " ,
url . pathComponents [ 1 ] = = " web " ,
url . pathComponents [ 2 ] = = " statuses " {
let statusID = url . pathComponents [ 3 ]
let threadViewModel = RemoteThreadViewModel ( context : provider . context , statusID : statusID )
provider . coordinator . present ( scene : . thread ( viewModel : threadViewModel ) , from : nil , transition : . show )
} else {
provider . coordinator . present ( scene : . safari ( url : url ) , from : nil , transition : . safariPresent ( animated : true , completion : nil ) )
}
default :
break
}
}
private static func coordinateToStatusMentionProfileScene ( for target : Target , provider : StatusProvider , node : ASCellNode , mention : String ) {
guard let status = provider . status ( node : node , indexPath : nil ) else { return }
2021-07-12 14:00:50 +02:00
coordinateToStatusMentionProfileScene ( for : target , provider : provider , status : status , mention : mention , href : nil )
2021-06-20 18:14:47 +02:00
}
2021-06-22 07:41:40 +02:00
#endif
2021-06-20 18:14:47 +02:00
2021-07-08 10:55:46 +02:00
private static func coordinateToStatusMentionProfileScene ( for target : Target , provider : StatusProvider , cell : UITableViewCell , mention : String , href : String ? ) {
2021-04-02 13:33:29 +02:00
provider . status ( for : cell , indexPath : nil )
. sink { [ weak provider ] status in
guard let provider = provider else { return }
2021-06-20 18:14:47 +02:00
guard let status = status else { return }
2021-07-08 10:55:46 +02:00
coordinateToStatusMentionProfileScene ( for : target , provider : provider , status : status , mention : mention , href : href )
2021-04-02 13:33:29 +02:00
}
. store ( in : & provider . disposeBag )
}
2021-06-20 18:14:47 +02:00
2021-07-08 10:55:46 +02:00
private static func coordinateToStatusMentionProfileScene ( for target : Target , provider : StatusProvider , status : Status , mention : String , href : String ? ) {
2021-06-20 18:14:47 +02:00
guard let activeMastodonAuthenticationBox = provider . context . authenticationService . activeMastodonAuthenticationBox . value else { return }
let domain = activeMastodonAuthenticationBox . domain
let status : Status = {
switch target {
case . primary : return status . reblog ? ? status
case . secondary : return status
}
} ( )
// c a n n o t c o n t i n u e w i t h o u t m e t a
2021-07-08 10:55:46 +02:00
guard let mentionMeta = ( status . mentions ? ? Set ( ) ) . first ( where : { $0 . username = = mention } ) else {
// p r e s e n t w e b p a g e i f p o s s i b l e
if let url = href . flatMap ( { URL ( string : $0 ) } ) {
provider . coordinator . present ( scene : . safari ( url : url ) , from : provider , transition : . safariPresent ( animated : true , completion : nil ) )
}
return
}
2021-06-20 18:14:47 +02:00
let userID = mentionMeta . id
let profileViewModel : ProfileViewModel = {
// c h e c k i f s e l f
guard userID != activeMastodonAuthenticationBox . userID else {
return MeProfileViewModel ( context : provider . context )
}
let request = MastodonUser . sortedFetchRequest
request . fetchLimit = 1
request . predicate = MastodonUser . predicate ( domain : domain , id : userID )
let mastodonUser = provider . context . managedObjectContext . safeFetch ( request ) . first
if let mastodonUser = mastodonUser {
return CachedProfileViewModel ( context : provider . context , mastodonUser : mastodonUser )
} else {
return RemoteProfileViewModel ( context : provider . context , userID : userID )
}
} ( )
DispatchQueue . main . async {
provider . coordinator . present ( scene : . profile ( viewModel : profileViewModel ) , from : provider , transition : . show )
}
}
2021-04-02 13:33:29 +02:00
}
2021-02-08 11:29:27 +01:00
extension StatusProviderFacade {
static func responseToStatusLikeAction ( provider : StatusProvider ) {
_responseToStatusLikeAction (
provider : provider ,
2021-04-01 08:39:15 +02:00
status : provider . status ( )
2021-02-08 11:29:27 +01:00
)
}
static func responseToStatusLikeAction ( provider : StatusProvider , cell : UITableViewCell ) {
_responseToStatusLikeAction (
provider : provider ,
2021-04-01 08:39:15 +02:00
status : provider . status ( for : cell , indexPath : nil )
2021-02-08 11:29:27 +01:00
)
}
2021-05-21 09:23:02 +02:00
static func responseToStatusLikeAction ( provider : StatusProvider , indexPath : IndexPath ) {
_responseToStatusLikeAction (
provider : provider ,
status : provider . status ( for : nil , indexPath : indexPath )
)
}
2021-04-01 08:39:15 +02:00
private static func _responseToStatusLikeAction ( provider : StatusProvider , status : Future < Status ? , Never > ) {
2021-02-08 11:29:27 +01:00
// p r e p a r e a u t h e n t i c a t i o n
guard let activeMastodonAuthenticationBox = provider . context . authenticationService . activeMastodonAuthenticationBox . value else {
assertionFailure ( )
return
}
// p r e p a r e c u r r e n t u s e r i n f o s
guard let _currentMastodonUser = provider . context . authenticationService . activeMastodonAuthentication . value ? . user else {
assertionFailure ( )
return
}
let mastodonUserID = activeMastodonAuthenticationBox . userID
assert ( _currentMastodonUser . id = = mastodonUserID )
let mastodonUserObjectID = _currentMastodonUser . objectID
guard let context = provider . context else { return }
// h a p t i c f e e d b a c k g e n e r a t o r
let generator = UIImpactFeedbackGenerator ( style : . light )
let responseFeedbackGenerator = UIImpactFeedbackGenerator ( style : . medium )
2021-04-01 08:39:15 +02:00
status
. compactMap { status -> ( NSManagedObjectID , Mastodon . API . Favorites . FavoriteKind ) ? in
guard let status = status ? . reblog ? ? status else { return nil }
2021-02-08 11:29:27 +01:00
let favoriteKind : Mastodon . API . Favorites . FavoriteKind = {
2021-04-01 08:39:15 +02:00
let isLiked = status . favouritedBy . flatMap { $0 . contains ( where : { $0 . id = = mastodonUserID } ) } ? ? false
2021-02-08 11:29:27 +01:00
return isLiked ? . destroy : . create
} ( )
2021-04-01 08:39:15 +02:00
return ( status . objectID , favoriteKind )
2021-02-08 11:29:27 +01:00
}
2021-04-01 08:39:15 +02:00
. map { statusObjectID , favoriteKind -> AnyPublisher < ( Status . ID , Mastodon . API . Favorites . FavoriteKind ) , Error > in
2021-04-07 08:24:28 +02:00
return context . apiService . favorite (
2021-06-17 10:44:57 +02:00
statusObjectID : statusObjectID ,
2021-02-08 11:29:27 +01:00
mastodonUserObjectID : mastodonUserObjectID ,
favoriteKind : favoriteKind
)
2021-04-01 08:39:15 +02:00
. map { statusID in ( statusID , favoriteKind ) }
2021-02-08 11:29:27 +01:00
. eraseToAnyPublisher ( )
}
. setFailureType ( to : Error . self )
. eraseToAnyPublisher ( )
. switchToLatest ( )
. receive ( on : DispatchQueue . main )
. handleEvents { _ in
generator . prepare ( )
responseFeedbackGenerator . prepare ( )
} receiveOutput : { _ , favoriteKind in
generator . impactOccurred ( )
2021-04-01 08:39:15 +02:00
os_log ( " %{public}s[%{public}ld], %{public}s: [Like] update local status like status to: %s " , ( ( #file as NSString ) . lastPathComponent ) , #line , #function , favoriteKind = = . create ? " like " : " unlike " )
2021-02-08 11:29:27 +01:00
} receiveCompletion : { completion in
switch completion {
2021-02-24 11:40:47 +01:00
case . failure :
2021-02-08 11:29:27 +01:00
// TODO: h a n d l e e r r o r
break
case . finished :
break
}
}
2021-04-01 08:39:15 +02:00
. map { statusID , favoriteKind in
2021-04-07 08:24:28 +02:00
return context . apiService . favorite (
2021-04-01 08:39:15 +02:00
statusID : statusID ,
2021-02-08 11:29:27 +01:00
favoriteKind : favoriteKind ,
mastodonAuthenticationBox : activeMastodonAuthenticationBox
)
}
. switchToLatest ( )
. receive ( on : DispatchQueue . main )
. sink { [ weak provider ] completion in
guard let provider = provider else { return }
if provider . view . window != nil {
responseFeedbackGenerator . impactOccurred ( )
}
switch completion {
case . failure ( let error ) :
os_log ( " %{public}s[%{public}ld], %{public}s: [Like] remote like request fail: %{public}s " , ( ( #file as NSString ) . lastPathComponent ) , #line , #function , error . localizedDescription )
case . finished :
os_log ( " %{public}s[%{public}ld], %{public}s: [Like] remote like request success " , ( ( #file as NSString ) . lastPathComponent ) , #line , #function )
}
} receiveValue : { response in
// d o n o t h i n g
}
. store ( in : & provider . disposeBag )
}
}
2021-03-09 08:18:43 +01:00
extension StatusProviderFacade {
2021-03-15 11:22:44 +01:00
static func responseToStatusReblogAction ( provider : StatusProvider ) {
_responseToStatusReblogAction (
2021-03-09 08:18:43 +01:00
provider : provider ,
2021-04-01 08:39:15 +02:00
status : provider . status ( )
2021-03-09 08:18:43 +01:00
)
}
2021-03-15 11:19:45 +01:00
static func responseToStatusReblogAction ( provider : StatusProvider , cell : UITableViewCell ) {
2021-03-15 11:22:44 +01:00
_responseToStatusReblogAction (
2021-03-09 08:18:43 +01:00
provider : provider ,
2021-04-01 08:39:15 +02:00
status : provider . status ( for : cell , indexPath : nil )
2021-03-09 08:18:43 +01:00
)
}
2021-05-21 09:23:02 +02:00
static func responseToStatusReblogAction ( provider : StatusProvider , indexPath : IndexPath ) {
_responseToStatusReblogAction (
provider : provider ,
status : provider . status ( for : nil , indexPath : indexPath )
)
}
2021-04-01 08:39:15 +02:00
private static func _responseToStatusReblogAction ( provider : StatusProvider , status : Future < Status ? , Never > ) {
2021-03-09 08:18:43 +01:00
// p r e p a r e a u t h e n t i c a t i o n
guard let activeMastodonAuthenticationBox = provider . context . authenticationService . activeMastodonAuthenticationBox . value else {
assertionFailure ( )
return
}
// p r e p a r e c u r r e n t u s e r i n f o s
guard let _currentMastodonUser = provider . context . authenticationService . activeMastodonAuthentication . value ? . user else {
assertionFailure ( )
return
}
let mastodonUserID = activeMastodonAuthenticationBox . userID
assert ( _currentMastodonUser . id = = mastodonUserID )
let mastodonUserObjectID = _currentMastodonUser . objectID
guard let context = provider . context else { return }
// h a p t i c f e e d b a c k g e n e r a t o r
let generator = UIImpactFeedbackGenerator ( style : . light )
let responseFeedbackGenerator = UIImpactFeedbackGenerator ( style : . medium )
2021-04-01 08:39:15 +02:00
status
. compactMap { status -> ( NSManagedObjectID , Mastodon . API . Reblog . ReblogKind ) ? in
guard let status = status ? . reblog ? ? status else { return nil }
2021-03-15 11:19:45 +01:00
let reblogKind : Mastodon . API . Reblog . ReblogKind = {
2021-04-01 08:39:15 +02:00
let isReblogged = status . rebloggedBy . flatMap { $0 . contains ( where : { $0 . id = = mastodonUserID } ) } ? ? false
2021-03-15 11:19:45 +01:00
return isReblogged ? . undoReblog : . reblog ( query : . init ( visibility : nil ) )
2021-03-09 08:18:43 +01:00
} ( )
2021-04-01 08:39:15 +02:00
return ( status . objectID , reblogKind )
2021-03-09 08:18:43 +01:00
}
2021-04-01 08:39:15 +02:00
. map { statusObjectID , reblogKind -> AnyPublisher < ( Status . ID , Mastodon . API . Reblog . ReblogKind ) , Error > in
2021-03-15 11:19:45 +01:00
return context . apiService . reblog (
2021-04-01 08:39:15 +02:00
statusObjectID : statusObjectID ,
2021-03-09 08:18:43 +01:00
mastodonUserObjectID : mastodonUserObjectID ,
2021-03-15 11:19:45 +01:00
reblogKind : reblogKind
2021-03-09 08:18:43 +01:00
)
2021-04-01 08:39:15 +02:00
. map { statusID in ( statusID , reblogKind ) }
2021-03-09 08:18:43 +01:00
. eraseToAnyPublisher ( )
}
. setFailureType ( to : Error . self )
. eraseToAnyPublisher ( )
. switchToLatest ( )
. receive ( on : DispatchQueue . main )
. handleEvents { _ in
generator . prepare ( )
responseFeedbackGenerator . prepare ( )
2021-03-15 11:19:45 +01:00
} receiveOutput : { _ , reblogKind in
2021-03-09 08:18:43 +01:00
generator . impactOccurred ( )
2021-03-15 11:19:45 +01:00
switch reblogKind {
case . reblog :
2021-04-01 08:39:15 +02:00
os_log ( " %{public}s[%{public}ld], %{public}s: [Reblog] update local status reblog status to: %s " , ( ( #file as NSString ) . lastPathComponent ) , #line , #function , " reblog " )
2021-03-15 11:19:45 +01:00
case . undoReblog :
2021-04-01 08:39:15 +02:00
os_log ( " %{public}s[%{public}ld], %{public}s: [Reblog] update local status reblog status to: %s " , ( ( #file as NSString ) . lastPathComponent ) , #line , #function , " unreblog " )
2021-03-15 11:19:45 +01:00
}
2021-03-09 08:18:43 +01:00
} receiveCompletion : { completion in
switch completion {
case . failure :
// TODO: h a n d l e e r r o r
break
case . finished :
break
}
}
2021-04-01 08:39:15 +02:00
. map { statusID , reblogKind in
2021-03-15 11:19:45 +01:00
return context . apiService . reblog (
2021-04-01 08:39:15 +02:00
statusID : statusID ,
2021-03-15 11:19:45 +01:00
reblogKind : reblogKind ,
2021-03-09 08:18:43 +01:00
mastodonAuthenticationBox : activeMastodonAuthenticationBox
)
}
. switchToLatest ( )
. receive ( on : DispatchQueue . main )
. sink { [ weak provider ] completion in
guard let provider = provider else { return }
if provider . view . window != nil {
responseFeedbackGenerator . impactOccurred ( )
}
switch completion {
case . failure ( let error ) :
2021-03-15 11:19:45 +01:00
os_log ( " %{public}s[%{public}ld], %{public}s: [Reblog] remote reblog request fail: %{public}s " , ( ( #file as NSString ) . lastPathComponent ) , #line , #function , error . localizedDescription )
2021-03-09 08:18:43 +01:00
case . finished :
2021-03-15 11:19:45 +01:00
os_log ( " %{public}s[%{public}ld], %{public}s: [Reblog] remote reblog request success " , ( ( #file as NSString ) . lastPathComponent ) , #line , #function )
2021-03-09 08:18:43 +01:00
}
} receiveValue : { response in
// d o n o t h i n g
}
. store ( in : & provider . disposeBag )
}
}
2021-04-14 09:59:29 +02:00
extension StatusProviderFacade {
static func responseToStatusReplyAction ( provider : StatusProvider ) {
_responseToStatusReplyAction (
provider : provider ,
status : provider . status ( )
)
}
static func responseToStatusReplyAction ( provider : StatusProvider , cell : UITableViewCell ) {
_responseToStatusReplyAction (
provider : provider ,
status : provider . status ( for : cell , indexPath : nil )
)
}
2021-05-21 09:23:02 +02:00
static func responseToStatusReplyAction ( provider : StatusProvider , indexPath : IndexPath ) {
_responseToStatusReplyAction (
provider : provider ,
status : provider . status ( for : nil , indexPath : indexPath )
)
}
2021-04-14 09:59:29 +02:00
private static func _responseToStatusReplyAction ( provider : StatusProvider , status : Future < Status ? , Never > ) {
status
. sink { [ weak provider ] status in
guard let provider = provider else { return }
guard let status = status ? . reblog ? ? status else { return }
let composeViewModel = ComposeViewModel ( context : provider . context , composeKind : . reply ( repliedToStatusObjectID : status . objectID ) )
provider . coordinator . present ( scene : . compose ( viewModel : composeViewModel ) , from : provider , transition : . modal ( animated : true , completion : nil ) )
}
. store ( in : & provider . context . disposeBag )
}
}
2021-04-16 14:06:36 +02:00
extension StatusProviderFacade {
static func responseToStatusContentWarningRevealAction ( provider : StatusProvider , cell : UITableViewCell ) {
_responseToStatusContentWarningRevealAction (
2021-04-20 07:40:14 +02:00
dependency : provider ,
2021-04-16 14:06:36 +02:00
status : provider . status ( for : cell , indexPath : nil )
)
}
2021-05-21 09:23:02 +02:00
static func responseToStatusContentWarningRevealAction ( provider : StatusProvider , indexPath : IndexPath ) {
_responseToStatusContentWarningRevealAction (
dependency : provider ,
status : provider . status ( for : nil , indexPath : indexPath )
)
}
2021-04-20 07:40:14 +02:00
private static func _responseToStatusContentWarningRevealAction ( dependency : NeedsDependency , status : Future < Status ? , Never > ) {
2021-04-16 14:06:36 +02:00
status
2021-04-20 07:40:14 +02:00
. compactMap { [ weak dependency ] status -> AnyPublisher < Status ? , Never > ? in
guard let dependency = dependency else { return nil }
2021-04-16 14:06:36 +02:00
guard let _status = status else { return nil }
2021-06-23 14:47:49 +02:00
let managedObjectContext = dependency . context . backgroundManagedObjectContext
return managedObjectContext . performChanges {
guard let status = managedObjectContext . object ( with : _status . objectID ) as ? Status else { return }
2021-04-20 07:40:14 +02:00
let appStartUpTimestamp = dependency . context . documentStore . appStartUpTimestamp
2021-04-16 14:06:36 +02:00
let isRevealing : Bool = {
2021-04-20 07:40:14 +02:00
if dependency . context . documentStore . defaultRevealStatusDict [ status . id ] = = true {
2021-04-16 14:06:36 +02:00
return true
}
2021-04-20 07:40:14 +02:00
if status . reblog . flatMap ( { dependency . context . documentStore . defaultRevealStatusDict [ $0 . id ] } ) = = true {
2021-04-16 14:06:36 +02:00
return true
}
if let revealedAt = status . revealedAt , revealedAt > appStartUpTimestamp {
return true
}
return false
} ( )
// t o g g l e r e v e a l
2021-04-20 07:40:14 +02:00
dependency . context . documentStore . defaultRevealStatusDict [ status . id ] = false
2021-04-16 14:06:36 +02:00
status . update ( isReveal : ! isRevealing )
2021-06-23 14:47:49 +02:00
if let reblog = status . reblog {
dependency . context . documentStore . defaultRevealStatusDict [ reblog . id ] = false
reblog . update ( isReveal : ! isRevealing )
}
2021-04-19 12:33:11 +02:00
// p a u s e v i d e o p l a y b a c k i f i s R e v e a l i n g b e f o r e t o g g l e
if isRevealing , let attachment = ( status . reblog ? ? status ) . mediaAttachments ? . first ,
2021-04-20 07:40:14 +02:00
let playerViewModel = dependency . context . videoPlaybackService . dequeueVideoPlayerViewModel ( for : attachment ) {
2021-04-19 12:33:11 +02:00
playerViewModel . pause ( )
}
2021-04-20 07:40:14 +02:00
// r e s u m e G I F p l a y b a c k i f N O T i s R e v e a l i n g b e f o r e t o g g l e
if ! isRevealing , let attachment = ( status . reblog ? ? status ) . mediaAttachments ? . first ,
let playerViewModel = dependency . context . videoPlaybackService . dequeueVideoPlayerViewModel ( for : attachment ) , playerViewModel . videoKind = = . gif {
playerViewModel . play ( )
}
2021-04-16 14:06:36 +02:00
}
. map { result in
return status
}
. eraseToAnyPublisher ( )
}
. sink { _ in
// d o n o t h i n g
}
2021-04-20 07:40:14 +02:00
. store ( in : & dependency . context . disposeBag )
2021-04-16 14:06:36 +02:00
}
2021-04-25 17:49:19 +02:00
static func responseToStatusContentWarningRevealAction ( dependency : ReportViewController , cell : UITableViewCell ) {
let status = Future < Status ? , Never > { promise in
guard let diffableDataSource = dependency . viewModel . diffableDataSource ,
let indexPath = dependency . tableView . indexPath ( for : cell ) ,
let item = diffableDataSource . itemIdentifier ( for : indexPath ) else {
promise ( . success ( nil ) )
return
}
let managedObjectContext = dependency . viewModel . statusFetchedResultsController
. fetchedResultsController
. managedObjectContext
switch item {
case . reportStatus ( let objectID , _ ) :
managedObjectContext . perform {
let status = managedObjectContext . object ( with : objectID ) as ! Status
promise ( . success ( status ) )
}
default :
promise ( . success ( nil ) )
}
}
_responseToStatusContentWarningRevealAction (
dependency : dependency ,
status : status
)
}
2021-04-16 14:06:36 +02:00
}
2021-04-28 09:02:34 +02:00
extension StatusProviderFacade {
static func coordinateToStatusMediaPreviewScene ( provider : StatusProvider & MediaPreviewableViewController , cell : UITableViewCell , mosaicImageView : MosaicImageViewContainer , didTapImageView imageView : UIImageView , atIndex index : Int ) {
provider . status ( for : cell , indexPath : nil )
. sink { [ weak provider ] status in
guard let provider = provider else { return }
2021-04-28 13:06:45 +02:00
guard let source = status else { return }
let status = source . reblog ? ? source
2021-04-28 09:02:34 +02:00
let meta = MediaPreviewViewModel . StatusImagePreviewMeta (
statusObjectID : status . objectID ,
initialIndex : index ,
2021-04-30 13:28:06 +02:00
preloadThumbnailImages : mosaicImageView . thumbnails ( )
2021-04-28 09:02:34 +02:00
)
2021-04-28 13:06:45 +02:00
let pushTransitionItem = MediaPreviewTransitionItem (
source : . mosaic ( mosaicImageView ) ,
previewableViewController : provider
)
pushTransitionItem . aspectRatio = {
if let image = imageView . image {
return image . size
}
guard let media = status . mediaAttachments ? . sorted ( by : { $0 . index . compare ( $1 . index ) = = . orderedAscending } ) else { return nil }
guard index < media . count else { return nil }
let meta = media [ index ] . meta
guard let width = meta ? . original ? . width , let height = meta ? . original ? . height else { return nil }
return CGSize ( width : width , height : height )
} ( )
pushTransitionItem . sourceImageView = imageView
pushTransitionItem . initialFrame = {
let initialFrame = imageView . superview ! . convert ( imageView . frame , to : nil )
assert ( initialFrame != . zero )
return initialFrame
} ( )
pushTransitionItem . image = {
if let image = imageView . image {
return image
}
if index < mosaicImageView . blurhashOverlayImageViews . count {
return mosaicImageView . blurhashOverlayImageViews [ index ] . image
}
return nil
} ( )
let mediaPreviewViewModel = MediaPreviewViewModel (
context : provider . context ,
meta : meta ,
pushTransitionItem : pushTransitionItem
)
2021-04-28 09:02:34 +02:00
DispatchQueue . main . async {
provider . coordinator . present ( scene : . mediaPreview ( viewModel : mediaPreviewViewModel ) , from : provider , transition : . custom ( transitioningDelegate : provider . mediaPreviewTransitionController ) )
}
}
. store ( in : & provider . disposeBag )
}
}
2021-02-08 11:29:27 +01:00
extension StatusProviderFacade {
enum Target {
2021-04-13 13:46:42 +02:00
case primary // o r i g i n a l s t a t u s
case secondary // w r a p p e r s t a t u s o r r e p l y ( w h e n n e e d s . e . g t a p h e a d e r o f s t a t u s v i e w )
2021-02-08 11:29:27 +01:00
}
}