diff --git a/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m b/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m index 1145d471e..ab31393cd 100644 --- a/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m +++ b/Signal/src/ViewControllers/AppSettings/PrivacySettingsTableViewController.m @@ -64,6 +64,16 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s return Environment.shared.preferences; } +- (OWSReadReceiptManager *)readReceiptManager +{ + return OWSReadReceiptManager.sharedManager; +} + +- (id)typingIndicators +{ + return SSKEnvironment.shared.typingIndicators; +} + #pragma mark - Table Contents - (void)updateTableContents @@ -88,14 +98,24 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s = NSLocalizedString(@"SETTINGS_READ_RECEIPT", @"Label for the 'read receipts' setting."); readReceiptsSection.footerTitle = NSLocalizedString( @"SETTINGS_READ_RECEIPTS_SECTION_FOOTER", @"An explanation of the 'read receipts' setting."); - [readReceiptsSection - addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_READ_RECEIPT", - @"Label for the 'read receipts' setting.") - isOn:[OWSReadReceiptManager.sharedManager areReadReceiptsEnabled] - target:weakSelf - selector:@selector(didToggleReadReceiptsSwitch:)]]; + [readReceiptsSection addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_READ_RECEIPT", + @"Label for the 'read receipts' setting.") + isOn:[self.readReceiptManager areReadReceiptsEnabled] + target:weakSelf + selector:@selector(didToggleReadReceiptsSwitch:)]]; [contents addSection:readReceiptsSection]; + OWSTableSection *typingIndicatorsSection = [OWSTableSection new]; + typingIndicatorsSection.headerTitle + = NSLocalizedString(@"SETTINGS_TYPING_INDICATORS", @"Label for the 'typing indicators' setting."); + // TODO: Should we have a footer? + [typingIndicatorsSection addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"SETTINGS_TYPING_INDICATORS", + @"Label for the 'typing indicators' setting.") + isOn:[self.typingIndicators areTypingIndicatorsEnabled] + target:weakSelf + selector:@selector(didToggleTypingIndicatorsSwitch:)]]; + [contents addSection:typingIndicatorsSection]; + OWSTableSection *screenLockSection = [OWSTableSection new]; screenLockSection.headerTitle = NSLocalizedString( @"SETTINGS_SCREEN_LOCK_SECTION_TITLE", @"Title for the 'screen lock' section of the privacy settings."); @@ -337,7 +357,14 @@ static NSString *const kSealedSenderInfoURL = @"https://signal.org/blog/sealed-s { BOOL enabled = sender.isOn; OWSLogInfo(@"toggled areReadReceiptsEnabled: %@", enabled ? @"ON" : @"OFF"); - [OWSReadReceiptManager.sharedManager setAreReadReceiptsEnabled:enabled]; + [self.readReceiptManager setAreReadReceiptsEnabled:enabled]; +} + +- (void)didToggleTypingIndicatorsSwitch:(UISwitch *)sender +{ + BOOL enabled = sender.isOn; + OWSLogInfo(@"toggled areTypingIndicatorsEnabled: %@", enabled ? @"ON" : @"OFF"); + [self.typingIndicators setTypingIndicatorsEnabledWithValue:enabled]; } - (void)didToggleCallsHideIPAddressSwitch:(UISwitch *)sender diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index c54673b3a..e83999b99 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2090,6 +2090,9 @@ /* Title for the 'two factor auth' section of the privacy settings. */ "SETTINGS_TWO_FACTOR_AUTH_TITLE" = "Registration Lock"; +/* Label for the 'typing indicators' setting. */ +"SETTINGS_TYPING_INDICATORS" = "Typing Indicators"; + /* Label for a link to more info about unidentified delivery. */ "SETTINGS_UNIDENTIFIED_DELIVERY_LEARN_MORE" = "Learn More"; diff --git a/SignalServiceKit/src/Util/TypingIndicators.swift b/SignalServiceKit/src/Util/TypingIndicators.swift index 6a077adc9..0a6b1c483 100644 --- a/SignalServiceKit/src/Util/TypingIndicators.swift +++ b/SignalServiceKit/src/Util/TypingIndicators.swift @@ -27,14 +27,66 @@ public protocol TypingIndicators: class { // TODO: Use this method. @objc func areTypingIndicatorsVisible(inThread thread: TSThread, recipientId: String) -> Bool + + @objc + func setTypingIndicatorsEnabled(value: Bool) + + @objc + func areTypingIndicatorsEnabled() -> Bool } // MARK: - @objc(OWSTypingIndicatorsImpl) public class TypingIndicatorsImpl: NSObject, TypingIndicators { + @objc public static let typingIndicatorStateDidChange = Notification.Name("typingIndicatorStateDidChange") + private let kDatabaseCollection = "TypingIndicators" + private let kDatabaseKey_TypingIndicatorsEnabled = "kDatabaseKey_TypingIndicatorsEnabled" + + private var _areTypingIndicatorsEnabled = false + + public override init() { + super.init() + + AppReadiness.runNowOrWhenAppIsReady { + self.setup() + } + } + + private func setup() { + AssertIsOnMainThread() + + _areTypingIndicatorsEnabled = primaryStorage.dbReadConnection.bool(forKey: kDatabaseKey_TypingIndicatorsEnabled, inCollection: kDatabaseCollection, defaultValue: true) + } + + // MARK: - Dependencies + + private var primaryStorage: OWSPrimaryStorage { + return SSKEnvironment.shared.primaryStorage + } + + // MARK: - + + @objc + public func setTypingIndicatorsEnabled(value: Bool) { + AssertIsOnMainThread() + + _areTypingIndicatorsEnabled = value + + primaryStorage.dbReadWriteConnection.setBool(value, forKey: kDatabaseKey_TypingIndicatorsEnabled, inCollection: kDatabaseCollection) + } + + @objc + public func areTypingIndicatorsEnabled() -> Bool { + AssertIsOnMainThread() + + return _areTypingIndicatorsEnabled + } + + // MARK: - + @objc public func didStartTypingOutgoingInput(inThread thread: TSThread) { AssertIsOnMainThread() @@ -117,7 +169,7 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators { if let outgoingIndicators = outgoingIndicatorsMap[threadId] { return outgoingIndicators } - let outgoingIndicators = OutgoingIndicators(thread: thread) + let outgoingIndicators = OutgoingIndicators(delegate: self, thread: thread) outgoingIndicatorsMap[threadId] = outgoingIndicators return outgoingIndicators } @@ -127,11 +179,13 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators { // A sendPause timer // A sendRefresh timer private class OutgoingIndicators { + private weak var delegate: TypingIndicators? private let thread: TSThread private var sendPauseTimer: Timer? private var sendRefreshTimer: Timer? - init(thread: TSThread) { + init(delegate: TypingIndicators, thread: TSThread) { + self.delegate = delegate self.thread = thread } @@ -238,6 +292,14 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators { private func sendTypingMessage(forThread thread: TSThread, action: TypingIndicatorAction) { Logger.verbose("\(TypingIndicatorMessage.string(forTypingIndicatorAction: action))") + guard let delegate = delegate else { + owsFailDebug("Missing delegate.") + return + } + guard delegate.areTypingIndicatorsEnabled() else { + return + } + let message = TypingIndicatorMessage(thread: thread, action: action) messageSender.sendPromise(message: message).retainUntilComplete() } @@ -257,12 +319,12 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators { let key = incomingIndicatorsKey(forThread: thread, recipientId: recipientId) guard let deviceMap = incomingIndicatorsMap[key] else { - let incomingIndicators = IncomingIndicators(recipientId: recipientId, deviceId: deviceId) + let incomingIndicators = IncomingIndicators(delegate: self, recipientId: recipientId, deviceId: deviceId) incomingIndicatorsMap[key] = [deviceId: incomingIndicators] return incomingIndicators } guard let incomingIndicators = deviceMap[deviceId] else { - let incomingIndicators = IncomingIndicators(recipientId: recipientId, deviceId: deviceId) + let incomingIndicators = IncomingIndicators(delegate: self, recipientId: recipientId, deviceId: deviceId) var deviceMapCopy = deviceMap deviceMapCopy[deviceId] = incomingIndicators incomingIndicatorsMap[key] = deviceMapCopy @@ -273,6 +335,7 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators { // The receiver maintains one timer for each (sender, device) in a chat: private class IncomingIndicators { + private weak var delegate: TypingIndicators? private let recipientId: String private let deviceId: UInt private var displayTypingTimer: Timer? @@ -289,7 +352,8 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators { } } - init(recipientId: String, deviceId: UInt) { + init(delegate: TypingIndicators, recipientId: String, deviceId: UInt) { + self.delegate = delegate self.recipientId = recipientId self.deviceId = deviceId } @@ -350,6 +414,15 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators { private func notify() { Logger.verbose("") + + guard let delegate = delegate else { + owsFailDebug("Missing delegate.") + return + } + guard delegate.areTypingIndicatorsEnabled() else { + return + } + NotificationCenter.default.postNotificationNameAsync(TypingIndicatorsImpl.typingIndicatorStateDidChange, object: recipientId) } }