Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/Signal/src/ViewControllers/HomeView/Chat List/ChatListViewController+Notifications.swift
1 views
//
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

public import SignalServiceKit
public import SignalUI

extension ChatListViewController {

    public static let clearSearch = Notification.Name("clearSearch")

    public func observeNotifications() {
        AssertIsOnMainThread()

        NotificationCenter.default.addObserver(
            self,
            selector: #selector(signalAccountsDidChange),
            name: .OWSContactsManagerSignalAccountsDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(applicationWillEnterForeground),
            name: .OWSApplicationWillEnterForeground,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(applicationDidBecomeActive),
            name: .OWSApplicationDidBecomeActive,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(applicationWillResignActive),
            name: .OWSApplicationWillResignActive,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(registrationStateDidChange),
            name: .registrationStateDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(outageStateDidChange),
            name: OutageDetection.outageStateDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(localProfileDidChange),
            name: UserProfileNotifications.localProfileDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(profileWhitelistDidChange),
            name: UserProfileNotifications.profileWhitelistDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(otherProfileDidChange(_:)),
            name: UserProfileNotifications.otherUsersProfileDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(appExpiryDidChange),
            name: AppExpiry.AppExpiryDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(significantTimeChangeNotification),
            name: UIApplication.significantTimeChangeNotification,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(preferContactAvatarsPreferenceDidChange),
            name: SSKPreferences.preferContactAvatarsPreferenceDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(blockListDidChange),
            name: BlockingManager.blockListDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(clearSearch),
            name: ChatListViewController.clearSearch,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(clearSearch),
            name: ReactionManager.localUserReacted,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(localUsernameStateDidChange),
            name: Usernames.localUsernameStateChangedNotification,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(reloadExperienceUpgrades),
            name: .inactivePrimaryDeviceChanged,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(lastBackupDetailsDidChange),
            name: .lastBackupDetailsDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(backupSubscriptionFailedToRedeemDidChange),
            name: .backupSubscriptionAlreadyRedeemedDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(backupIAPNotFoundLocallyDidChange),
            name: .backupIAPNotFoundLocallyDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(hasConsumedMediaTierCapacityStateDidChange),
            name: .hasConsumedMediaTierCapacityStatusDidChange,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(showFYISheetIfNecessary),
            name: DonationReceiptCredentialRedemptionJob.didSucceedNotification,
            object: nil,
        )
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(showFYISheetIfNecessary),
            name: DonationReceiptCredentialRedemptionJob.didFailNotification,
            object: nil,
        )

        SUIEnvironment.shared.contactsViewHelperRef.addObserver(self)

