Rework typing indicators API.

pull/1/head
Matthew Chen 7 years ago
parent a113271b50
commit b063a49d56

@ -24,9 +24,14 @@ public protocol TypingIndicators: class {
@objc @objc
func didReceiveIncomingMessage(inThread thread: TSThread, recipientId: String, deviceId: UInt) func didReceiveIncomingMessage(inThread thread: TSThread, recipientId: String, deviceId: UInt)
// Returns the recipient id of the user who should currently be shown typing for a given thread.
//
// If no one is typing in that thread, returns nil.
// If multiple users are typing in that thread, returns the user to show.
//
// TODO: Use this method. // TODO: Use this method.
@objc @objc
func areTypingIndicatorsVisible(inThread thread: TSThread, recipientId: String) -> Bool func typingIndicators(forThread thread: TSThread, recipientId: String) -> String?
@objc @objc
func setTypingIndicatorsEnabled(value: Bool) func setTypingIndicatorsEnabled(value: Bool)
@ -145,19 +150,35 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
} }
@objc @objc
public func areTypingIndicatorsVisible(inThread thread: TSThread, recipientId: String) -> Bool { public func typingIndicators(forThread thread: TSThread, recipientId: String) -> String? {
AssertIsOnMainThread() AssertIsOnMainThread()
let key = incomingIndicatorsKey(forThread: thread, recipientId: recipientId) var firstRecipientId: String?
guard let deviceMap = incomingIndicatorsMap[key] else { var firstTimestamp: UInt64?
return false
let threadKey = incomingIndicatorsKey(forThread: thread)
guard let deviceMap = incomingIndicatorsMap[threadKey] else {
// No devices are typing in this thread.
return nil
} }
for incomingIndicators in deviceMap.values { for incomingIndicators in deviceMap.values {
if incomingIndicators.isTyping { guard incomingIndicators.isTyping else {
return true continue
} }
guard let startedTypingTimestamp = incomingIndicators.startedTypingTimestamp else {
owsFailDebug("Typing device is missing start timestamp.")
continue
} }
return false if let firstTimestamp = firstTimestamp,
firstTimestamp < startedTypingTimestamp {
// More than one recipient/device is typing in this conversation;
// prefer the one that started typing first.
continue
}
firstRecipientId = incomingIndicators.recipientId
firstTimestamp = startedTypingTimestamp
}
return firstRecipientId
} }
// MARK: - // MARK: -
@ -316,27 +337,32 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
// MARK: - // MARK: -
// Map of (thread id and recipient id)-to-(device id)-to-IncomingIndicators. // Map of (thread id)-to-(recipient id and device id)-to-IncomingIndicators.
private var incomingIndicatorsMap = [String: [UInt: IncomingIndicators]]() private var incomingIndicatorsMap = [String: [String: IncomingIndicators]]()
private func incomingIndicatorsKey(forThread thread: TSThread) -> String {
return String(describing: thread.uniqueId)
}
private func incomingIndicatorsKey(forThread thread: TSThread, recipientId: String) -> String { private func incomingIndicatorsKey(recipientId: String, deviceId: UInt) -> String {
return "\(String(describing: thread.uniqueId)) \(recipientId)" return "\(recipientId) \(deviceId)"
} }
private func ensureIncomingIndicators(forThread thread: TSThread, recipientId: String, deviceId: UInt) -> IncomingIndicators { private func ensureIncomingIndicators(forThread thread: TSThread, recipientId: String, deviceId: UInt) -> IncomingIndicators {
AssertIsOnMainThread() AssertIsOnMainThread()
let key = incomingIndicatorsKey(forThread: thread, recipientId: recipientId) let threadKey = incomingIndicatorsKey(forThread: thread)
guard let deviceMap = incomingIndicatorsMap[key] else { let deviceKey = incomingIndicatorsKey(recipientId: recipientId, deviceId: deviceId)
let incomingIndicators = IncomingIndicators(delegate: self, recipientId: recipientId, deviceId: deviceId) guard let deviceMap = incomingIndicatorsMap[threadKey] else {
incomingIndicatorsMap[key] = [deviceId: incomingIndicators] let incomingIndicators = IncomingIndicators(delegate: self, thread: thread, recipientId: recipientId, deviceId: deviceId)
incomingIndicatorsMap[threadKey] = [deviceKey: incomingIndicators]
return incomingIndicators return incomingIndicators
} }
guard let incomingIndicators = deviceMap[deviceId] else { guard let incomingIndicators = deviceMap[deviceKey] else {
let incomingIndicators = IncomingIndicators(delegate: self, recipientId: recipientId, deviceId: deviceId) let incomingIndicators = IncomingIndicators(delegate: self, thread: thread, recipientId: recipientId, deviceId: deviceId)
var deviceMapCopy = deviceMap var deviceMapCopy = deviceMap
deviceMapCopy[deviceId] = incomingIndicators deviceMapCopy[deviceKey] = incomingIndicators
incomingIndicatorsMap[key] = deviceMapCopy incomingIndicatorsMap[threadKey] = deviceMapCopy
return incomingIndicators return incomingIndicators
} }
return incomingIndicators return incomingIndicators
@ -345,9 +371,12 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
// The receiver maintains one timer for each (sender, device) in a chat: // The receiver maintains one timer for each (sender, device) in a chat:
private class IncomingIndicators { private class IncomingIndicators {
private weak var delegate: TypingIndicators? private weak var delegate: TypingIndicators?
private let recipientId: String private let thread: TSThread
fileprivate let recipientId: String
private let deviceId: UInt private let deviceId: UInt
private var displayTypingTimer: Timer? private var displayTypingTimer: Timer?
fileprivate var startedTypingTimestamp: UInt64?
var isTyping = false { var isTyping = false {
didSet { didSet {
AssertIsOnMainThread() AssertIsOnMainThread()
@ -361,8 +390,10 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
} }
} }
init(delegate: TypingIndicators, recipientId: String, deviceId: UInt) { init(delegate: TypingIndicators, thread: TSThread,
recipientId: String, deviceId: UInt) {
self.delegate = delegate self.delegate = delegate
self.thread = thread
self.recipientId = recipientId self.recipientId = recipientId
self.deviceId = deviceId self.deviceId = deviceId
} }
@ -381,43 +412,37 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
selector: #selector(IncomingIndicators.displayTypingTimerDidFire), selector: #selector(IncomingIndicators.displayTypingTimerDidFire),
userInfo: nil, userInfo: nil,
repeats: false) repeats: false)
if !isTyping {
startedTypingTimestamp = NSDate.ows_millisecondTimeStamp()
}
isTyping = true isTyping = true
} }
func didReceiveTypingStoppedMessage() { func didReceiveTypingStoppedMessage() {
AssertIsOnMainThread() AssertIsOnMainThread()
// If the client receives a ACTION=STOPPED message: clearTyping()
//
// Cancel the displayTyping timer for that (sender, device)
// Hide the typing indicator for that (sender, device)
displayTypingTimer?.invalidate()
displayTypingTimer = nil
isTyping = false
} }
@objc @objc
func displayTypingTimerDidFire() { func displayTypingTimerDidFire() {
AssertIsOnMainThread() AssertIsOnMainThread()
// If the displayTyping indicator fires: clearTyping()
//
// Cancel the displayTyping timer for that (sender, device)
// Hide the typing indicator for that (sender, device)
displayTypingTimer?.invalidate()
displayTypingTimer = nil
isTyping = false
} }
func didReceiveIncomingMessage() { func didReceiveIncomingMessage() {
AssertIsOnMainThread() AssertIsOnMainThread()
// If the client receives a message: clearTyping()
// }
// Cancel the displayTyping timer for that (sender, device)
// Hide the typing indicator for that (sender, device) private func clearTyping() {
AssertIsOnMainThread()
displayTypingTimer?.invalidate() displayTypingTimer?.invalidate()
displayTypingTimer = nil displayTypingTimer = nil
startedTypingTimestamp = nil
isTyping = false isTyping = false
} }
@ -434,8 +459,11 @@ public class TypingIndicatorsImpl: NSObject, TypingIndicators {
guard delegate.areTypingIndicatorsEnabled() else { guard delegate.areTypingIndicatorsEnabled() else {
return return
} }
guard let threadId = thread.uniqueId else {
NotificationCenter.default.postNotificationNameAsync(TypingIndicatorsImpl.typingIndicatorStateDidChange, object: recipientId) owsFailDebug("Thread is missing id.")
return
}
NotificationCenter.default.postNotificationNameAsync(TypingIndicatorsImpl.typingIndicatorStateDidChange, object: threadId)
} }
} }
} }

Loading…
Cancel
Save