From e00edd5f566f7d190a808b51fb27488ac7e7565b Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 30 Dec 2021 17:24:59 +0400 Subject: [PATCH] Reaction improvements --- .../Sources/LiveLocationManager.swift | 1 + .../Sources/ReactionButtonListComponent.swift | 175 ++++++++++-------- .../Sources/DeviceLocationManager.swift | 19 +- .../Sources/LiveLocationManager.swift | 13 +- .../Sources/ReactionSelectionNode.swift | 96 +++++----- .../ThemeAutoNightSettingsController.swift | 2 +- .../Sources/State/AccountViewTracker.swift | 2 +- .../TelegramUI/Sources/AppDelegate.swift | 8 +- .../Sources/ChatHistoryListNode.swift | 107 ++++++++++- 9 files changed, 288 insertions(+), 135 deletions(-) diff --git a/submodules/AccountContext/Sources/LiveLocationManager.swift b/submodules/AccountContext/Sources/LiveLocationManager.swift index 6224e1a410..73d73aa356 100644 --- a/submodules/AccountContext/Sources/LiveLocationManager.swift +++ b/submodules/AccountContext/Sources/LiveLocationManager.swift @@ -10,6 +10,7 @@ public protocol LiveLocationSummaryManager { public protocol LiveLocationManager { var summaryManager: LiveLocationSummaryManager { get } var isPolling: Signal { get } + var hasBackgroundTasks: Signal { get } func cancelLiveLocation(peerId: EnginePeer.Id) func pollOnce() diff --git a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift index ae0b468144..250a80194c 100644 --- a/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift +++ b/submodules/Components/ReactionButtonListComponent/Sources/ReactionButtonListComponent.swift @@ -31,16 +31,60 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { var counter: Counter? } + private struct AnimationState { + var fromCounter: Counter + var startTime: Double + var duration: Double + } + private var isExtracted: Bool = false private var currentLayout: Layout? + private var animationState: AnimationState? + private var animator: ConstantDisplayLinkAnimator? + init() { super.init(pointerStyle: nil) } func update(layout: Layout) { if self.currentLayout != layout { + if let currentLayout = self.currentLayout, let counter = currentLayout.counter { + self.animationState = AnimationState(fromCounter: counter, startTime: CACurrentMediaTime(), duration: 0.15 * UIView.animationDurationFactor()) + } + self.currentLayout = layout + + self.updateBackgroundImage(animated: false) + + self.updateAnimation() + } + } + + private func updateAnimation() { + if let animationState = self.animationState { + let timestamp = CACurrentMediaTime() + if timestamp >= animationState.startTime + animationState.duration { + self.animationState = nil + } + } + + if self.animationState != nil { + if self.animator == nil { + let animator = ConstantDisplayLinkAnimator(update: { [weak self] in + guard let strongSelf = self else { + return + } + strongSelf.updateBackgroundImage(animated: false) + strongSelf.updateAnimation() + }) + self.animator = animator + animator.isPaused = false + } + } else if let animator = self.animator { + animator.invalidate() + self.animator = nil + self.updateBackgroundImage(animated: false) } } @@ -87,10 +131,52 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { } var textOrigin: CGFloat = size.width - counter.frame.width - 8.0 + floorToScreenPixels((counter.frame.width - totalComponentWidth) / 2.0) - for component in counter.components { - let string = NSAttributedString(string: component.string, font: Font.medium(11.0), textColor: foregroundColor) - string.draw(at: component.bounds.origin.offsetBy(dx: textOrigin, dy: floorToScreenPixels(size.height - component.bounds.height) / 2.0)) - textOrigin += component.bounds.width + textOrigin = max(textOrigin, layout.baseSize.height / 2.0 + UIScreenPixel) + + var rightTextOrigin = textOrigin + totalComponentWidth + + let animationFraction: CGFloat + if let animationState = self.animationState { + animationFraction = max(0.0, min(1.0, (CACurrentMediaTime() - animationState.startTime) / animationState.duration)) + } else { + animationFraction = 1.0 + } + + for i in (0 ..< counter.components.count).reversed() { + let component = counter.components[i] + var componentAlpha: CGFloat = 1.0 + var componentVerticalOffset: CGFloat = 0.0 + + if let animationState = self.animationState { + let reverseIndex = counter.components.count - 1 - i + if reverseIndex < animationState.fromCounter.components.count { + let previousComponent = animationState.fromCounter.components[animationState.fromCounter.components.count - 1 - reverseIndex] + + if previousComponent != component { + componentAlpha = animationFraction + componentVerticalOffset = (1.0 - animationFraction) * 8.0 + if previousComponent.string < component.string { + componentVerticalOffset = -componentVerticalOffset + } + + let previousComponentAlpha = 1.0 - componentAlpha + var previousComponentVerticalOffset = -animationFraction * 8.0 + if previousComponent.string < component.string { + previousComponentVerticalOffset = -previousComponentVerticalOffset + } + + let componentOrigin = rightTextOrigin - previousComponent.bounds.width + let string = NSAttributedString(string: previousComponent.string, font: Font.medium(11.0), textColor: foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - previousComponentAlpha)) + string.draw(at: previousComponent.bounds.origin.offsetBy(dx: componentOrigin, dy: floorToScreenPixels(size.height - previousComponent.bounds.height) / 2.0 + previousComponentVerticalOffset)) + } + } + } + + let componentOrigin = rightTextOrigin - component.bounds.width + let string = NSAttributedString(string: component.string, font: Font.medium(11.0), textColor: foregroundColor.mixedWith(backgroundColor, alpha: 1.0 - componentAlpha)) + string.draw(at: component.bounds.origin.offsetBy(dx: componentOrigin, dy: floorToScreenPixels(size.height - component.bounds.height) / 2.0 + componentVerticalOffset)) + + rightTextOrigin -= component.bounds.width } } @@ -120,7 +206,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { private static let maxDigitWidth: CGFloat = { var maxWidth: CGFloat = 0.0 - for i in 0 ..< 9 { + for i in 0 ... 9 { let string = NSAttributedString(string: "\(i)", font: Font.medium(11.0), textColor: .black) let boundingRect = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) maxWidth = max(maxWidth, boundingRect.width) @@ -151,13 +237,19 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { } else { var resultSize = CGSize() var resultComponents: [Component] = [] - for component in spec.stringComponents { + for i in 0 ..< spec.stringComponents.count { + let component = spec.stringComponents[i] + let string = NSAttributedString(string: component, font: Font.medium(11.0), textColor: .black) let boundingRect = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) resultComponents.append(Component(string: component, bounds: boundingRect)) - resultSize.width += CounterLayout.maxDigitWidth + if spec.stringComponents.count <= 2 { + resultSize.width += CounterLayout.maxDigitWidth + } else { + resultSize.width += boundingRect.width + } resultSize.height = max(resultSize.height, boundingRect.height) } size = CGSize(width: ceil(resultSize.width), height: ceil(resultSize.height)) @@ -243,75 +335,6 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode { let imageFrame = CGRect(origin: CGPoint(x: sideInsets, y: floorToScreenPixels((height - imageSize.height) / 2.0)), size: imageSize) - /*var previousDisplayCounter: String? - if let currentLayout = currentLayout { - if currentLayout.spec.component.avatarPeers.isEmpty { - previousDisplayCounter = countString(Int64(spec.component.count)) - } - } - var currentDisplayCounter: String? - if spec.component.avatarPeers.isEmpty { - currentDisplayCounter = countString(Int64(spec.component.count)) - }*/ - - /*let backgroundImage: UIImage - let extractedBackgroundImage: UIImage - if let currentLayout = currentLayout, currentLayout.spec.component.isSelected == spec.component.isSelected, currentLayout.spec.component.colors == spec.component.colors, previousDisplayCounter == currentDisplayCounter { - backgroundImage = currentLayout.backgroundImage - extractedBackgroundImage = currentLayout.extractedBackgroundImage - } else { - backgroundImage = generateImage(CGSize(width: height + 18.0, height: height), rotatedContext: { size, context in - UIGraphicsPushContext(context) - - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setBlendMode(.copy) - - context.setFillColor(UIColor(argb: backgroundColor).cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: height, height: height))) - context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - height, y: 0.0), size: CGSize(width: height, height: size.height))) - context.fill(CGRect(origin: CGPoint(x: height / 2.0, y: 0.0), size: CGSize(width: size.width - height, height: size.height))) - - context.setBlendMode(.normal) - - if let currentDisplayCounter = currentDisplayCounter { - let textColor = UIColor(argb: spec.component.isSelected ? spec.component.colors.selectedForeground : spec.component.colors.deselectedForeground) - let string = NSAttributedString(string: currentDisplayCounter, font: Font.medium(11.0), textColor: textColor) - let boundingRect = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) - if textColor.alpha < 1.0 { - context.setBlendMode(.copy) - } - string.draw(at: CGPoint(x: size.width - sideInsets - boundingRect.width, y: (size.height - boundingRect.height) / 2.0)) - } - - UIGraphicsPopContext() - })!.stretchableImage(withLeftCapWidth: Int(height / 2.0), topCapHeight: Int(height / 2.0)) - extractedBackgroundImage = generateImage(CGSize(width: height + 18.0, height: height), rotatedContext: { size, context in - UIGraphicsPushContext(context) - - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setBlendMode(.copy) - - context.setFillColor(UIColor(argb: spec.component.colors.extractedBackground).cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: height, height: height))) - context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - height, y: 0.0), size: CGSize(width: height, height: size.height))) - context.fill(CGRect(origin: CGPoint(x: height / 2.0, y: 0.0), size: CGSize(width: size.width - height, height: size.height))) - - context.setBlendMode(.normal) - - if let currentDisplayCounter = currentDisplayCounter { - let textColor = UIColor(argb: spec.component.colors.extractedForeground) - let string = NSAttributedString(string: currentDisplayCounter, font: Font.medium(11.0), textColor: textColor) - let boundingRect = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) - if textColor.alpha < 1.0 { - context.setBlendMode(.copy) - } - string.draw(at: CGPoint(x: size.width - sideInsets - boundingRect.width, y: (size.height - boundingRect.height) / 2.0)) - } - - UIGraphicsPopContext() - })!.stretchableImage(withLeftCapWidth: Int(height / 2.0), topCapHeight: Int(height / 2.0)) - }*/ - var counterLayout: CounterLayout? var counterFrame: CGRect? diff --git a/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift b/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift index 839f28e184..6336580241 100644 --- a/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift +++ b/submodules/DeviceLocationManager/Sources/DeviceLocationManager.swift @@ -3,7 +3,8 @@ import CoreLocation import SwiftSignalKit public enum DeviceLocationMode: Int32 { - case precise = 0 + case preciseForeground = 0 + case preciseAlways = 1 } private final class DeviceLocationSubscriber { @@ -51,15 +52,15 @@ public final class DeviceLocationManager: NSObject { super.init() - if #available(iOSApplicationExtension 9.0, iOS 9.0, *) { - self.manager.allowsBackgroundLocationUpdates = true - } self.manager.delegate = self self.manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters self.manager.distanceFilter = 5.0 self.manager.activityType = .other self.manager.pausesLocationUpdatesAutomatically = false self.manager.headingFilter = 2.0 + if #available(iOS 11.0, *) { + self.manager.showsBackgroundLocationIndicator = true + } } public func push(mode: DeviceLocationMode, updated: @escaping (CLLocation, Double?) -> Void) -> Disposable { @@ -108,6 +109,14 @@ public final class DeviceLocationManager: NSObject { self.requestedAuthorization = true self.manager.requestAlwaysAuthorization() } + + switch topMode { + case .preciseForeground: + self.manager.allowsBackgroundLocationUpdates = false + case .preciseAlways: + self.manager.allowsBackgroundLocationUpdates = true + } + self.manager.startUpdatingLocation() self.manager.startUpdatingHeading() } @@ -164,7 +173,7 @@ extension DeviceLocationManager: CLLocationManagerDelegate { public func currentLocationManagerCoordinate(manager: DeviceLocationManager, timeout timeoutValue: Double) -> Signal { return ( Signal { subscriber in - let disposable = manager.push(mode: .precise, updated: { location, _ in + let disposable = manager.push(mode: .preciseForeground, updated: { location, _ in subscriber.putNext(location.coordinate) subscriber.putCompletion() }) diff --git a/submodules/LiveLocationManager/Sources/LiveLocationManager.swift b/submodules/LiveLocationManager/Sources/LiveLocationManager.swift index b78f60a1cd..352e0c9815 100644 --- a/submodules/LiveLocationManager/Sources/LiveLocationManager.swift +++ b/submodules/LiveLocationManager/Sources/LiveLocationManager.swift @@ -22,6 +22,11 @@ public final class LiveLocationManagerImpl: LiveLocationManager { public var isPolling: Signal { return self.pollingOnce.get() } + + public var hasBackgroundTasks: Signal { + return self.hasActiveMessagesToBroadcast.get() + } + private let pollingOnce = ValuePromise(false, ignoreRepeated: true) private var pollingOnceValue = false { didSet { @@ -92,6 +97,8 @@ public final class LiveLocationManagerImpl: LiveLocationManager { |> map { inForeground, hasActiveMessagesToBroadcast, pollingOnce -> Bool in if (inForeground || pollingOnce) && hasActiveMessagesToBroadcast { return true + } else if hasActiveMessagesToBroadcast { + return true } else { return false } @@ -100,7 +107,7 @@ public final class LiveLocationManagerImpl: LiveLocationManager { |> deliverOn(self.queue)).start(next: { [weak self] value in if let strongSelf = self { if value { - strongSelf.deviceLocationDisposable.set(strongSelf.locationManager.push(mode: .precise, updated: { [weak self] location, heading in + strongSelf.deviceLocationDisposable.set(strongSelf.locationManager.push(mode: .preciseAlways, updated: { [weak self] location, heading in self?.deviceLocationPromise.set(.single((location, heading))) })) } else { @@ -249,9 +256,9 @@ public final class LiveLocationManagerImpl: LiveLocationManager { } public func pollOnce() { - if !self.broadcastToMessageIds.isEmpty { + /*if !self.broadcastToMessageIds.isEmpty { self.pollingOnceValue = true - } + }*/ } public func internalMessageForPeerId(_ peerId: EnginePeer.Id) -> EngineMessage.Id? { diff --git a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift index b2099cba02..7e79510bb8 100644 --- a/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift +++ b/submodules/ReactionSelectionNode/Sources/ReactionSelectionNode.swift @@ -179,58 +179,60 @@ final class ReactionNode: ASDisplayNode { self.validSize = size } - if isPreviewing { - if self.stillAnimationNode == nil { - let stillAnimationNode = AnimatedStickerNode() - self.stillAnimationNode = stillAnimationNode - self.addSubnode(stillAnimationNode) + if self.animationNode == nil { + if isPreviewing { + if self.stillAnimationNode == nil { + let stillAnimationNode = AnimatedStickerNode() + self.stillAnimationNode = stillAnimationNode + self.addSubnode(stillAnimationNode) + + stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .loop, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id))) + stillAnimationNode.position = animationFrame.center + stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size) + stillAnimationNode.updateLayout(size: animationFrame.size) + stillAnimationNode.started = { [weak self, weak stillAnimationNode] in + guard let strongSelf = self, let stillAnimationNode = stillAnimationNode, strongSelf.stillAnimationNode === stillAnimationNode, strongSelf.animationNode == nil else { + return + } + strongSelf.staticAnimationNode.alpha = 0.0 + + if let animateInAnimationNode = strongSelf.animateInAnimationNode, !animateInAnimationNode.alpha.isZero { + animateInAnimationNode.alpha = 0.0 + animateInAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1) + + strongSelf.staticAnimationNode.isHidden = false + } + } + stillAnimationNode.visibility = true + + transition.animateTransformScale(node: stillAnimationNode, from: self.staticAnimationNode.bounds.width / animationFrame.width) + transition.animatePositionAdditive(node: stillAnimationNode, offset: CGPoint(x: self.staticAnimationNode.frame.midX - animationFrame.midX, y: self.staticAnimationNode.frame.midY - animationFrame.midY)) + } else { + if let stillAnimationNode = self.stillAnimationNode { + transition.updatePosition(node: stillAnimationNode, position: animationFrame.center, beginWithCurrentState: true) + transition.updateTransformScale(node: stillAnimationNode, scale: animationFrame.size.width / stillAnimationNode.bounds.width, beginWithCurrentState: true) + } + } + } else if let stillAnimationNode = self.stillAnimationNode { + self.stillAnimationNode = nil + self.dismissedStillAnimationNodes.append(stillAnimationNode) - stillAnimationNode.setup(source: AnimatedStickerResourceSource(account: self.context.account, resource: self.item.stillAnimation.resource), width: Int(animationDisplaySize.width * 2.0), height: Int(animationDisplaySize.height * 2.0), playbackMode: .loop, mode: .direct(cachePathPrefix: self.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(self.item.stillAnimation.resource.id))) - stillAnimationNode.position = animationFrame.center - stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size) - stillAnimationNode.updateLayout(size: animationFrame.size) - stillAnimationNode.started = { [weak self, weak stillAnimationNode] in - guard let strongSelf = self, let stillAnimationNode = stillAnimationNode, strongSelf.stillAnimationNode === stillAnimationNode else { + transition.updatePosition(node: stillAnimationNode, position: animationFrame.center, beginWithCurrentState: true) + transition.updateTransformScale(node: stillAnimationNode, scale: animationFrame.size.width / stillAnimationNode.bounds.width, beginWithCurrentState: true) + + stillAnimationNode.alpha = 0.0 + stillAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, completion: { [weak self, weak stillAnimationNode] _ in + guard let strongSelf = self, let stillAnimationNode = stillAnimationNode else { return } - strongSelf.staticAnimationNode.alpha = 0.0 - - if let animateInAnimationNode = strongSelf.animateInAnimationNode, !animateInAnimationNode.alpha.isZero { - animateInAnimationNode.alpha = 0.0 - animateInAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1) - - strongSelf.staticAnimationNode.isHidden = false - } - } - stillAnimationNode.visibility = true + stillAnimationNode.removeFromSupernode() + strongSelf.dismissedStillAnimationNodes.removeAll(where: { $0 === stillAnimationNode }) + }) - transition.animateTransformScale(node: stillAnimationNode, from: self.staticAnimationNode.bounds.width / animationFrame.width) - transition.animatePositionAdditive(node: stillAnimationNode, offset: CGPoint(x: self.staticAnimationNode.frame.midX - animationFrame.midX, y: self.staticAnimationNode.frame.midY - animationFrame.midY)) - } else { - if let stillAnimationNode = self.stillAnimationNode { - transition.updatePosition(node: stillAnimationNode, position: animationFrame.center, beginWithCurrentState: true) - transition.updateTransformScale(node: stillAnimationNode, scale: animationFrame.size.width / stillAnimationNode.bounds.width, beginWithCurrentState: true) - } + let previousAlpha = CGFloat(self.staticAnimationNode.layer.presentation()?.opacity ?? self.staticAnimationNode.layer.opacity) + self.staticAnimationNode.alpha = 1.0 + self.staticAnimationNode.layer.animateAlpha(from: previousAlpha, to: 1.0, duration: 0.08) } - } else if let stillAnimationNode = self.stillAnimationNode { - self.stillAnimationNode = nil - self.dismissedStillAnimationNodes.append(stillAnimationNode) - - transition.updatePosition(node: stillAnimationNode, position: animationFrame.center, beginWithCurrentState: true) - transition.updateTransformScale(node: stillAnimationNode, scale: animationFrame.size.width / stillAnimationNode.bounds.width, beginWithCurrentState: true) - - stillAnimationNode.alpha = 0.0 - stillAnimationNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.1, completion: { [weak self, weak stillAnimationNode] _ in - guard let strongSelf = self, let stillAnimationNode = stillAnimationNode else { - return - } - stillAnimationNode.removeFromSupernode() - strongSelf.dismissedStillAnimationNodes.removeAll(where: { $0 === stillAnimationNode }) - }) - - let previousAlpha = CGFloat(self.staticAnimationNode.layer.presentation()?.opacity ?? self.staticAnimationNode.layer.opacity) - self.staticAnimationNode.alpha = 1.0 - self.staticAnimationNode.layer.animateAlpha(from: previousAlpha, to: 1.0, duration: 0.08) } if !self.didSetupStillAnimation { diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift index bfa5f81ea8..e1b9d76ff0 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeAutoNightSettingsController.swift @@ -384,7 +384,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon let forceUpdateLocation: () -> Void = { let locationCoordinates = Signal<(Double, Double), NoError> { subscriber in - return context.sharedContext.locationManager!.push(mode: DeviceLocationMode.precise, updated: { location, _ in + return context.sharedContext.locationManager!.push(mode: DeviceLocationMode.preciseForeground, updated: { location, _ in subscriber.putNext((location.coordinate.latitude, location.coordinate.longitude)) subscriber.putCompletion() }) diff --git a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift index 494caf4978..1c3435e00a 100644 --- a/submodules/TelegramCore/Sources/State/AccountViewTracker.swift +++ b/submodules/TelegramCore/Sources/State/AccountViewTracker.swift @@ -866,7 +866,7 @@ public final class AccountViewTracker { added = true updatedReactions = attribute.withUpdatedResults(reactions) - if updatedReactions.reactions == attribute.reactions { + if updatedReactions == attribute { return .skip } attributes[j] = updatedReactions diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 5ec03202f2..d1d055e314 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -827,7 +827,13 @@ private func extractAccountManagerState(records: AccountRecordsView mapToSignal { context -> Signal in if let context = context, let liveLocationManager = context.context.liveLocationManager { let accountId = context.context.account.id - return liveLocationManager.isPolling + return combineLatest(queue: .mainQueue(), + liveLocationManager.isPolling, + liveLocationManager.hasBackgroundTasks + ) + |> map { isPolling, hasBackgroundTasks -> Bool in + return isPolling || hasBackgroundTasks + } |> distinctUntilChanged |> map { value -> AccountRecordId? in if value { diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index de90518046..ab676eb1bb 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -18,6 +18,7 @@ import AccountContext import ChatInterfaceState import ChatListUI import ComponentFlow +import ReactionSelectionNode extension ChatReplyThreadMessage { var effectiveTopId: MessageId { @@ -2316,6 +2317,77 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { let completion: (Bool, ListViewDisplayedItemRange) -> Void = { [weak self] wasTransformed, visibleRange in if let strongSelf = self { + var newIncomingReactions: [MessageId: String] = [:] + if case let .peer(peerId) = strongSelf.chatLocation, peerId.namespace == Namespaces.Peer.CloudUser, let previousHistoryView = strongSelf.historyView { + var updatedIncomingReactions: [MessageId: String] = [:] + for entry in transition.historyView.filteredEntries { + switch entry { + case let .MessageEntry(message, _, _, _, _, _): + if message.flags.contains(.Incoming) { + continue + } + if let reactions = message.reactionsAttribute { + for reaction in reactions.reactions { + if !reaction.isSelected { + updatedIncomingReactions[message.id] = reaction.value + } + } + } + case let .MessageGroupEntry(_, messages, _): + for message in messages { + if message.0.flags.contains(.Incoming) { + continue + } + if let reactions = message.0.reactionsAttribute { + for reaction in reactions.reactions { + if !reaction.isSelected { + updatedIncomingReactions[message.0.id] = reaction.value + } + } + } + } + default: + break + } + } + for entry in previousHistoryView.filteredEntries { + switch entry { + case let .MessageEntry(message, _, _, _, _, _): + if let updatedReaction = updatedIncomingReactions[message.id] { + var previousReaction: String? + if let reactions = message.reactionsAttribute { + for reaction in reactions.reactions { + if !reaction.isSelected { + previousReaction = reaction.value + } + } + } + if previousReaction != updatedReaction { + newIncomingReactions[message.id] = updatedReaction + } + } + case let .MessageGroupEntry(_, messages, _): + for message in messages { + if let updatedReaction = updatedIncomingReactions[message.0.id] { + var previousReaction: String? + if let reactions = message.0.reactionsAttribute { + for reaction in reactions.reactions { + if !reaction.isSelected { + previousReaction = reaction.value + } + } + } + if previousReaction != updatedReaction { + newIncomingReactions[message.0.id] = updatedReaction + } + } + } + default: + break + } + } + } + strongSelf.historyView = transition.historyView let loadState: ChatHistoryNodeLoadState @@ -2360,7 +2432,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { if let visible = visibleRange.visibleRange { let visibleFirstIndex = visible.firstIndex if visibleFirstIndex <= visible.lastIndex { - let (incomingIndex, overallIndex) = maxMessageIndexForEntries(transition.historyView, indexRange: (transition.historyView.filteredEntries.count - 1 - visible.lastIndex, transition.historyView.filteredEntries.count - 1 - visibleFirstIndex)) + let (incomingIndex, overallIndex) = maxMessageIndexForEntries(transition.historyView, indexRange: (transition.historyView.filteredEntries.count - 1 - visible.lastIndex, transition.historyView.filteredEntries.count - 1 - visibleFirstIndex)) let messageIndex: MessageIndex? switch strongSelf.chatLocation { @@ -2463,6 +2535,39 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } + if !newIncomingReactions.isEmpty, let chatDisplayNode = strongSelf.controllerInteraction.chatControllerNode() as? ChatControllerNode { + strongSelf.forEachVisibleItemNode { itemNode in + if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item, let updatedReaction = newIncomingReactions[item.content.firstMessage.id], let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: updatedReaction) { + for reaction in availableReactions.reactions { + if reaction.value == updatedReaction { + let standaloneReactionAnimation = StandaloneReactionAnimation() + + chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation) + + chatDisplayNode.addSubnode(standaloneReactionAnimation) + standaloneReactionAnimation.frame = chatDisplayNode.bounds + standaloneReactionAnimation.animateReactionSelection( + context: strongSelf.context, + theme: item.presentationData.theme.theme, + reaction: ReactionContextItem( + reaction: ReactionContextItem.Reaction(rawValue: reaction.value), + appearAnimation: reaction.appearAnimation, + stillAnimation: reaction.selectAnimation, + listAnimation: reaction.activateAnimation, + applicationAnimation: reaction.effectAnimation + ), + targetView: targetView, + hideNode: true, + completion: { [weak standaloneReactionAnimation] in + standaloneReactionAnimation?.removeFromSupernode() + } + ) + } + } + } + } + } + strongSelf.hasActiveTransition = false strongSelf.dequeueHistoryViewTransitions() }