diff --git a/Signal/Calls/UserInterface/CallsListViewController+ViewModelLoader.swift b/Signal/Calls/UserInterface/CallsListViewController+ViewModelLoader.swift index 1707af907b..a94075ed92 100644 --- a/Signal/Calls/UserInterface/CallsListViewController+ViewModelLoader.swift +++ b/Signal/Calls/UserInterface/CallsListViewController+ViewModelLoader.swift @@ -29,9 +29,8 @@ extension CallsListViewController { /// changes. When the user scrolls to the bottom of the list, callers should /// request additional items. struct ViewModelLoader { - typealias CreateCallViewModelBlock = ( - _ primaryCallRecord: CallRecord, - _ coalescedCallRecords: [CallRecord], + typealias CallViewModelForCallRecords = ( + _ callRecords: [CallRecord], _ tx: DBReadTransaction ) -> CallViewModel @@ -46,7 +45,7 @@ extension CallsListViewController { } private let callRecordLoader: CallRecordLoader - private let createCallViewModelBlock: CreateCallViewModelBlock + private let callViewModelForCallRecords: CallViewModelForCallRecords private let fetchCallRecordBlock: FetchCallRecordBlock private let viewModelPageSize: Int private let maxCachedViewModelCount: Int @@ -54,7 +53,7 @@ extension CallsListViewController { init( callRecordLoader: CallRecordLoader, - createCallViewModelBlock: @escaping CreateCallViewModelBlock, + callViewModelForCallRecords: @escaping CallViewModelForCallRecords, fetchCallRecordBlock: @escaping FetchCallRecordBlock, viewModelPageSize: Int = 50, maxCachedViewModelCount: Int = 150, @@ -66,7 +65,7 @@ extension CallsListViewController { ) self.callRecordLoader = callRecordLoader - self.createCallViewModelBlock = createCallViewModelBlock + self.callViewModelForCallRecords = callViewModelForCallRecords self.fetchCallRecordBlock = fetchCallRecordBlock self.viewModelPageSize = viewModelPageSize self.maxCachedViewModelCount = maxCachedViewModelCount @@ -250,9 +249,8 @@ extension CallsListViewController { callHistoryItemReferences[0] = CallHistoryItemReference( callRecordIds: combinedGroupOfCallRecords.map(\.id) ) - viewModels[0] = createCallViewModelBlock( - combinedGroupOfCallRecords.first, - Array(combinedGroupOfCallRecords.rawValue.dropFirst()), + viewModels[0] = callViewModelForCallRecords( + combinedGroupOfCallRecords.rawValue, tx ) fetchResult = fetchResult.dropLast() @@ -293,7 +291,7 @@ extension CallsListViewController { let callRecords = reference.callRecordIds.map { return fetchCallRecordBlock($0, tx)! } - return createCallViewModelBlock(callRecords.first, Array(callRecords.rawValue.dropFirst()), tx) + return callViewModelForCallRecords(callRecords.rawValue, tx) } return newViewModels @@ -458,7 +456,7 @@ extension CallsListViewController { continue } let newCallRecords = viewModel.callRecords.map(refreshIfPossible(_:)) - viewModels[index] = createCallViewModelBlock(newCallRecords.first!, Array(newCallRecords.dropFirst()), tx) + viewModels[index] = callViewModelForCallRecords(newCallRecords, tx) refreshedViewModelReferences.append(viewModels[index].reference) } diff --git a/Signal/Calls/UserInterface/CallsListViewController.swift b/Signal/Calls/UserInterface/CallsListViewController.swift index 8a13c9f10d..5c4fae33f1 100644 --- a/Signal/Calls/UserInterface/CallsListViewController.swift +++ b/Signal/Calls/UserInterface/CallsListViewController.swift @@ -685,10 +685,9 @@ class CallsListViewController: OWSViewController, HomeTabViewController, CallSer // Reset our loaded calls. self.viewModelLoader = ViewModelLoader( callRecordLoader: callRecordLoader, - createCallViewModelBlock: { primaryCallRecord, coalescedCallRecords, tx in - return Self.createCallViewModel( - primaryCallRecord: primaryCallRecord, - coalescedCallRecords: coalescedCallRecords, + callViewModelForCallRecords: { callRecords, tx in + return Self.callViewModel( + forCallRecords: callRecords, deps: capturedDeps, tx: SDSDB.shimOnlyBridge(tx) ) @@ -752,42 +751,44 @@ class CallsListViewController: OWSViewController, HomeTabViewController, CallSer /// - Important /// The primary and and coalesced call records *must* all have the same /// thread, direction, missed status, and call type. - private static func createCallViewModel( - primaryCallRecord: CallRecord, - coalescedCallRecords: [CallRecord], + private static func callViewModel( + forCallRecords callRecords: [CallRecord], deps: Dependencies, tx: SDSAnyReadTransaction ) -> CallViewModel { + owsPrecondition(!callRecords.isEmpty) owsPrecondition( - coalescedCallRecords.allSatisfy { $0.threadRowId == primaryCallRecord.threadRowId }, + Set(callRecords.map(\.threadRowId)).count == 1, "Coalesced call records were for a different thread than the primary!" ) owsPrecondition( - coalescedCallRecords.allSatisfy { $0.callDirection == primaryCallRecord.callDirection }, + Set(callRecords.map(\.callDirection)).count == 1, "Coalesced call records were of a different direction than the primary!" ) owsPrecondition( - coalescedCallRecords.allSatisfy { $0.callStatus.isMissedCall == primaryCallRecord.callStatus.isMissedCall }, + Set(callRecords.map(\.callStatus.isMissedCall)).count == 1, "Coalesced call records were of a different missed status than the primary!" ) owsPrecondition( - ([primaryCallRecord] + coalescedCallRecords).isSortedByTimestamp(.descending), + callRecords.isSortedByTimestamp(.descending), "Primary and coalesced call records were not ordered descending by timestamp!" ) + let mostRecentCallRecord = callRecords.first! + guard let callThread = deps.threadStore.fetchThread( - rowId: primaryCallRecord.threadRowId, + rowId: mostRecentCallRecord.threadRowId, tx: tx.asV2Read ) else { owsFail("Missing thread for call record! This should be impossible, per the DB schema.") } let callDirection: CallViewModel.Direction = { - if primaryCallRecord.callStatus.isMissedCall { + if mostRecentCallRecord.callStatus.isMissedCall { return .missed } - switch primaryCallRecord.callDirection { + switch mostRecentCallRecord.callDirection { case .incoming: return .incoming case .outgoing: return .outgoing } @@ -798,9 +799,9 @@ class CallsListViewController: OWSViewController, HomeTabViewController, CallSer let callState: CallViewModel.State = { let currentCallId: UInt64? = deps.callService.callServiceState.currentCall?.callId - switch primaryCallRecord.callStatus { + switch mostRecentCallRecord.callStatus { case .individual: - if primaryCallRecord.callId == currentCallId { + if mostRecentCallRecord.callId == currentCallId { // We can have at most one 1:1 call active at a time, and if // we have an active 1:1 call we must be in it. All other // 1:1 calls must have ended. @@ -809,7 +810,7 @@ class CallsListViewController: OWSViewController, HomeTabViewController, CallSer case .group: guard let groupCallInteraction: OWSGroupCallMessage = deps.interactionStore .fetchAssociatedInteraction( - callRecord: primaryCallRecord, tx: tx.asV2Read + callRecord: mostRecentCallRecord, tx: tx.asV2Read ) else { owsFail("Missing interaction for group call. This should be impossible per the DB schema!") @@ -820,7 +821,7 @@ class CallsListViewController: OWSViewController, HomeTabViewController, CallSer // leetle wonky that we use the interaction to store that info, // but such is life. if !groupCallInteraction.hasEnded { - if primaryCallRecord.callId == currentCallId { + if mostRecentCallRecord.callId == currentCallId { return .participating } @@ -838,7 +839,7 @@ class CallsListViewController: OWSViewController, HomeTabViewController, CallSer case let contactThread as TSContactThread: title = deps.contactsManager.displayName(for: contactThread.contactAddress, tx: tx).resolvedValue() let callType: CallViewModel.RecipientType.CallType = { - switch primaryCallRecord.callType { + switch mostRecentCallRecord.callType { case .audioCall: return .audio case .groupCall: @@ -857,8 +858,8 @@ class CallsListViewController: OWSViewController, HomeTabViewController, CallSer } return CallViewModel( - reference: .callRecords(primaryId: primaryCallRecord.id, coalescedIds: coalescedCallRecords.map(\.id)), - callRecords: [primaryCallRecord] + coalescedCallRecords, + reference: .callRecords(primaryId: mostRecentCallRecord.id, coalescedIds: callRecords.dropFirst().map(\.id)), + callRecords: callRecords, title: title, recipientType: recipientType, direction: callDirection, diff --git a/Signal/test/CallsTab/CallsListViewController+ViewModelLoaderTest.swift b/Signal/test/CallsTab/CallsListViewController+ViewModelLoaderTest.swift index 2ad61c0c69..17edbd544b 100644 --- a/Signal/test/CallsTab/CallsListViewController+ViewModelLoaderTest.swift +++ b/Signal/test/CallsTab/CallsListViewController+ViewModelLoaderTest.swift @@ -16,20 +16,16 @@ final class CallsListViewControllerViewModelLoaderTest: XCTestCase { private var mockDB: MockDB! private var mockCallRecordLoader: MockCallRecordLoader! - private lazy var createCallViewModelBlock: ViewModelLoader.CreateCallViewModelBlock! = { - self.createCallViewModel(primaryCallRecord: $0, coalescedCallRecords: $1, tx: $2) + private lazy var callViewModelForCallRecords: ViewModelLoader.CallViewModelForCallRecords! = { + self.createCallViewModel(callRecords: $0, tx: $1) } private lazy var fetchCallRecordBlock: ViewModelLoader.FetchCallRecordBlock! = { (callRecordId, tx) -> CallRecord? in return self.mockCallRecordLoader.callRecordsById[callRecordId] } - private func createCallViewModel( - primaryCallRecord: CallRecord, - coalescedCallRecords: [CallRecord], - tx: DBReadTransaction - ) -> CallViewModel { + private func createCallViewModel(callRecords: [CallRecord], tx: DBReadTransaction) -> CallViewModel { let recipientType: CallViewModel.RecipientType = { - switch primaryCallRecord.callStatus { + switch callRecords.first!.callStatus { case .individual: return .individual(type: .audio, contactThread: TSContactThread( contactUUID: UUID().uuidString, @@ -41,19 +37,19 @@ final class CallsListViewControllerViewModelLoaderTest: XCTestCase { }() let direction: CallViewModel.Direction = { - if primaryCallRecord.callStatus.isMissedCall { + if callRecords.first!.callStatus.isMissedCall { return .missed } - switch primaryCallRecord.callDirection { + switch callRecords.first!.callDirection { case .incoming: return .incoming case .outgoing: return .outgoing } }() return CallViewModel( - reference: .callRecords(primaryId: primaryCallRecord.id, coalescedIds: coalescedCallRecords.map(\.id)), - callRecords: [primaryCallRecord] + coalescedCallRecords, + reference: .callRecords(primaryId: callRecords.first!.id, coalescedIds: callRecords.dropFirst().map(\.id)), + callRecords: callRecords, title: "Hey, I just met you, and this is crazy, but here's my number, so call me maybe?", recipientType: recipientType, direction: direction, @@ -68,7 +64,7 @@ final class CallsListViewControllerViewModelLoaderTest: XCTestCase { ) { viewModelLoader = ViewModelLoader( callRecordLoader: mockCallRecordLoader, - createCallViewModelBlock: { self.createCallViewModelBlock($0, $1, $2) }, + callViewModelForCallRecords: { self.callViewModelForCallRecords($0, $1) }, fetchCallRecordBlock: { self.fetchCallRecordBlock($0, $1) }, viewModelPageSize: viewModelPageSize, maxCachedViewModelCount: maxCachedViewModelCount,