2021-02-07 07:42:50 +01:00
//
// H o m e T i m e l i n e V i e w C o n t r o l l e r . 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 / 5 .
//
import os . log
import UIKit
import AVKit
import Combine
import CoreData
import CoreDataStack
import GameplayKit
import MastodonSDK
import AlamofireImage
2021-04-28 09:02:34 +02:00
final class HomeTimelineViewController : UIViewController , NeedsDependency , MediaPreviewableViewController {
2021-02-07 07:42:50 +01:00
weak var context : AppContext ! { willSet { precondition ( ! isViewLoaded ) } }
weak var coordinator : SceneCoordinator ! { willSet { precondition ( ! isViewLoaded ) } }
var disposeBag = Set < AnyCancellable > ( )
private ( set ) lazy var viewModel = HomeTimelineViewModel ( context : context )
2021-04-28 09:02:34 +02:00
let mediaPreviewTransitionController = MediaPreviewTransitionController ( )
2021-07-09 07:16:58 +02:00
let friendsAssetImageView : UIImageView = {
let imageView = UIImageView ( )
imageView . image = Asset . Asset . friends . image
imageView . contentMode = . scaleAspectFill
return imageView
} ( )
2021-04-28 09:02:34 +02:00
2021-04-21 08:46:31 +02:00
lazy var emptyView : UIStackView = {
let emptyView = UIStackView ( )
emptyView . axis = . vertical
emptyView . distribution = . fill
emptyView . isLayoutMarginsRelativeArrangement = true
return emptyView
} ( )
2021-03-29 11:44:52 +02:00
let titleView = HomeTimelineNavigationBarTitleView ( )
2021-02-23 09:45:00 +01:00
let settingBarButtonItem : UIBarButtonItem = {
let barButtonItem = UIBarButtonItem ( )
2021-06-17 08:30:24 +02:00
barButtonItem . tintColor = Asset . Colors . brandBlue . color
2021-02-23 09:45:00 +01:00
barButtonItem . image = UIImage ( systemName : " gear " ) ? . withRenderingMode ( . alwaysTemplate )
return barButtonItem
} ( )
let composeBarButtonItem : UIBarButtonItem = {
let barButtonItem = UIBarButtonItem ( )
2021-06-17 08:30:24 +02:00
barButtonItem . tintColor = Asset . Colors . brandBlue . color
2021-02-23 09:45:00 +01:00
barButtonItem . image = UIImage ( systemName : " square.and.pencil " ) ? . withRenderingMode ( . alwaysTemplate )
return barButtonItem
} ( )
2021-02-07 07:42:50 +01:00
let tableView : UITableView = {
let tableView = ControlContainableTableView ( )
2021-02-23 08:16:55 +01:00
tableView . register ( StatusTableViewCell . self , forCellReuseIdentifier : String ( describing : StatusTableViewCell . self ) )
2021-02-07 07:42:50 +01:00
tableView . register ( TimelineMiddleLoaderTableViewCell . self , forCellReuseIdentifier : String ( describing : TimelineMiddleLoaderTableViewCell . self ) )
tableView . register ( TimelineBottomLoaderTableViewCell . self , forCellReuseIdentifier : String ( describing : TimelineBottomLoaderTableViewCell . self ) )
tableView . rowHeight = UITableView . automaticDimension
tableView . separatorStyle = . none
tableView . backgroundColor = . clear
return tableView
} ( )
2021-03-29 11:44:52 +02:00
let publishProgressView : UIProgressView = {
let progressView = UIProgressView ( progressViewStyle : . bar )
progressView . alpha = 0
return progressView
} ( )
2021-02-07 07:42:50 +01:00
let refreshControl = UIRefreshControl ( )
deinit {
os_log ( . info , log : . debug , " %{public}s[%{public}ld], %{public}s: " , ( ( #file as NSString ) . lastPathComponent ) , #line , #function )
}
2021-02-23 09:45:00 +01:00
2021-02-07 07:42:50 +01:00
}
extension HomeTimelineViewController {
override func viewDidLoad ( ) {
super . viewDidLoad ( )
2021-07-05 10:07:17 +02:00
2021-02-22 09:20:44 +01:00
title = L10n . Scene . HomeTimeline . title
2021-07-06 12:00:39 +02:00
view . backgroundColor = ThemeService . shared . currentTheme . value . secondarySystemBackgroundColor
2021-07-05 10:07:17 +02:00
ThemeService . shared . currentTheme
. receive ( on : RunLoop . main )
. sink { [ weak self ] theme in
guard let self = self else { return }
self . view . backgroundColor = theme . secondarySystemBackgroundColor
}
. store ( in : & disposeBag )
2021-02-23 09:45:00 +01:00
navigationItem . leftBarButtonItem = settingBarButtonItem
2021-03-29 11:44:52 +02:00
navigationItem . titleView = titleView
titleView . delegate = self
viewModel . homeTimelineNavigationBarTitleViewModel . state
2021-06-17 10:31:34 +02:00
. removeDuplicates ( )
2021-06-17 13:43:16 +02:00
. receive ( on : DispatchQueue . main )
2021-03-29 11:44:52 +02:00
. sink { [ weak self ] state in
guard let self = self else { return }
self . titleView . configure ( state : state )
}
. store ( in : & disposeBag )
2021-02-26 11:27:47 +01:00
#if DEBUG
// l o n g p r e s s t o t r i g g e r d e b u g m e n u
settingBarButtonItem . menu = debugMenu
2021-06-21 14:38:00 +02:00
#else
settingBarButtonItem . target = self
settingBarButtonItem . action = #selector ( HomeTimelineViewController . settingBarButtonItemPressed ( _ : ) )
#endif
2021-02-26 11:27:47 +01:00
2021-02-23 09:45:00 +01:00
navigationItem . rightBarButtonItem = composeBarButtonItem
composeBarButtonItem . target = self
composeBarButtonItem . action = #selector ( HomeTimelineViewController . composeBarButtonItemPressed ( _ : ) )
2021-02-07 07:42:50 +01:00
tableView . refreshControl = refreshControl
refreshControl . addTarget ( self , action : #selector ( HomeTimelineViewController . refreshControlValueChanged ( _ : ) ) , for : . valueChanged )
tableView . translatesAutoresizingMaskIntoConstraints = false
view . addSubview ( tableView )
NSLayoutConstraint . activate ( [
tableView . topAnchor . constraint ( equalTo : view . topAnchor ) ,
tableView . leadingAnchor . constraint ( equalTo : view . leadingAnchor ) ,
tableView . trailingAnchor . constraint ( equalTo : view . trailingAnchor ) ,
tableView . bottomAnchor . constraint ( equalTo : view . bottomAnchor ) ,
] )
2021-03-29 11:44:52 +02:00
publishProgressView . translatesAutoresizingMaskIntoConstraints = false
view . addSubview ( publishProgressView )
NSLayoutConstraint . activate ( [
publishProgressView . topAnchor . constraint ( equalTo : view . layoutMarginsGuide . topAnchor ) ,
publishProgressView . leadingAnchor . constraint ( equalTo : view . leadingAnchor ) ,
publishProgressView . trailingAnchor . constraint ( equalTo : view . trailingAnchor ) ,
] )
2021-02-07 07:42:50 +01:00
viewModel . tableView = tableView
viewModel . contentOffsetAdjustableTimelineViewControllerDelegate = self
tableView . delegate = self
2021-03-10 12:12:53 +01:00
tableView . prefetchDataSource = self
2021-02-07 07:42:50 +01:00
viewModel . setupDiffableDataSource (
for : tableView ,
dependency : self ,
2021-03-03 09:12:48 +01:00
statusTableViewCellDelegate : self ,
2021-02-07 07:42:50 +01:00
timelineMiddleLoaderTableViewCellDelegate : self
)
// b i n d r e f r e s h c o n t r o l
viewModel . isFetchingLatestTimeline
. receive ( on : DispatchQueue . main )
. sink { [ weak self ] isFetching in
guard let self = self else { return }
if ! isFetching {
UIView . animate ( withDuration : 0.5 ) { [ weak self ] in
guard let self = self else { return }
self . refreshControl . endRefreshing ( )
2021-04-22 09:45:32 +02:00
} completion : { _ in }
2021-02-07 07:42:50 +01:00
}
}
. store ( in : & disposeBag )
2021-03-29 11:44:52 +02:00
viewModel . homeTimelineNavigationBarTitleViewModel . publishingProgress
. receive ( on : DispatchQueue . main )
. sink { [ weak self ] progress in
guard let self = self else { return }
guard progress > 0 else {
let dismissAnimator = UIViewPropertyAnimator ( duration : 0.1 , curve : . easeInOut )
dismissAnimator . addAnimations {
self . publishProgressView . alpha = 0
}
dismissAnimator . addCompletion { _ in
self . publishProgressView . setProgress ( 0 , animated : false )
}
dismissAnimator . startAnimation ( )
return
}
if self . publishProgressView . alpha = = 0 {
let progressAnimator = UIViewPropertyAnimator ( duration : 0.1 , curve : . easeOut )
progressAnimator . addAnimations {
self . publishProgressView . alpha = 1
}
progressAnimator . startAnimation ( )
}
self . publishProgressView . setProgress ( progress , animated : true )
}
. store ( in : & disposeBag )
2021-04-22 09:45:32 +02:00
viewModel . timelineIsEmpty
. receive ( on : DispatchQueue . main )
. sink { [ weak self ] isEmpty in
if isEmpty {
self ? . showEmptyView ( )
} else {
self ? . emptyView . removeFromSuperview ( )
}
}
. store ( in : & disposeBag )
2021-02-07 07:42:50 +01:00
}
2021-04-01 08:39:15 +02:00
override func viewWillAppear ( _ animated : Bool ) {
super . viewWillAppear ( animated )
2021-04-13 13:46:42 +02:00
aspectViewWillAppear ( animated )
2021-04-01 08:39:15 +02:00
// n e e d s t r i g g e r m a n u a l l y a f t e r o n b o a r d i n g d i s m i s s
setNeedsStatusBarAppearanceUpdate ( )
}
2021-02-07 07:42:50 +01:00
override func viewDidAppear ( _ animated : Bool ) {
super . viewDidAppear ( animated )
viewModel . viewDidAppear . send ( )
2021-07-07 14:13:33 +02:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 2 ) { [ weak self ] in
2021-02-07 07:42:50 +01:00
guard let self = self else { return }
2021-07-07 14:13:33 +02:00
// a l w a y s t r y t o r e f r e s h t i m e l i n e a f t e r a p p e a r
self . viewModel . homeTimelineNeedRefresh . send ( )
2021-02-07 07:42:50 +01:00
}
}
override func viewDidDisappear ( _ animated : Bool ) {
super . viewDidDisappear ( animated )
2021-04-13 13:46:42 +02:00
aspectViewDidDisappear ( animated )
2021-02-07 07:42:50 +01:00
}
override func viewWillTransition ( to size : CGSize , with coordinator : UIViewControllerTransitionCoordinator ) {
super . viewWillTransition ( to : size , with : coordinator )
coordinator . animate { _ in
// d o n o t h i n g
} completion : { _ in
// f i x A u t o L a y o u t c e l l h e i g h t n o t u p d a t e a f t e r r o t a t e i s s u e
self . viewModel . cellFrameCache . removeAllObjects ( )
self . tableView . reloadData ( )
}
}
}
extension HomeTimelineViewController {
2021-04-21 08:46:31 +02:00
func showEmptyView ( ) {
if emptyView . superview != nil {
return
}
view . addSubview ( emptyView )
emptyView . translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint . activate ( [
2021-07-09 07:16:58 +02:00
emptyView . topAnchor . constraint ( equalTo : view . layoutMarginsGuide . topAnchor ) ,
emptyView . leadingAnchor . constraint ( equalTo : view . leadingAnchor ) ,
emptyView . trailingAnchor . constraint ( equalTo : view . trailingAnchor ) ,
emptyView . bottomAnchor . constraint ( equalTo : view . layoutMarginsGuide . bottomAnchor )
2021-04-21 08:46:31 +02:00
] )
2021-04-21 11:58:56 +02:00
if emptyView . arrangedSubviews . count > 0 {
return
}
2021-04-21 08:46:31 +02:00
let findPeopleButton : PrimaryActionButton = {
let button = PrimaryActionButton ( )
button . setTitle ( L10n . Common . Controls . Actions . findPeople , for : . normal )
button . addTarget ( self , action : #selector ( HomeTimelineViewController . findPeopleButtonPressed ( _ : ) ) , for : . touchUpInside )
return button
} ( )
NSLayoutConstraint . activate ( [
findPeopleButton . heightAnchor . constraint ( equalToConstant : 46 )
] )
let manuallySearchButton : HighlightDimmableButton = {
let button = HighlightDimmableButton ( )
button . titleLabel ? . font = UIFontMetrics ( forTextStyle : . headline ) . scaledFont ( for : . systemFont ( ofSize : 15 , weight : . semibold ) )
button . setTitle ( L10n . Common . Controls . Actions . manuallySearch , for : . normal )
button . setTitleColor ( Asset . Colors . brandBlue . color , for : . normal )
button . addTarget ( self , action : #selector ( HomeTimelineViewController . manuallySearchButtonPressed ( _ : ) ) , for : . touchUpInside )
return button
} ( )
2021-07-09 07:16:58 +02:00
let topPaddingView = UIView ( )
let bottomPaddingView = UIView ( )
emptyView . addArrangedSubview ( topPaddingView )
emptyView . addArrangedSubview ( friendsAssetImageView )
emptyView . addArrangedSubview ( bottomPaddingView )
2021-07-09 13:39:44 +02:00
friendsAssetImageView . isHidden = traitCollection . userInterfaceIdiom != . phone
2021-07-09 07:16:58 +02:00
topPaddingView . translatesAutoresizingMaskIntoConstraints = false
bottomPaddingView . translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint . activate ( [
topPaddingView . heightAnchor . constraint ( equalTo : bottomPaddingView . heightAnchor , multiplier : 0.8 ) ,
] )
let buttonContainerStackView = UIStackView ( )
emptyView . addArrangedSubview ( buttonContainerStackView )
buttonContainerStackView . isLayoutMarginsRelativeArrangement = true
buttonContainerStackView . layoutMargins = UIEdgeInsets ( top : 0 , left : 32 , bottom : 22 , right : 32 )
buttonContainerStackView . axis = . vertical
buttonContainerStackView . spacing = 17
buttonContainerStackView . addArrangedSubview ( findPeopleButton )
buttonContainerStackView . addArrangedSubview ( manuallySearchButton )
2021-04-21 08:46:31 +02:00
}
}
extension HomeTimelineViewController {
@objc private func findPeopleButtonPressed ( _ sender : PrimaryActionButton ) {
let viewModel = SuggestionAccountViewModel ( context : context )
2021-04-21 12:52:09 +02:00
viewModel . delegate = self . viewModel
2021-04-21 08:46:31 +02:00
coordinator . present ( scene : . suggestionAccount ( viewModel : viewModel ) , from : self , transition : . modal ( animated : true , completion : nil ) )
}
@objc private func manuallySearchButtonPressed ( _ sender : UIButton ) {
coordinator . switchToTabBar ( tab : . search )
}
2021-02-07 07:42:50 +01:00
2021-02-23 09:45:00 +01:00
@objc private func settingBarButtonItemPressed ( _ sender : UIBarButtonItem ) {
os_log ( . info , log : . debug , " %{public}s[%{public}ld], %{public}s " , ( ( #file as NSString ) . lastPathComponent ) , #line , #function )
2021-04-26 10:57:50 +02:00
guard let setting = context . settingService . currentSetting . value else { return }
let settingsViewModel = SettingsViewModel ( context : context , setting : setting )
coordinator . present ( scene : . settings ( viewModel : settingsViewModel ) , from : self , transition : . modal ( animated : true , completion : nil ) )
2021-02-23 09:45:00 +01:00
}
@objc private func composeBarButtonItemPressed ( _ sender : UIBarButtonItem ) {
2021-02-07 07:42:50 +01:00
os_log ( . info , log : . debug , " %{public}s[%{public}ld], %{public}s " , ( ( #file as NSString ) . lastPathComponent ) , #line , #function )
2021-03-15 06:42:46 +01:00
let composeViewModel = ComposeViewModel ( context : context , composeKind : . post )
2021-03-11 08:41:27 +01:00
coordinator . present ( scene : . compose ( viewModel : composeViewModel ) , from : self , transition : . modal ( animated : true , completion : nil ) )
2021-02-07 07:42:50 +01:00
}
@objc private func refreshControlValueChanged ( _ sender : UIRefreshControl ) {
guard viewModel . loadLatestStateMachine . enter ( HomeTimelineViewModel . LoadLatestState . Loading . self ) else {
sender . endRefreshing ( )
return
}
}
2021-02-26 11:27:47 +01:00
@objc func signOutAction ( _ sender : UIAction ) {
guard let activeMastodonAuthenticationBox = context . authenticationService . activeMastodonAuthenticationBox . value else {
return
}
context . authenticationService . signOutMastodonUser (
domain : activeMastodonAuthenticationBox . domain ,
userID : activeMastodonAuthenticationBox . userID
)
. receive ( on : DispatchQueue . main )
. sink { [ weak self ] result in
guard let self = self else { return }
switch result {
case . failure ( let error ) :
assertionFailure ( error . localizedDescription )
case . success ( let isSignOut ) :
os_log ( . info , log : . debug , " %{public}s[%{public}ld], %{public}s: sign out %s " , ( ( #file as NSString ) . lastPathComponent ) , #line , #function , isSignOut ? " success " : " fail " )
guard isSignOut else { return }
self . coordinator . setup ( )
self . coordinator . setupOnboardingIfNeeds ( animated : true )
}
}
. store ( in : & disposeBag )
}
2021-02-07 07:42:50 +01:00
}
2021-04-13 13:46:42 +02:00
// MARK: - S t a t u s T a b l e V i e w C o n t r o l l e r A s p e c t
extension HomeTimelineViewController : StatusTableViewControllerAspect { }
extension HomeTimelineViewController : TableViewCellHeightCacheableContainer {
var cellFrameCache : NSCache < NSNumber , NSValue > { return viewModel . cellFrameCache }
}
2021-02-07 07:42:50 +01:00
// MARK: - U I S c r o l l V i e w D e l e g a t e
extension HomeTimelineViewController {
func scrollViewDidScroll ( _ scrollView : UIScrollView ) {
2021-04-13 13:46:42 +02:00
aspectScrollViewDidScroll ( scrollView )
viewModel . homeTimelineNavigationBarTitleViewModel . handleScrollViewDidScroll ( scrollView )
2021-02-07 07:42:50 +01:00
}
}
2021-02-24 10:23:01 +01:00
extension HomeTimelineViewController : LoadMoreConfigurableTableViewContainer {
typealias BottomLoaderTableViewCell = TimelineBottomLoaderTableViewCell
typealias LoadingState = HomeTimelineViewModel . LoadOldestState . Loading
var loadMoreConfigurableTableView : UITableView { return tableView }
var loadMoreConfigurableStateMachine : GKStateMachine { return viewModel . loadoldestStateMachine }
}
2021-02-07 07:42:50 +01:00
// MARK: - U I T a b l e V i e w D e l e g a t e
extension HomeTimelineViewController : UITableViewDelegate {
2021-03-29 11:44:52 +02:00
func tableView ( _ tableView : UITableView , estimatedHeightForRowAt indexPath : IndexPath ) -> CGFloat {
2021-04-13 13:46:42 +02:00
aspectTableView ( tableView , estimatedHeightForRowAt : indexPath )
2021-03-29 11:44:52 +02:00
}
2021-03-03 09:12:48 +01:00
func tableView ( _ tableView : UITableView , willDisplay cell : UITableViewCell , forRowAt indexPath : IndexPath ) {
2021-04-13 13:46:42 +02:00
aspectTableView ( tableView , willDisplay : cell , forRowAt : indexPath )
2021-02-07 07:42:50 +01:00
}
2021-03-10 14:19:56 +01:00
func tableView ( _ tableView : UITableView , didEndDisplaying cell : UITableViewCell , forRowAt indexPath : IndexPath ) {
2021-04-13 13:46:42 +02:00
aspectTableView ( tableView , didEndDisplaying : cell , forRowAt : indexPath )
}
func tableView ( _ tableView : UITableView , didSelectRowAt indexPath : IndexPath ) {
aspectTableView ( tableView , didSelectRowAt : indexPath )
2021-03-10 14:19:56 +01:00
}
2021-04-30 13:28:06 +02:00
func tableView ( _ tableView : UITableView , contextMenuConfigurationForRowAt indexPath : IndexPath , point : CGPoint ) -> UIContextMenuConfiguration ? {
return aspectTableView ( tableView , contextMenuConfigurationForRowAt : indexPath , point : point )
}
func tableView ( _ tableView : UITableView , previewForHighlightingContextMenuWithConfiguration configuration : UIContextMenuConfiguration ) -> UITargetedPreview ? {
return aspectTableView ( tableView , previewForHighlightingContextMenuWithConfiguration : configuration )
}
func tableView ( _ tableView : UITableView , previewForDismissingContextMenuWithConfiguration configuration : UIContextMenuConfiguration ) -> UITargetedPreview ? {
return aspectTableView ( tableView , previewForDismissingContextMenuWithConfiguration : configuration )
}
func tableView ( _ tableView : UITableView , willPerformPreviewActionForMenuWith configuration : UIContextMenuConfiguration , animator : UIContextMenuInteractionCommitAnimating ) {
aspectTableView ( tableView , willPerformPreviewActionForMenuWith : configuration , animator : animator )
}
2021-02-07 07:42:50 +01:00
}
2021-03-10 12:12:53 +01:00
// MARK: - U I T a b l e V i e w D a t a S o u r c e P r e f e t c h i n g
extension HomeTimelineViewController : UITableViewDataSourcePrefetching {
func tableView ( _ tableView : UITableView , prefetchRowsAt indexPaths : [ IndexPath ] ) {
2021-04-13 13:46:42 +02:00
aspectTableView ( tableView , prefetchRowsAt : indexPaths )
2021-07-15 03:56:26 +02:00
}
func tableView ( _ tableView : UITableView , cancelPrefetchingForRowsAt indexPaths : [ IndexPath ] ) {
aspectTableView ( tableView , cancelPrefetchingForRowsAt : indexPaths )
2021-03-10 12:12:53 +01:00
}
}
2021-02-07 07:42:50 +01:00
// MARK: - C o n t e n t O f f s e t A d j u s t a b l e T i m e l i n e V i e w C o n t r o l l e r D e l e g a t e
extension HomeTimelineViewController : ContentOffsetAdjustableTimelineViewControllerDelegate {
func navigationBar ( ) -> UINavigationBar ? {
return navigationController ? . navigationBar
}
}
// MARK: - T i m e l i n e M i d d l e L o a d e r T a b l e V i e w C e l l D e l e g a t e
extension HomeTimelineViewController : TimelineMiddleLoaderTableViewCellDelegate {
2021-04-01 08:39:15 +02:00
func configure ( cell : TimelineMiddleLoaderTableViewCell , upperTimelineStatusID : String ? , timelineIndexobjectID : NSManagedObjectID ? ) {
2021-02-07 07:42:50 +01:00
guard let upperTimelineIndexObjectID = timelineIndexobjectID else {
return
}
viewModel . loadMiddleSateMachineList
. receive ( on : DispatchQueue . main )
. sink { [ weak self ] ids in
2021-02-24 11:41:40 +01:00
guard let _ = self else { return }
2021-02-07 07:42:50 +01:00
if let stateMachine = ids [ upperTimelineIndexObjectID ] {
guard let state = stateMachine . currentState else {
assertionFailure ( )
return
}
// m a k e s u c c e s s s t a t e s a m e a s l o a d i n g d u e t o s n a p s h o t u p d a t i n g d e l a y
let isLoading = state is HomeTimelineViewModel . LoadMiddleState . Loading || state is HomeTimelineViewModel . LoadMiddleState . Success
if isLoading {
2021-03-16 12:28:52 +01:00
cell . startAnimating ( )
2021-02-07 07:42:50 +01:00
} else {
2021-03-16 12:28:52 +01:00
cell . stopAnimating ( )
2021-02-07 07:42:50 +01:00
}
} else {
2021-03-16 12:28:52 +01:00
cell . stopAnimating ( )
2021-02-07 07:42:50 +01:00
}
}
. store ( in : & cell . disposeBag )
var dict = viewModel . loadMiddleSateMachineList . value
if let _ = dict [ upperTimelineIndexObjectID ] {
// d o n o t h i n g
} else {
let stateMachine = GKStateMachine ( states : [
HomeTimelineViewModel . LoadMiddleState . Initial ( viewModel : viewModel , upperTimelineIndexObjectID : upperTimelineIndexObjectID ) ,
HomeTimelineViewModel . LoadMiddleState . Loading ( viewModel : viewModel , upperTimelineIndexObjectID : upperTimelineIndexObjectID ) ,
HomeTimelineViewModel . LoadMiddleState . Fail ( viewModel : viewModel , upperTimelineIndexObjectID : upperTimelineIndexObjectID ) ,
HomeTimelineViewModel . LoadMiddleState . Success ( viewModel : viewModel , upperTimelineIndexObjectID : upperTimelineIndexObjectID ) ,
] )
stateMachine . enter ( HomeTimelineViewModel . LoadMiddleState . Initial . self )
dict [ upperTimelineIndexObjectID ] = stateMachine
viewModel . loadMiddleSateMachineList . value = dict
}
}
func timelineMiddleLoaderTableViewCell ( _ cell : TimelineMiddleLoaderTableViewCell , loadMoreButtonDidPressed button : UIButton ) {
guard let diffableDataSource = viewModel . diffableDataSource else { return }
guard let indexPath = tableView . indexPath ( for : cell ) else { return }
guard let item = diffableDataSource . itemIdentifier ( for : indexPath ) else { return }
switch item {
case . homeMiddleLoader ( let upper ) :
guard let stateMachine = viewModel . loadMiddleSateMachineList . value [ upper ] else {
assertionFailure ( )
return
}
stateMachine . enter ( HomeTimelineViewModel . LoadMiddleState . Loading . self )
default :
assertionFailure ( )
}
}
}
// MARK: - S c r o l l V i e w C o n t a i n e r
extension HomeTimelineViewController : ScrollViewContainer {
var scrollView : UIScrollView { return tableView }
func scrollToTop ( animated : Bool ) {
if scrollView . contentOffset . y < scrollView . frame . height ,
viewModel . loadLatestStateMachine . canEnterState ( HomeTimelineViewModel . LoadLatestState . Loading . self ) ,
( scrollView . contentOffset . y + scrollView . adjustedContentInset . top ) = = 0.0 ,
! refreshControl . isRefreshing {
scrollView . scrollRectToVisible ( CGRect ( origin : CGPoint ( x : 0 , y : - refreshControl . frame . height ) , size : CGSize ( width : 1 , height : 1 ) ) , animated : animated )
DispatchQueue . main . async { [ weak self ] in
guard let self = self else { return }
self . refreshControl . beginRefreshing ( )
self . refreshControl . sendActions ( for : . valueChanged )
}
} else {
let indexPath = IndexPath ( row : 0 , section : 0 )
guard viewModel . diffableDataSource ? . itemIdentifier ( for : indexPath ) != nil else { return }
tableView . scrollToRow ( at : indexPath , at : . top , animated : true )
}
}
}
2021-02-24 09:11:48 +01:00
2021-03-10 07:36:28 +01:00
// MARK: - 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
extension HomeTimelineViewController : AVPlayerViewControllerDelegate {
func playerViewController ( _ playerViewController : AVPlayerViewController , willBeginFullScreenPresentationWithAnimationCoordinator coordinator : UIViewControllerTransitionCoordinator ) {
handlePlayerViewController ( playerViewController , willBeginFullScreenPresentationWithAnimationCoordinator : coordinator )
}
func playerViewController ( _ playerViewController : AVPlayerViewController , willEndFullScreenPresentationWithAnimationCoordinator coordinator : UIViewControllerTransitionCoordinator ) {
handlePlayerViewController ( playerViewController , willEndFullScreenPresentationWithAnimationCoordinator : coordinator )
}
}
2021-02-24 09:11:48 +01:00
// MARK: - S t a t u s T a b l e V i e w C e l l D e l e g a t e
2021-03-10 07:36:28 +01:00
extension HomeTimelineViewController : StatusTableViewCellDelegate {
weak var playerViewControllerDelegate : AVPlayerViewControllerDelegate ? { return self }
func parent ( ) -> UIViewController { return self }
}
2021-03-29 11:44:52 +02:00
// MARK: - H o m e T i m e l i n e N a v i g a t i o n B a r T i t l e V i e w D e l e g a t e
extension HomeTimelineViewController : HomeTimelineNavigationBarTitleViewDelegate {
2021-06-21 12:58:58 +02:00
func homeTimelineNavigationBarTitleView ( _ titleView : HomeTimelineNavigationBarTitleView , logoButtonDidPressed sender : UIButton ) {
scrollToTop ( animated : true )
}
2021-03-29 11:44:52 +02:00
func homeTimelineNavigationBarTitleView ( _ titleView : HomeTimelineNavigationBarTitleView , buttonDidPressed sender : UIButton ) {
switch titleView . state {
case . newPostButton :
guard let diffableDataSource = viewModel . diffableDataSource else { return }
let indexPath = IndexPath ( row : 0 , section : 0 )
guard diffableDataSource . itemIdentifier ( for : indexPath ) != nil else { return }
tableView . scrollToRow ( at : indexPath , at : . top , animated : true )
case . offlineButton :
// TODO: r e t r y
break
case . publishedButton :
break
default :
break
}
}
}
2021-05-21 09:23:02 +02:00
extension HomeTimelineViewController {
override var keyCommands : [ UIKeyCommand ] ? {
2021-05-21 10:52:47 +02:00
return navigationKeyCommands + statusNavigationKeyCommands
2021-05-21 09:23:02 +02:00
}
}
// MARK: - S t a t u s T a b l e V i e w C o n t r o l l e r N a v i g a t e a b l e
2021-05-21 10:52:47 +02:00
extension HomeTimelineViewController : StatusTableViewControllerNavigateable {
@objc func navigateKeyCommandHandlerRelay ( _ sender : UIKeyCommand ) {
navigateKeyCommandHandler ( sender )
}
@objc func statusKeyCommandHandlerRelay ( _ sender : UIKeyCommand ) {
statusKeyCommandHandler ( sender )
2021-05-21 09:23:02 +02:00
}
}