mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Reaction improvements
This commit is contained in:
parent
dc0e3435fd
commit
e00edd5f56
@ -10,6 +10,7 @@ public protocol LiveLocationSummaryManager {
|
||||
public protocol LiveLocationManager {
|
||||
var summaryManager: LiveLocationSummaryManager { get }
|
||||
var isPolling: Signal<Bool, NoError> { get }
|
||||
var hasBackgroundTasks: Signal<Bool, NoError> { get }
|
||||
|
||||
func cancelLiveLocation(peerId: EnginePeer.Id)
|
||||
func pollOnce()
|
||||
|
@ -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?
|
||||
|
||||
|
@ -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<CLLocationCoordinate2D?, NoError> {
|
||||
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()
|
||||
})
|
||||
|
@ -22,6 +22,11 @@ public final class LiveLocationManagerImpl: LiveLocationManager {
|
||||
public var isPolling: Signal<Bool, NoError> {
|
||||
return self.pollingOnce.get()
|
||||
}
|
||||
|
||||
public var hasBackgroundTasks: Signal<Bool, NoError> {
|
||||
return self.hasActiveMessagesToBroadcast.get()
|
||||
}
|
||||
|
||||
private let pollingOnce = ValuePromise<Bool>(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? {
|
||||
|
@ -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 else {
|
||||
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)
|
||||
|
||||
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
|
||||
stillAnimationNode.removeFromSupernode()
|
||||
strongSelf.dismissedStillAnimationNodes.removeAll(where: { $0 === stillAnimationNode })
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
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 {
|
||||
|
@ -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()
|
||||
})
|
||||
|
@ -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
|
||||
|
@ -827,7 +827,13 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
|> mapToSignal { context -> Signal<AccountRecordId?, NoError> 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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user