@ -30,7 +30,7 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
public static let pageSize : Int = 50
public static let pageSize : Int = 50
private let threadId : String
private var threadId : String
public let initialThreadVariant : SessionThread . Variant
public let initialThreadVariant : SessionThread . Variant
public var sentMessageBeforeUpdate : Bool = false
public var sentMessageBeforeUpdate : Bool = false
public var lastSearchedText : String ?
public var lastSearchedText : String ?
@ -62,7 +62,73 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
// a l s o w a n t t o s k i p t h e i n i t i a l q u e r y a n d t r i g g e r i t a s y n c s o t h a t t h e p u s h a n i m a t i o n
// a l s o w a n t t o s k i p t h e i n i t i a l q u e r y a n d t r i g g e r i t a s y n c s o t h a t t h e p u s h a n i m a t i o n
// d o e s n ' t s t u t t e r ( i t s h o u l d l o a d b a s i c a l l y i m m e d i a t e l y b u t w i t h o u t t h i s t h e r e i s a
// d o e s n ' t s t u t t e r ( i t s h o u l d l o a d b a s i c a l l y i m m e d i a t e l y b u t w i t h o u t t h i s t h e r e i s a
// d i s t i n c t s t u t t e r )
// d i s t i n c t s t u t t e r )
self . pagedDataObserver = PagedDatabaseObserver (
self . pagedDataObserver = self . setupPagedObserver ( for : threadId )
// R u n t h e i n i t i a l q u e r y o n a b a c k g o r u n d t h r e a d s o w e d o n ' t b l o c k t h e p u s h t r a n s i t i o n
DispatchQueue . global ( qos : . default ) . async { [ weak self ] in
// I f w e d o n ' t h a v e a ` i n i t i a l F o c u s e d I d ` t h e n d e f a u l t t o ` . p a g e B e f o r e ` ( i t ' l l q u e r y
// f r o m a ` 0 ` o f f s e t )
guard let initialFocusedId : Int64 = focusedInteractionId else {
self ? . pagedDataObserver ? . load ( . pageBefore )
return
}
self ? . pagedDataObserver ? . load ( . initialPageAround ( id : initialFocusedId ) )
}
}
// MARK: - T h r e a d D a t a
// / T h i s v a l u e i s t h e c u r r e n t s t a t e o f t h e v i e w
public private ( set ) var threadData : SessionThreadViewModel = SessionThreadViewModel ( )
// / T h i s i s a l l t h e d a t a t h e s c r e e n n e e d s t o p o p u l a t e i t s e l f , p l e a s e s e e t h e f o l l o w i n g l i n k f o r t i p s t o h e l p o p t i m i s e
// / p e r f o r m a n c e h t t p s : / / g i t h u b . c o m / g r o u e / G R D B . s w i f t # v a l u e o b s e r v a t i o n - p e r f o r m a n c e
// /
// / * * N o t e : * * T h e ' t r a c k i n g C o n s t a n t R e g i o n ' i s o p t i m i s e d i n s u c h a w a y t h a t t h e r e q u e s t n e e d s t o b e s t a t i c
// / o t h e r w i s e t h e r e m a y b e s i t u a t i o n s w h e r e i t d o e s n ' t g e t u p d a t e s , t h i s m e a n s w e c a n ' t h a v e c o n d i t i o n a l q u e r i e s
// /
// / * * N o t e : * * T h i s o b s e r v a t i o n w i l l b e t r i g g e r e d t w i c e i m m e d i a t e l y ( a n d b e d e - d u p e d b y t h e ` r e m o v e D u p l i c a t e s ` )
// / t h i s i s d u e t o t h e b e h a v i o u r o f ` V a l u e C o n c u r r e n t O b s e r v e r . a s y n c S t a r t O b s e r v a t i o n ` w h i c h t r i g g e r s i t ' s o w n
// / f e t c h ( a f t e r t h e o n e s i n ` V a l u e C o n c u r r e n t O b s e r v e r . a s y n c S t a r t ` / ` V a l u e C o n c u r r e n t O b s e r v e r . s y n c S t a r t ` )
// / j u s t i n c a s e t h e d a t a b a s e h a s c h a n g e d b e t w e e n t h e t w o r e a d s - u n f o r t u n a t e l y i t d o e s n ' t l o o k l i k e t h e r e i s a w a y t o p r e v e n t t h i s
public lazy var observableThreadData : ValueObservation < ValueReducers . RemoveDuplicates < ValueReducers . Fetch < SessionThreadViewModel ? >>> = setupObservableThreadData ( for : self . threadId )
private func setupObservableThreadData ( for threadId : String ) -> ValueObservation < ValueReducers . RemoveDuplicates < ValueReducers . Fetch < SessionThreadViewModel ? >>> {
return ValueObservation
. trackingConstantRegion { db -> SessionThreadViewModel ? in
let userPublicKey : String = getUserHexEncodedPublicKey ( db )
return try SessionThreadViewModel
. conversationQuery ( threadId : threadId , userPublicKey : userPublicKey )
. fetchOne ( db )
}
. removeDuplicates ( )
}
public func updateThreadData ( _ updatedData : SessionThreadViewModel ) {
self . threadData = updatedData
}
// MARK: - I n t e r a c t i o n D a t a
public private ( set ) var unobservedInteractionDataChanges : [ SectionModel ] ?
public private ( set ) var interactionData : [ SectionModel ] = [ ]
public private ( set ) var pagedDataObserver : PagedDatabaseObserver < Interaction , MessageViewModel > ?
public var onInteractionChange : ( ( [ SectionModel ] ) -> ( ) ) ? {
didSet {
// W h e n s t a r t i n g t o o b s e r v e i n t e r a c t i o n c h a n g e s w e w a n t t o t r i g g e r a U I u p d a t e j u s t i n c a s e t h e
// d a t a w a s c h a n g e d w h i l e w e w e r e n ' t o b s e r v i n g
if let unobservedInteractionDataChanges : [ SectionModel ] = self . unobservedInteractionDataChanges {
onInteractionChange ? ( unobservedInteractionDataChanges )
self . unobservedInteractionDataChanges = nil
}
}
}
private func setupPagedObserver ( for threadId : String ) -> PagedDatabaseObserver < Interaction , MessageViewModel > {
return PagedDatabaseObserver (
pagedTable : Interaction . self ,
pagedTable : Interaction . self ,
pageSize : ConversationViewModel . pageSize ,
pageSize : ConversationViewModel . pageSize ,
idColumn : . id ,
idColumn : . id ,
@ -113,58 +179,20 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
return
return
}
}
self ? . onInteractionChange ? ( updatedInteractionData )
// I f w e h a v e t h e ' o n I n t e r a c t i o n C h a n g e d ' c a l l b a c k t h e n t r i g g e r i t , o t h e r w i s e j u s t s t o r e t h e c h a n g e s
// t o b e s e n t t o t h e c a l l b a c k i f w e e v e r s t a r t o b s e r v i n g a g a i n ( w h e n w e h a v e t h e c a l l b a c k i t n e e d s
// t o d o t h e d a t a u p d a t i n g a s i t ' s t i e d t o U I u p d a t e s a n d c a n c a u s e c r a s h e s i f n o t u p d a t e d i n t h e
// c o r r e c t o r d e r )
guard let onInteractionChange : ( ( [ SectionModel ] ) -> ( ) ) = self ? . onInteractionChange else {
self ? . unobservedInteractionDataChanges = updatedInteractionData
return
}
onInteractionChange ( updatedInteractionData )
}
}
)
)
// R u n t h e i n i t i a l q u e r y o n a b a c k g o r u n d t h r e a d s o w e d o n ' t b l o c k t h e p u s h t r a n s i t i o n
DispatchQueue . global ( qos : . default ) . async { [ weak self ] in
// I f w e d o n ' t h a v e a ` i n i t i a l F o c u s e d I d ` t h e n d e f a u l t t o ` . p a g e B e f o r e ` ( i t ' l l q u e r y
// f r o m a ` 0 ` o f f s e t )
guard let initialFocusedId : Int64 = focusedInteractionId else {
self ? . pagedDataObserver ? . load ( . pageBefore )
return
}
self ? . pagedDataObserver ? . load ( . initialPageAround ( id : initialFocusedId ) )
}
}
// MARK: - T h r e a d D a t a
// / T h i s v a l u e i s t h e c u r r e n t s t a t e o f t h e v i e w
public private ( set ) var threadData : SessionThreadViewModel = SessionThreadViewModel ( )
// / T h i s i s a l l t h e d a t a t h e s c r e e n n e e d s t o p o p u l a t e i t s e l f , p l e a s e s e e t h e f o l l o w i n g l i n k f o r t i p s t o h e l p o p t i m i s e
// / p e r f o r m a n c e h t t p s : / / g i t h u b . c o m / g r o u e / G R D B . s w i f t # v a l u e o b s e r v a t i o n - p e r f o r m a n c e
// /
// / * * N o t e : * * T h e ' t r a c k i n g C o n s t a n t R e g i o n ' i s o p t i m i s e d i n s u c h a w a y t h a t t h e r e q u e s t n e e d s t o b e s t a t i c
// / o t h e r w i s e t h e r e m a y b e s i t u a t i o n s w h e r e i t d o e s n ' t g e t u p d a t e s , t h i s m e a n s w e c a n ' t h a v e c o n d i t i o n a l q u e r i e s
// /
// / * * N o t e : * * T h i s o b s e r v a t i o n w i l l b e t r i g g e r e d t w i c e i m m e d i a t e l y ( a n d b e d e - d u p e d b y t h e ` r e m o v e D u p l i c a t e s ` )
// / t h i s i s d u e t o t h e b e h a v i o u r o f ` V a l u e C o n c u r r e n t O b s e r v e r . a s y n c S t a r t O b s e r v a t i o n ` w h i c h t r i g g e r s i t ' s o w n
// / f e t c h ( a f t e r t h e o n e s i n ` V a l u e C o n c u r r e n t O b s e r v e r . a s y n c S t a r t ` / ` V a l u e C o n c u r r e n t O b s e r v e r . s y n c S t a r t ` )
// / j u s t i n c a s e t h e d a t a b a s e h a s c h a n g e d b e t w e e n t h e t w o r e a d s - u n f o r t u n a t e l y i t d o e s n ' t l o o k l i k e t h e r e i s a w a y t o p r e v e n t t h i s
public lazy var observableThreadData = ValueObservation
. trackingConstantRegion { [ threadId = self . threadId ] db -> SessionThreadViewModel ? in
let userPublicKey : String = getUserHexEncodedPublicKey ( db )
return try SessionThreadViewModel
. conversationQuery ( threadId : threadId , userPublicKey : userPublicKey )
. fetchOne ( db )
}
. removeDuplicates ( )
public func updateThreadData ( _ updatedData : SessionThreadViewModel ) {
self . threadData = updatedData
}
}
// MARK: - I n t e r a c t i o n D a t a
public private ( set ) var interactionData : [ SectionModel ] = [ ]
public private ( set ) var pagedDataObserver : PagedDatabaseObserver < Interaction , MessageViewModel > ?
public var onInteractionChange : ( ( [ SectionModel ] ) -> ( ) ) ?
private func process ( data : [ MessageViewModel ] , for pageInfo : PagedData . PageInfo ) -> [ SectionModel ] {
private func process ( data : [ MessageViewModel ] , for pageInfo : PagedData . PageInfo ) -> [ SectionModel ] {
let typingIndicator : MessageViewModel ? = data . first ( where : { $0 . isTypingIndicator = = true } )
let typingIndicator : MessageViewModel ? = data . first ( where : { $0 . isTypingIndicator = = true } )
let sortedData : [ MessageViewModel ] = data
let sortedData : [ MessageViewModel ] = data
@ -361,6 +389,26 @@ public class ConversationViewModel: OWSAudioPlayerDelegate {
}
}
}
}
public func swapToThread ( updatedThreadId : String ) {
let oldestMessageId : Int64 ? = self . interactionData
. filter { $0 . model = = . messages }
. first ?
. elements
. first ?
. id
self . threadId = updatedThreadId
self . observableThreadData = self . setupObservableThreadData ( for : updatedThreadId )
self . pagedDataObserver = self . setupPagedObserver ( for : updatedThreadId )
// T r y l o a d e v e r y t h i n g u p t o t h e i n i t i a l v i s i b l e m e s s a g e , f a l l b a c k t o j u s t t h e i n i t i a l p a g e o f m e s s a g e s
// i f w e d o n ' t h a v e o n e
switch oldestMessageId {
case . some ( let id ) : self . pagedDataObserver ? . load ( . untilInclusive ( id : id , padding : 0 ) )
case . none : self . pagedDataObserver ? . load ( . pageBefore )
}
}
// MARK: - A u d i o P l a y b a c k
// MARK: - A u d i o P l a y b a c k
public struct PlaybackInfo {
public struct PlaybackInfo {