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-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 ( )
)
}
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-02-08 11:29:27 +01:00
}
2021-03-09 08:18:43 +01: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-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-02-08 11:29:27 +01:00
return context . apiService . like (
2021-04-01 08:39:15 +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-02-08 11:29:27 +01:00
return context . apiService . like (
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-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-02-08 11:29:27 +01:00
extension StatusProviderFacade {
enum Target {
2021-04-01 08:39:15 +02:00
case primary // o r i g i n a l
case secondary // a t t a c h m e n t r e b l o g o r r e p l y
2021-02-08 11:29:27 +01:00
}
}