diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 660b29e7b..c3c767ae2 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -591,12 +591,13 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers snInputView.text = draft } + // Now we have done all the needed diffs, update the viewModel with the latest data + self.viewModel.updateThreadData(updatedThreadData) + + /// **Note:** This needs to happen **after** we have update the viewModel's thread data if viewModel.threadData.currentUserIsClosedGroupMember != updatedThreadData.currentUserIsClosedGroupMember { reloadInputViews() } - - // Now we have done all the needed diffs, update the viewModel with the latest data - self.viewModel.updateThreadData(updatedThreadData) } private func handleInteractionUpdates(_ updatedData: [ConversationViewModel.SectionModel], initialLoad: Bool = false) { @@ -837,7 +838,7 @@ final class ConversationVC: BaseVC, OWSConversationSettingsViewDelegate, Convers // Note: We sort the headers as we want to prioritise loading newer pages over older ones let sections: [(ConversationViewModel.Section, CGRect)] = (self?.viewModel.interactionData .enumerated() - .map { index, section in (section.model, (self?.tableView.rectForHeader(inSection: 0) ?? .zero)) }) + .map { index, section in (section.model, (self?.tableView.rectForHeader(inSection: index) ?? .zero)) }) .defaulting(to: []) let shouldLoadOlder: Bool = sections .contains { section, headerRect in diff --git a/Session/Conversations/ConversationViewModel.swift b/Session/Conversations/ConversationViewModel.swift index c5fb9aca5..d6e3aa314 100644 --- a/Session/Conversations/ConversationViewModel.swift +++ b/Session/Conversations/ConversationViewModel.swift @@ -98,7 +98,20 @@ public class ConversationViewModel: OWSAudioPlayerDelegate { // MARK: - Thread Data /// This value is the current state of the view - public private(set) var threadData: SessionThreadViewModel = SessionThreadViewModel() + public private(set) lazy var threadData: SessionThreadViewModel = SessionThreadViewModel( + threadId: self.threadId, + threadVariant: self.initialThreadVariant, + currentUserIsClosedGroupMember: (self.initialThreadVariant != .closedGroup ? + nil : + GRDBStorage.shared.read { db in + try GroupMember + .filter(GroupMember.Columns.groupId == self.threadId) + .filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db)) + .filter(GroupMember.Columns.role == GroupMember.Role.standard) + .isNotEmpty(db) + } + ) + ) /// This is all the data the screen needs to populate itself, please see the following link for tips to help optimise /// performance https://github.com/groue/GRDB.swift#valueobservation-performance diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index ca1d002b5..67c08c973 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -15,6 +15,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve private var hasLoadedInitialStateData: Bool = false private var hasLoadedInitialThreadData: Bool = false private var isLoadingMore: Bool = false + private var isAutoLoadingNextPage: Bool = false // MARK: - Intialization @@ -319,6 +320,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve CATransaction.setCompletionBlock { [weak self] in // Complete page loading self?.isLoadingMore = false + self?.autoLoadNextPageIfNeeded() } // Reload the table content (animate changes after the first load) @@ -328,7 +330,7 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve insertSectionsAnimation: .none, reloadSectionsAnimation: .none, deleteRowsAnimation: .bottom, - insertRowsAnimation: .top, + insertRowsAnimation: .none, reloadRowsAnimation: .none, interrupt: { $0.changeCount > 100 } // Prevent too many changes from causing performance issues ) { [weak self] updatedData in @@ -338,6 +340,36 @@ final class HomeVC: BaseVC, UITableViewDataSource, UITableViewDelegate, NewConve CATransaction.commit() } + private func autoLoadNextPageIfNeeded() { + guard !self.isAutoLoadingNextPage && !self.isLoadingMore else { return } + + self.isAutoLoadingNextPage = true + + DispatchQueue.main.asyncAfter(deadline: .now() + PagedData.autoLoadNextPageDelay) { [weak self] in + self?.isAutoLoadingNextPage = false + + // Note: We sort the headers as we want to prioritise loading newer pages over older ones + let sections: [(HomeViewModel.Section, CGRect)] = (self?.viewModel.threadData + .enumerated() + .map { index, section in (section.model, (self?.tableView.rectForHeader(inSection: index) ?? .zero)) }) + .defaulting(to: []) + let shouldLoadMore: Bool = sections + .contains { section, headerRect in + section == .loadMore && + headerRect != .zero && + (self?.tableView.bounds.contains(headerRect) == true) + } + + guard shouldLoadMore else { return } + + self?.isLoadingMore = true + + DispatchQueue.global(qos: .default).async { [weak self] in + self?.viewModel.pagedDataObserver?.load(.pageAfter) + } + } + } + private func updateNavBarButtons() { // Profile picture view let profilePictureSize = Values.verySmallProfilePictureSize diff --git a/Session/Home/HomeViewModel.swift b/Session/Home/HomeViewModel.swift index 543830292..a1ff37bfd 100644 --- a/Session/Home/HomeViewModel.swift +++ b/Session/Home/HomeViewModel.swift @@ -19,7 +19,7 @@ public class HomeViewModel { // MARK: - Variables - public static let pageSize: Int = 14 + public static let pageSize: Int = 15 public struct State: Equatable { let showViewedSeedBanner: Bool diff --git a/Session/Notifications/SyncPushTokensJob.swift b/Session/Notifications/SyncPushTokensJob.swift index 1ce1a1220..a5234e826 100644 --- a/Session/Notifications/SyncPushTokensJob.swift +++ b/Session/Notifications/SyncPushTokensJob.swift @@ -33,31 +33,8 @@ public enum SyncPushTokensJob: JobExecutor { } return } - let isRegisteredForRemoteNotifications: Bool = UIApplication.shared.isRegisteredForRemoteNotifications - // Swap back to the correct queue before continuing (don't want to inadvertantly do stuff on the main - // thread that could block the user) - queue.async { - SyncPushTokensJob.internalRun( - job, - queue: queue, - isRegisteredForRemoteNotifications: isRegisteredForRemoteNotifications, - success: success, - failure: failure, - deferred: deferred - ) - } - } - - private static func internalRun( - _ job: Job, - queue: DispatchQueue, - isRegisteredForRemoteNotifications: Bool, - success: @escaping (Job, Bool) -> (), - failure: @escaping (Job, Error?, Bool) -> (), - deferred: @escaping (Job) -> () - ) { - guard !isRegisteredForRemoteNotifications else { + guard !UIApplication.shared.isRegisteredForRemoteNotifications else { deferred(job) // Don't need to do anything if push notifications are already registered return } @@ -97,7 +74,7 @@ public enum SyncPushTokensJob: JobExecutor { ) return promise - .done { _ in + .done(on: queue) { _ in Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))") GRDBStorage.shared.write { db in diff --git a/Session/Shared/FullConversationCell.swift b/Session/Shared/FullConversationCell.swift index b131b808a..fa9973805 100644 --- a/Session/Shared/FullConversationCell.swift +++ b/Session/Shared/FullConversationCell.swift @@ -374,7 +374,7 @@ public final class FullConversationCell: UITableViewCell { statusIndicatorView.isHidden = false default: - statusIndicatorView.isHidden = false + statusIndicatorView.isHidden = true } } diff --git a/SessionMessagingKit/Database/Models/Interaction.swift b/SessionMessagingKit/Database/Models/Interaction.swift index 82ea89024..d082e108d 100644 --- a/SessionMessagingKit/Database/Models/Interaction.swift +++ b/SessionMessagingKit/Database/Models/Interaction.swift @@ -356,13 +356,18 @@ public struct Interaction: Codable, Identifiable, Equatable, FetchableRecord, Mu return } - try members.forEach { member in - try RecipientState( - interactionId: interactionId, - recipientId: member.profileId, - state: .sending - ).insert(db) - } + // Exclude the current user when creating recipient states (as they will never + // receive the message resulting in the message getting flagged as failed) + let userPublicKey: String = getUserHexEncodedPublicKey(db) + try members + .filter { member -> Bool in member.profileId != userPublicKey } + .forEach { member in + try RecipientState( + interactionId: interactionId, + recipientId: member.profileId, + state: .sending + ).insert(db) + } case .openGroup: // Since we use the 'RecipientState' type to manage the message state diff --git a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift index a4401df7f..d4f7083d3 100644 --- a/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift +++ b/SessionMessagingKit/Shared Models/SessionThreadViewModel.swift @@ -194,11 +194,18 @@ public struct SessionThreadViewModel: FetchableRecordWithRowId, Decodable, Equat // MARK: - Convenience Initialization public extension SessionThreadViewModel { + static let invalidId: String = "INVALID_THREAD_ID" + // Note: This init method is only used system-created cells or empty states - init(unreadCount: UInt = 0) { + init( + threadId: String? = nil, + threadVariant: SessionThread.Variant? = nil, + currentUserIsClosedGroupMember: Bool? = nil, + unreadCount: UInt = 0 + ) { self.rowId = -1 - self.threadId = "INVALID_THREAD_ID" - self.threadVariant = .contact + self.threadId = (threadId ?? SessionThreadViewModel.invalidId) + self.threadVariant = (threadVariant ?? .contact) self.threadCreationDateTimestamp = 0 self.threadMemberNames = nil @@ -224,7 +231,7 @@ public extension SessionThreadViewModel { self.closedGroupProfileBackFallback = nil self.closedGroupName = nil self.closedGroupUserCount = nil - self.currentUserIsClosedGroupMember = nil + self.currentUserIsClosedGroupMember = currentUserIsClosedGroupMember self.currentUserIsClosedGroupAdmin = nil self.openGroupName = nil self.openGroupServer = nil