|
|
|
@ -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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -150,7 +204,7 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
|
|
|
|
|
// If the user types a character into the compose box, and the sendRefresh timer isn’t running:
|
|
|
|
|
|
|
|
|
|
// Send a ACTION=TYPING message.
|
|
|
|
|
sendTypingMessage(forThread: thread, action: .started)
|
|
|
|
|
sendTypingMessageIfNecessary(forThread: thread, action: .started)
|
|
|
|
|
// Start the sendRefresh timer for 10 seconds
|
|
|
|
|
sendRefreshTimer?.invalidate()
|
|
|
|
|
sendRefreshTimer = Timer.weakScheduledTimer(withTimeInterval: 10,
|
|
|
|
@ -179,7 +233,7 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
|
|
|
|
|
AssertIsOnMainThread()
|
|
|
|
|
|
|
|
|
|
// Send ACTION=STOPPED message.
|
|
|
|
|
sendTypingMessage(forThread: thread, action: .stopped)
|
|
|
|
|
sendTypingMessageIfNecessary(forThread: thread, action: .stopped)
|
|
|
|
|
// Cancel the sendRefresh timer
|
|
|
|
|
sendRefreshTimer?.invalidate()
|
|
|
|
|
sendRefreshTimer = nil
|
|
|
|
@ -195,7 +249,7 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
|
|
|
|
|
// If the sendPause timer fires:
|
|
|
|
|
|
|
|
|
|
// Send ACTION=STOPPED message.
|
|
|
|
|
sendTypingMessage(forThread: thread, action: .stopped)
|
|
|
|
|
sendTypingMessageIfNecessary(forThread: thread, action: .stopped)
|
|
|
|
|
// Cancel the sendRefresh timer
|
|
|
|
|
sendRefreshTimer?.invalidate()
|
|
|
|
|
sendRefreshTimer = nil
|
|
|
|
@ -211,7 +265,7 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
|
|
|
|
|
// If the sendRefresh timer fires:
|
|
|
|
|
|
|
|
|
|
// Send ACTION=TYPING message
|
|
|
|
|
sendTypingMessage(forThread: thread, action: .started)
|
|
|
|
|
sendTypingMessageIfNecessary(forThread: thread, action: .started)
|
|
|
|
|
// Cancel the sendRefresh timer
|
|
|
|
|
sendRefreshTimer?.invalidate()
|
|
|
|
|
// Start the sendRefresh timer for 10 seconds again
|
|
|
|
@ -235,9 +289,20 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
|
|
|
|
|
sendPauseTimer = nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func sendTypingMessage(forThread thread: TSThread, action: TypingIndicatorAction) {
|
|
|
|
|
private func sendTypingMessageIfNecessary(forThread thread: TSThread, action: TypingIndicatorAction) {
|
|
|
|
|
Logger.verbose("\(TypingIndicatorMessage.string(forTypingIndicatorAction: action))")
|
|
|
|
|
|
|
|
|
|
guard let delegate = delegate else {
|
|
|
|
|
owsFailDebug("Missing delegate.")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// `areTypingIndicatorsEnabled` reflects the user-facing setting in the app preferences.
|
|
|
|
|
// If it's disabled we don't want to emit "typing indicator" messages
|
|
|
|
|
// or show typing indicators for other users.
|
|
|
|
|
guard delegate.areTypingIndicatorsEnabled() else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let message = TypingIndicatorMessage(thread: thread, action: action)
|
|
|
|
|
messageSender.sendPromise(message: message).retainUntilComplete()
|
|
|
|
|
}
|
|
|
|
@ -257,12 +322,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 +338,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?
|
|
|
|
@ -284,12 +350,13 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
|
|
|
|
|
if didChange {
|
|
|
|
|
Logger.debug("isTyping changed: \(oldValue) -> \(self.isTyping)")
|
|
|
|
|
|
|
|
|
|
notify()
|
|
|
|
|
notifyIfNecessary()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
init(recipientId: String, deviceId: UInt) {
|
|
|
|
|
init(delegate: TypingIndicators, recipientId: String, deviceId: UInt) {
|
|
|
|
|
self.delegate = delegate
|
|
|
|
|
self.recipientId = recipientId
|
|
|
|
|
self.deviceId = deviceId
|
|
|
|
|
}
|
|
|
|
@ -348,8 +415,20 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
|
|
|
|
|
isTyping = false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func notify() {
|
|
|
|
|
private func notifyIfNecessary() {
|
|
|
|
|
Logger.verbose("")
|
|
|
|
|
|
|
|
|
|
guard let delegate = delegate else {
|
|
|
|
|
owsFailDebug("Missing delegate.")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// `areTypingIndicatorsEnabled` reflects the user-facing setting in the app preferences.
|
|
|
|
|
// If it's disabled we don't want to emit "typing indicator" messages
|
|
|
|
|
// or show typing indicators for other users.
|
|
|
|
|
guard delegate.areTypingIndicatorsEnabled() else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NotificationCenter.default.postNotificationNameAsync(TypingIndicatorsImpl.typingIndicatorStateDidChange, object: recipientId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|