2021-02-07 07:42:50 +01:00
//
// H o m e T i m e l i n e V i e w M o d e l + D i f f a b l 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 / 7 .
//
import os . log
import UIKit
import CoreData
import CoreDataStack
extension HomeTimelineViewModel {
func setupDiffableDataSource (
2022-01-27 14:23:39 +01:00
tableView : UITableView ,
2021-03-03 09:12:48 +01:00
statusTableViewCellDelegate : StatusTableViewCellDelegate ,
2021-02-07 07:42:50 +01:00
timelineMiddleLoaderTableViewCellDelegate : TimelineMiddleLoaderTableViewCellDelegate
) {
2022-01-27 14:23:39 +01:00
diffableDataSource = StatusSection . diffableDataSource (
tableView : tableView ,
context : context ,
configuration : StatusSection . Configuration (
statusTableViewCellDelegate : statusTableViewCellDelegate ,
2022-02-15 12:44:45 +01:00
timelineMiddleLoaderTableViewCellDelegate : timelineMiddleLoaderTableViewCellDelegate ,
filterContext : . home ,
activeFilters : context . statusFilterService . $ activeFilters
2022-01-27 14:23:39 +01:00
)
2021-02-07 07:42:50 +01:00
)
2021-07-09 07:17:25 +02:00
// m a k e i n i t i a l s n a p s h o t a n i m a t i o n s m o o t h
2022-01-27 14:23:39 +01:00
var snapshot = NSDiffableDataSourceSnapshot < StatusSection , StatusItem > ( )
2021-06-22 11:49:07 +02:00
snapshot . appendSections ( [ . main ] )
diffableDataSource ? . apply ( snapshot )
2022-01-27 14:23:39 +01:00
fetchedResultsController . $ records
. receive ( on : DispatchQueue . main )
. sink { [ weak self ] records in
guard let self = self else { return }
guard let diffableDataSource = self . diffableDataSource else { return }
self . logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : incoming \( records . count ) objects " )
Task {
let start = CACurrentMediaTime ( )
defer {
let end = CACurrentMediaTime ( )
self . logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : cost \( end - start , format : . fixed ( precision : 4 ) ) s to process \( records . count ) feeds " )
}
let oldSnapshot = diffableDataSource . snapshot ( )
var newSnapshot : NSDiffableDataSourceSnapshot < StatusSection , StatusItem > = {
let newItems = records . map { record in
StatusItem . feed ( record : record )
}
var snapshot = NSDiffableDataSourceSnapshot < StatusSection , StatusItem > ( )
snapshot . appendSections ( [ . main ] )
snapshot . appendItems ( newItems , toSection : . main )
return snapshot
} ( )
let parentManagedObjectContext = self . context . managedObjectContext
let managedObjectContext = NSManagedObjectContext ( concurrencyType : . privateQueueConcurrencyType )
managedObjectContext . parent = parentManagedObjectContext
try ? await managedObjectContext . perform {
let anchors : [ Feed ] = {
let request = Feed . sortedFetchRequest
request . predicate = NSCompoundPredicate ( andPredicateWithSubpredicates : [
Feed . hasMorePredicate ( ) ,
self . fetchedResultsController . predicate ,
] )
do {
return try managedObjectContext . fetch ( request )
} catch {
assertionFailure ( error . localizedDescription )
return [ ]
}
} ( )
let itemIdentifiers = newSnapshot . itemIdentifiers
for ( index , item ) in itemIdentifiers . enumerated ( ) {
guard case let . feed ( record ) = item else { continue }
guard anchors . contains ( where : { feed in feed . objectID = = record . objectID } ) else { continue }
let isLast = index + 1 = = itemIdentifiers . count
if isLast {
newSnapshot . insertItems ( [ . bottomLoader ] , afterItem : item )
} else {
newSnapshot . insertItems ( [ . feedLoader ( record : record ) ] , afterItem : item )
}
}
}
let hasChanges = newSnapshot . itemIdentifiers != oldSnapshot . itemIdentifiers
if ! hasChanges {
self . logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : snapshot not changes " )
self . didLoadLatest . send ( )
return
} else {
self . logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : snapshot has changes " )
}
guard let difference = await self . calculateReloadSnapshotDifference (
tableView : tableView ,
oldSnapshot : oldSnapshot ,
newSnapshot : newSnapshot
) else {
await self . updateSnapshotUsingReloadData ( snapshot : newSnapshot )
self . didLoadLatest . send ( )
self . logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : applied new snapshot " )
return
}
await self . updateSnapshotUsingReloadData ( snapshot : newSnapshot )
await tableView . scrollToRow ( at : difference . targetIndexPath , at : . top , animated : false )
var contentOffset = await tableView . contentOffset
contentOffset . y = await tableView . contentOffset . y - difference . sourceDistanceToTableViewTopEdge
await tableView . setContentOffset ( contentOffset , animated : false )
self . didLoadLatest . send ( )
self . logger . log ( level : . debug , " \( ( #file as NSString ) . lastPathComponent , privacy : . public ) [ \( #line , privacy : . public ) ], \( #function , privacy : . public ) : applied new snapshot " )
} // e n d T a s k
}
. store ( in : & disposeBag )
2021-02-07 07:42:50 +01:00
}
}
2022-01-27 14:23:39 +01:00
extension HomeTimelineViewModel {
2021-02-07 07:42:50 +01:00
2022-01-27 14:23:39 +01:00
@ MainActor func updateDataSource (
snapshot : NSDiffableDataSourceSnapshot < StatusSection , StatusItem > ,
animatingDifferences : Bool
) async {
diffableDataSource ? . apply ( snapshot , animatingDifferences : animatingDifferences )
2021-02-07 07:42:50 +01:00
}
2022-01-27 14:23:39 +01:00
@ MainActor func updateSnapshotUsingReloadData (
snapshot : NSDiffableDataSourceSnapshot < StatusSection , StatusItem >
) async {
if #available ( iOS 15.0 , * ) {
await self . diffableDataSource ? . applySnapshotUsingReloadData ( snapshot )
} else {
diffableDataSource ? . applySnapshot ( snapshot , animated : false , completion : nil )
}
2021-02-07 07:42:50 +01:00
}
2022-01-27 14:23:39 +01:00
struct Difference < T > {
2021-02-07 07:42:50 +01:00
let item : T
let sourceIndexPath : IndexPath
2022-01-27 14:23:39 +01:00
let sourceDistanceToTableViewTopEdge : CGFloat
2021-02-07 07:42:50 +01:00
let targetIndexPath : IndexPath
}
2022-01-27 14:23:39 +01:00
@ MainActor private func calculateReloadSnapshotDifference < S : Hashable , T : Hashable > (
2021-02-07 07:42:50 +01:00
tableView : UITableView ,
2022-01-27 14:23:39 +01:00
oldSnapshot : NSDiffableDataSourceSnapshot < S , T > ,
newSnapshot : NSDiffableDataSourceSnapshot < S , T >
2021-02-07 07:42:50 +01:00
) -> Difference < T > ? {
2022-01-27 14:23:39 +01:00
guard let sourceIndexPath = ( tableView . indexPathsForVisibleRows ? ? [ ] ) . sorted ( ) . first else { return nil }
let rectForSourceItemCell = tableView . rectForRow ( at : sourceIndexPath )
let sourceDistanceToTableViewTopEdge = tableView . convert ( rectForSourceItemCell , to : nil ) . origin . y - tableView . safeAreaInsets . top
guard sourceIndexPath . section < oldSnapshot . numberOfSections ,
sourceIndexPath . row < oldSnapshot . numberOfItems ( inSection : oldSnapshot . sectionIdentifiers [ sourceIndexPath . section ] )
else { return nil }
2021-02-07 07:42:50 +01:00
2022-01-27 14:23:39 +01:00
let sectionIdentifier = oldSnapshot . sectionIdentifiers [ sourceIndexPath . section ]
let item = oldSnapshot . itemIdentifiers ( inSection : sectionIdentifier ) [ sourceIndexPath . row ]
2021-02-07 07:42:50 +01:00
2022-01-27 14:23:39 +01:00
guard let targetIndexPathRow = newSnapshot . indexOfItem ( item ) ,
let newSectionIdentifier = newSnapshot . sectionIdentifier ( containingItem : item ) ,
let targetIndexPathSection = newSnapshot . indexOfSection ( newSectionIdentifier )
else { return nil }
2021-02-07 07:42:50 +01:00
2022-01-27 14:23:39 +01:00
let targetIndexPath = IndexPath ( row : targetIndexPathRow , section : targetIndexPathSection )
2021-02-07 07:42:50 +01:00
return Difference (
2022-01-27 14:23:39 +01:00
item : item ,
2021-02-07 07:42:50 +01:00
sourceIndexPath : sourceIndexPath ,
2022-01-27 14:23:39 +01:00
sourceDistanceToTableViewTopEdge : sourceDistanceToTableViewTopEdge ,
targetIndexPath : targetIndexPath
2021-02-07 07:42:50 +01:00
)
}
}
2022-01-27 14:23:39 +01:00
// / / MARK: - N S F e t c h e d R e s u l t s C o n t r o l l e r D e l e g a t e
// e x t e n s i o n H o m e T i m e l i n e V i e w M o d e l : N S F e t c h e d R e s u l t s C o n t r o l l e r D e l e g a t e {
//
// f u n c c o n t r o l l e r W i l l C h a n g e C o n t e n t ( _ c o n t r o l l e r : N S F e t c h e d R e s u l t s C o n t r o l l e r < N S F e t c h R e q u e s t R e s u l t > ) {
// o s _ l o g ( " % { p u b l i c } s [ % { p u b l i c } l d ] , % { p u b l i c } s " , ( ( # f i l e a s N S S t r i n g ) . l a s t P a t h C o m p o n e n t ) , # l i n e , # f u n c t i o n )
// }
//
// f u n c c o n t r o l l e r ( _ c o n t r o l l e r : N S F e t c h e d R e s u l t s C o n t r o l l e r < N S F e t c h R e q u e s t R e s u l t > , d i d C h a n g e C o n t e n t W i t h s n a p s h o t : N S D i f f a b l e D a t a S o u r c e S n a p s h o t R e f e r e n c e ) {
// o s _ l o g ( " % { p u b l i c } s [ % { p u b l i c } l d ] , % { p u b l i c } s " , ( ( # f i l e a s N S S t r i n g ) . l a s t P a t h C o m p o n e n t ) , # l i n e , # f u n c t i o n )
//
// g u a r d l e t t a b l e V i e w = s e l f . t a b l e V i e w e l s e { r e t u r n }
// g u a r d l e t n a v i g a t i o n B a r = s e l f . 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 ? . n a v i g a t i o n B a r ( ) e l s e { r e t u r n }
//
// g u a r d l e t d i f f a b l e D a t a S o u r c e = s e l f . d i f f a b l e D a t a S o u r c e e l s e { r e t u r n }
// l e t o l d S n a p s h o t = d i f f a b l e D a t a S o u r c e . s n a p s h o t ( )
//
// l e t p r e d i c a t e = f e t c h e d R e s u l t s C o n t r o l l e r . f e t c h R e q u e s t . p r e d i c a t e
// l e t p a r e n t M a n a g e d O b j e c t C o n t e x t = f e t c h e d R e s u l t s C o n t r o l l e r . m a n a g e d O b j e c t C o n t e x t
// l e t m a n a g e d O b j e c t C o n t e x t = N S M a n a g e d O b j e c t C o n t e x t ( c o n c u r r e n c y T y p e : . p r i v a t e Q u e u e C o n c u r r e n c y T y p e )
// m a n a g e d O b j e c t C o n t e x t . p a r e n t = p a r e n t M a n a g e d O b j e c t C o n t e x t
//
// m a n a g e d O b j e c t C o n t e x t . p e r f o r m {
// v a r s h o u l d A d d B o t t o m L o a d e r = f a l s e
//
// l e t t i m e l i n e I n d e x e s : [ H o m e T i m e l i n e I n d e x ] = {
// l e t r e q u e s t = H o m e T i m e l i n e I n d e x . s o r t e d F e t c h R e q u e s t
// r e q u e s t . r e t u r n s O b j e c t s A s F a u l t s = f a l s e
// r e q u e s t . p r e d i c a t e = p r e d i c a t e
// d o {
// r e t u r n t r y m a n a g e d O b j e c t C o n t e x t . f e t c h ( r e q u e s t )
// } c a t c h {
// a s s e r t i o n F a i l u r e ( e r r o r . l o c a l i z e d D e s c r i p t i o n )
// r e t u r n [ ]
// }
// } ( )
//
// / / t h a t ' s w i l l b e t h e m o s t f a s t e s t f e t c h b e c a u s e o f u p s t r e a m j u s t u p d a t e a n d n o m o d i f y n e e d s c o n s i d e r
//
// v a r o l d S n a p s h o t A t t r i b u t e D i c t : [ N S M a n a g e d O b j e c t I D : I t e m . S t a t u s A t t r i b u t e ] = [ : ]
//
// f o r i t e m i n o l d S n a p s h o t . i t e m I d e n t i f i e r s {
// g u a r d c a s e l e t . h o m e T i m e l i n e I n d e x ( o b j e c t I D , a t t r i b u t e ) = i t e m e l s e { c o n t i n u e }
// o l d S n a p s h o t A t t r i b u t e D i c t [ o b j e c t I D ] = a t t r i b u t e
// }
//
// v a r n e w T i m e l i n e I t e m s : [ I t e m ] = [ ]
//
// f o r ( i , t i m e l i n e I n d e x ) i n t i m e l i n e I n d e x e s . e n u m e r a t e d ( ) {
// l e t a t t r i b u t e = o l d S n a p s h o t A t t r i b u t e D i c t [ t i m e l i n e I n d e x . o b j e c t I D ] ? ? I t e m . S t a t u s A t t r i b u t e ( )
// a t t r i b u t e . i s S e p a r a t o r L i n e H i d d e n = f a l s e
//
// / / a p p e n d n e w i t e m i n t o s n a p s h o t
// n e w T i m e l i n e I t e m s . a p p e n d ( . h o m e T i m e l i n e I n d e x ( o b j e c t I D : t i m e l i n e I n d e x . o b j e c t I D , a t t r i b u t e : a t t r i b u t e ) )
//
// l e t i s L a s t = i = = t i m e l i n e I n d e x e s . c o u n t - 1
// s w i t c h ( i s L a s t , t i m e l i n e I n d e x . h a s M o r e ) {
// c a s e ( f a l s e , t r u e ) :
// n e w T i m e l i n e I t e m s . a p p e n d ( . h o m e M i d d l e L o a d e r ( u p p e r T i m e l i n e I n d e x A n c h o r O b j e c t I D : t i m e l i n e I n d e x . o b j e c t I D ) )
// a t t r i b u t e . i s S e p a r a t o r L i n e H i d d e n = t r u e
// c a s e ( t r u e , t r u e ) :
// s h o u l d A d d B o t t o m L o a d e r = t r u e
// d e f a u l t :
// b r e a k
// }
// } / / e n d f o r
//
// v a r n e w S n a p s h o t = N S D i f f a b l e D a t a S o u r c e S n a p s h o t < S t a t u s S e c t i o n , I t e m > ( )
// n e w S n a p s h o t . a p p e n d S e c t i o n s ( [ . m a i n ] )
// n e w S n a p s h o t . a p p e n d I t e m s ( n e w T i m e l i n e I t e m s , t o S e c t i o n : . m a i n )
//
// l e t e n d S n a p s h o t = C A C u r r e n t M e d i a T i m e ( )
//
// D i s p a t c h Q u e u e . m a i n . a s y n c {
// i f s h o u l d A d d B o t t o m L o a d e r , ! ( s e l f . l o a d L a t e s t S t a t e M a c h i n e . c u r r e n t S t a t e i s L o a d O l d e s t S t a t e . N o M o r e ) {
// n e w S n a p s h o t . a p p e n d I t e m s ( [ . b o t t o m L o a d e r ] , t o S e c t i o n : . m a i n )
// }
//
// g u a r d l e t d i f f e r e n c e = s e l f . c a l c u l a t e R e l o a d S n a p s h o t D i f f e r e n c e ( n a v i g a t i o n B a r : n a v i g a t i o n B a r , t a b l e V i e w : t a b l e V i e w , o l d S n a p s h o t : o l d S n a p s h o t , n e w S n a p s h o t : n e w S n a p s h o t ) e l s e {
// d i f f a b l e D a t a S o u r c e . a p p l y ( n e w S n a p s h o t )
// s e l f . i s F e t c h i n g L a t e s t T i m e l i n e . v a l u e = f a l s e
// r e t u r n
// }
//
// d i f f a b l e D a t a S o u r c e . r e l o a d D a t a ( s n a p s h o t : n e w S n a p s h o t ) {
// t a b l e V i e w . s c r o l l T o R o w ( a t : d i f f e r e n c e . t a r g e t I n d e x P a t h , a t : . t o p , a n i m a t e d : f a l s e )
// t a b l e V i e w . c o n t e n t O f f s e t . y = t a b l e V i e w . c o n t e n t O f f s e t . y - d i f f e r e n c e . o f f s e t
// s e l f . i s F e t c h i n g L a t e s t T i m e l i n e . v a l u e = f a l s e
// }
//
// l e t e n d = C A C u r r e n t M e d i a T i m e ( )
// o s _ l o g ( " % { p u b l i c } s [ % { p u b l i c } l d ] , % { p u b l i c } s : c a l c u l a t e h o m e t i m e l i n e l a y o u t c o s t % . 2 f s " , ( ( # f i l e a s N S S t r i n g ) . l a s t P a t h C o m p o n e n t ) , # l i n e , # f u n c t i o n , e n d - e n d S n a p s h o t )
// }
// } / / e n d p e r f o r m
// }
//
// p r i v a t e s t r u c t D i f f e r e n c e < T > {
// l e t i t e m : T
// l e t s o u r c e I n d e x P a t h : I n d e x P a t h
// l e t t a r g e t I n d e x P a t h : I n d e x P a t h
// l e t o f f s e t : C G F l o a t
// }
//
// p r i v a t e f u n c c a l c u l a t e R e l o a d S n a p s h o t D i f f e r e n c e < T : H a s h a b l e > (
// n a v i g a t i o n B a r : U I N a v i g a t i o n B a r ,
// t a b l e V i e w : U I T a b l e V i e w ,
// o l d S n a p s h o t : N S D i f f a b l e D a t a S o u r c e S n a p s h o t < S t a t u s S e c t i o n , T > ,
// n e w S n a p s h o t : N S D i f f a b l e D a t a S o u r c e S n a p s h o t < S t a t u s S e c t i o n , T >
// ) - > D i f f e r e n c e < T > ? {
// g u a r d o l d S n a p s h o t . n u m b e r O f I t e m s ! = 0 e l s e { r e t u r n n i l }
//
// / / o l d s n a p s h o t n o t e m p t y . s e t s o u r c e i n d e x p a t h t o f i r s t i t e m i f n o t m a t c h
// l e t s o u r c e I n d e x P a t h = U I V i e w C o n t r o l l e r . t o p V i s i b l e T a b l e V i e w C e l l I n d e x P a t h ( i n : t a b l e V i e w , n a v i g a t i o n B a r : n a v i g a t i o n B a r ) ? ? I n d e x P a t h ( r o w : 0 , s e c t i o n : 0 )
//
// g u a r d s o u r c e I n d e x P a t h . r o w < o l d S n a p s h o t . i t e m I d e n t i f i e r s ( i n S e c t i o n : . m a i n ) . c o u n t e l s e { r e t u r n n i l }
//
// l e t t i m e l i n e I t e m = o l d S n a p s h o t . i t e m I d e n t i f i e r s ( i n S e c t i o n : . m a i n ) [ s o u r c e I n d e x P a t h . r o w ]
// g u a r d l e t i t e m I n d e x = n e w S n a p s h o t . i t e m I d e n t i f i e r s ( i n S e c t i o n : . m a i n ) . f i r s t I n d e x ( o f : t i m e l i n e I t e m ) e l s e { r e t u r n n i l }
// l e t t a r g e t I n d e x P a t h = I n d e x P a t h ( r o w : i t e m I n d e x , s e c t i o n : 0 )
//
// l e t o f f s e t = U I V i e w C o n t r o l l e r . t a b l e V i e w C e l l O r i g i n O f f s e t T o W i n d o w T o p ( i n : t a b l e V i e w , a t : s o u r c e I n d e x P a t h , n a v i g a t i o n B a r : n a v i g a t i o n B a r )
// r e t u r n D i f f e r e n c e (
// i t e m : t i m e l i n e I t e m ,
// s o u r c e I n d e x P a t h : s o u r c e I n d e x P a t h ,
// t a r g e t I n d e x P a t h : t a r g e t I n d e x P a t h ,
// o f f s e t : o f f s e t
// )
// }
//
// }