        DependenciesBridge.shared.databaseChangeObserver.appendDatabaseChangeDelegate(self)
    }

    // MARK: -

    @objc
    private func lastBackupDetailsDidChange(_ notification: NSNotification) {
        AssertIsOnMainThread()
        updateBackupFailureAlertsWithSneakyTransaction()
    }

    @objc
    private func backupSubscriptionFailedToRedeemDidChange(_ notification: NSNotification) {
        AssertIsOnMainThread()
        updateBackupSubscriptionFailedToRedeemAlertsWithSneakyTx()
    }

    @objc
    private func backupIAPNotFoundLocallyDidChange(_ notification: NSNotification) {
        AssertIsOnMainThread()
        updateBackupIAPNotFoundLocallyAlertsWithSneakyTx()
    }

    @objc
    private func hasConsumedMediaTierCapacityStateDidChange(_ notification: NSNotification) {
        AssertIsOnMainThread()
        updateHasConsumedMediaTierCapacityWithSneakyTransaction()
    }

    @objc
    private func preferContactAvatarsPreferenceDidChange(_ notification: NSNotification) {
        AssertIsOnMainThread()

        reloadTableDataAndResetCellContentCache()
    }

    @objc
    private func signalAccountsDidChange(_ notification: NSNotification) {
        AssertIsOnMainThread()

        // This is wasteful but this event is very rare.
        reloadTableDataAndResetThreadViewModelCache()
    }

    @objc
    private func registrationStateDidChange(_ notification: NSNotification) {
        AssertIsOnMainThread()

        updateRegistrationReminderView()
        loadCoordinator.loadIfNecessary()
    }

    @objc
    private func outageStateDidChange(_ notification: NSNotification) {
        AssertIsOnMainThread()

        updateOutageDetectionReminderView()
        loadCoordinator.loadIfNecessary()
    }

    @objc
    private func localProfileDidChange(_ notification: NSNotification) {
        AssertIsOnMainThread()

        showFYISheetIfNecessary()
    }

    @objc
    private func appExpiryDidChange(_ notification: NSNotification) {
        AssertIsOnMainThread()

        updateExpirationReminderView()
        loadCoordinator.loadIfNecessary()
    }

    @objc
    private func significantTimeChangeNotification(_ notification: NSNotification) {
        AssertIsOnMainThread()

        updateExpirationReminderView()
        loadCoordinator.loadIfNecessary()
    }

    @objc
    private func applicationWillEnterForeground(_ notification: NSNotification) {
        AssertIsOnMainThread()

        loadCoordinator.applicationWillEnterForeground()
    }

    @objc
    private func applicationDidBecomeActive(_ notification: NSNotification) {
        AssertIsOnMainThread()

        updateShouldBeUpdatingView()

        if !ExperienceUpgradeManager.presentNext(fromViewController: self) {
            presentGetStartedBannerIfNecessary()
        }
    }

    @objc
    private func applicationWillResignActive(_ notification: NSNotification) {
        AssertIsOnMainThread()

        updateShouldBeUpdatingView()
    }

    @objc
    private func profileWhitelistDidChange(_ notification: NSNotification) {
        AssertIsOnMainThread()

        // If profile whitelist just changed, we need to update the associated
        // thread to reflect the latest message request state.
        let address: SignalServiceAddress? = notification.userInfo?[UserProfileNotifications.profileAddressKey] as? SignalServiceAddress
        let groupId: Data? = notification.userInfo?[UserProfileNotifications.profileGroupIdKey] as? Data

        let changedThreadId: String? = SSKEnvironment.shared.databaseStorageRef.read { transaction in
            if
                let address,
                address.isValid
            {
                return TSContactThread.getWithContactAddress(address, transaction: transaction)?.uniqueId
            } else if let groupId {
                return TSGroupThread.threadId(forGroupId: groupId, transaction: transaction)
            } else {
                return nil
            }
        }

        if let threadId = changedThreadId {
            self.loadCoordinator.scheduleLoad(updatedThreadIds: Set([threadId]))
        }
    }

    @objc
    private func otherProfileDidChange(_ notification: NSNotification) {
        AssertIsOnMainThread()

        let address = notification.userInfo?[UserProfileNotifications.profileAddressKey] as? SignalServiceAddress

        let changedThreadId: String? = SSKEnvironment.shared.databaseStorageRef.read { readTx in
            if let address, address.isValid {
                return TSContactThread.getWithContactAddress(address, transaction: readTx)?.uniqueId
            } else {
                return nil
            }
        }

        if let changedThreadId {
            self.loadCoordinator.scheduleLoad(updatedThreadIds: Set([changedThreadId]))
        }
    }

    @objc
    private func blockListDidChange(_ notification: NSNotification) {
        AssertIsOnMainThread()

        // This is wasteful but this event is very rare.
        reloadTableDataAndResetCellContentCache()
    }

    @objc
    private func clearSearch(_ notification: NSNotification) {
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1) { [weak self] in
            if let self {
                self.searchBar.delegate?.searchBarCancelButtonClicked?(self.searchBar)
            }
        }
    }

    @objc
    private func localUsernameStateDidChange() {
        updateUsernameReminderView()
        loadCoordinator.loadIfNecessary()
    }

    @objc
    private func reloadExperienceUpgrades() {
        AssertIsOnMainThread()

        _ = ExperienceUpgradeManager.presentNext(fromViewController: self)
    }
}

// MARK: - Notifications

extension ChatListViewController: DatabaseChangeDelegate {

    public func databaseChangesDidUpdate(databaseChanges: DatabaseChanges) {
        if databaseChanges.didUpdate(tableName: TSPaymentModel.databaseTableName) {
            updateUnreadPaymentNotificationsCountWithSneakyTransaction()
        }

        if !databaseChanges.threadUniqueIdsForChatListUpdate.isEmpty {
            self.loadCoordinator.scheduleLoad(updatedThreadIds: databaseChanges.threadUniqueIdsForChatListUpdate)
        }
    }

    public func databaseChangesDidUpdateExternally() {
        // External database modifications can't be converted into incremental updates,
        // so rebuild everything.  This is expensive and usually isn't necessary, but
        // there's no alternative.
        self.loadCoordinator.scheduleHardReset()
    }

    public func databaseChangesDidReset() {
        // This should only happen if we need to recover from an error in the
        // database change observation pipeline.  This should never occur,
        // but when it does we need to rebuild everything.  This is expensive,
        // but there's no alternative.
        self.loadCoordinator.scheduleHardReset()
    }
}

extension ChatListViewController: ContactsViewHelperObserver {
    public func contactsViewHelperDidUpdateContacts() {
        if !firstConversationCueView.isHidden {
            updateFirstConversationLabel()
        }
    }
}