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 {
|
public protocol LiveLocationManager {
|
||||||
var summaryManager: LiveLocationSummaryManager { get }
|
var summaryManager: LiveLocationSummaryManager { get }
|
||||||
var isPolling: Signal<Bool, NoError> { get }
|
var isPolling: Signal<Bool, NoError> { get }
|
||||||
|
var hasBackgroundTasks: Signal<Bool, NoError> { get }
|
||||||
|
|
||||||
func cancelLiveLocation(peerId: EnginePeer.Id)
|
func cancelLiveLocation(peerId: EnginePeer.Id)
|
||||||
func pollOnce()
|
func pollOnce()
|
||||||
|
@ -31,16 +31,60 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
var counter: Counter?
|
var counter: Counter?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct AnimationState {
|
||||||
|
var fromCounter: Counter
|
||||||
|
var startTime: Double
|
||||||
|
var duration: Double
|
||||||
|
}
|
||||||
|
|
||||||
private var isExtracted: Bool = false
|
private var isExtracted: Bool = false
|
||||||
private var currentLayout: Layout?
|
private var currentLayout: Layout?
|
||||||
|
|
||||||
|
private var animationState: AnimationState?
|
||||||
|
private var animator: ConstantDisplayLinkAnimator?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(pointerStyle: nil)
|
super.init(pointerStyle: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(layout: Layout) {
|
func update(layout: Layout) {
|
||||||
if self.currentLayout != 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.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)
|
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)
|
var textOrigin: CGFloat = size.width - counter.frame.width - 8.0 + floorToScreenPixels((counter.frame.width - totalComponentWidth) / 2.0)
|
||||||
for component in counter.components {
|
textOrigin = max(textOrigin, layout.baseSize.height / 2.0 + UIScreenPixel)
|
||||||
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))
|
var rightTextOrigin = textOrigin + totalComponentWidth
|
||||||
textOrigin += component.bounds.width
|
|
||||||
|
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 = {
|
private static let maxDigitWidth: CGFloat = {
|
||||||
var maxWidth: CGFloat = 0.0
|
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 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)
|
let boundingRect = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||||
maxWidth = max(maxWidth, boundingRect.width)
|
maxWidth = max(maxWidth, boundingRect.width)
|
||||||
@ -151,13 +237,19 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceNode {
|
|||||||
} else {
|
} else {
|
||||||
var resultSize = CGSize()
|
var resultSize = CGSize()
|
||||||
var resultComponents: [Component] = []
|
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 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)
|
let boundingRect = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
|
||||||
|
|
||||||
resultComponents.append(Component(string: component, bounds: boundingRect))
|
resultComponents.append(Component(string: component, bounds: boundingRect))
|
||||||
|
|
||||||
|
if spec.stringComponents.count <= 2 {
|
||||||
resultSize.width += CounterLayout.maxDigitWidth
|
resultSize.width += CounterLayout.maxDigitWidth
|
||||||
|
} else {
|
||||||
|
resultSize.width += boundingRect.width
|
||||||
|
}
|
||||||
resultSize.height = max(resultSize.height, boundingRect.height)
|
resultSize.height = max(resultSize.height, boundingRect.height)
|
||||||
}
|
}
|
||||||
size = CGSize(width: ceil(resultSize.width), height: ceil(resultSize.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)
|
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 counterLayout: CounterLayout?
|
||||||
var counterFrame: CGRect?
|
var counterFrame: CGRect?
|
||||||
|
|
||||||
|
@ -3,7 +3,8 @@ import CoreLocation
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
public enum DeviceLocationMode: Int32 {
|
public enum DeviceLocationMode: Int32 {
|
||||||
case precise = 0
|
case preciseForeground = 0
|
||||||
|
case preciseAlways = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class DeviceLocationSubscriber {
|
private final class DeviceLocationSubscriber {
|
||||||
@ -51,15 +52,15 @@ public final class DeviceLocationManager: NSObject {
|
|||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
|
||||||
self.manager.allowsBackgroundLocationUpdates = true
|
|
||||||
}
|
|
||||||
self.manager.delegate = self
|
self.manager.delegate = self
|
||||||
self.manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
|
self.manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
|
||||||
self.manager.distanceFilter = 5.0
|
self.manager.distanceFilter = 5.0
|
||||||
self.manager.activityType = .other
|
self.manager.activityType = .other
|
||||||
self.manager.pausesLocationUpdatesAutomatically = false
|
self.manager.pausesLocationUpdatesAutomatically = false
|
||||||
self.manager.headingFilter = 2.0
|
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 {
|
public func push(mode: DeviceLocationMode, updated: @escaping (CLLocation, Double?) -> Void) -> Disposable {
|
||||||
@ -108,6 +109,14 @@ public final class DeviceLocationManager: NSObject {
|
|||||||
self.requestedAuthorization = true
|
self.requestedAuthorization = true
|
||||||
self.manager.requestAlwaysAuthorization()
|
self.manager.requestAlwaysAuthorization()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch topMode {
|
||||||
|
case .preciseForeground:
|
||||||
|
self.manager.allowsBackgroundLocationUpdates = false
|
||||||
|
case .preciseAlways:
|
||||||
|
self.manager.allowsBackgroundLocationUpdates = true
|
||||||
|
}
|
||||||
|
|
||||||
self.manager.startUpdatingLocation()
|
self.manager.startUpdatingLocation()
|
||||||
self.manager.startUpdatingHeading()
|
self.manager.startUpdatingHeading()
|
||||||
}
|
}
|
||||||
@ -164,7 +173,7 @@ extension DeviceLocationManager: CLLocationManagerDelegate {
|
|||||||
public func currentLocationManagerCoordinate(manager: DeviceLocationManager, timeout timeoutValue: Double) -> Signal<CLLocationCoordinate2D?, NoError> {
|
public func currentLocationManagerCoordinate(manager: DeviceLocationManager, timeout timeoutValue: Double) -> Signal<CLLocationCoordinate2D?, NoError> {
|
||||||
return (
|
return (
|
||||||
Signal { subscriber in
|
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.putNext(location.coordinate)
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
})
|
})
|
||||||
|
@ -22,6 +22,11 @@ public final class LiveLocationManagerImpl: LiveLocationManager {
|
|||||||
public var isPolling: Signal<Bool, NoError> {
|
public var isPolling: Signal<Bool, NoError> {
|
||||||
return self.pollingOnce.get()
|
return self.pollingOnce.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var hasBackgroundTasks: Signal<Bool, NoError> {
|
||||||
|
return self.hasActiveMessagesToBroadcast.get()
|
||||||
|
}
|
||||||
|
|
||||||
private let pollingOnce = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let pollingOnce = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
private var pollingOnceValue = false {
|
private var pollingOnceValue = false {
|
||||||
didSet {
|
didSet {
|
||||||
@ -92,6 +97,8 @@ public final class LiveLocationManagerImpl: LiveLocationManager {
|
|||||||
|> map { inForeground, hasActiveMessagesToBroadcast, pollingOnce -> Bool in
|
|> map { inForeground, hasActiveMessagesToBroadcast, pollingOnce -> Bool in
|
||||||
if (inForeground || pollingOnce) && hasActiveMessagesToBroadcast {
|
if (inForeground || pollingOnce) && hasActiveMessagesToBroadcast {
|
||||||
return true
|
return true
|
||||||
|
} else if hasActiveMessagesToBroadcast {
|
||||||
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -100,7 +107,7 @@ public final class LiveLocationManagerImpl: LiveLocationManager {
|
|||||||
|> deliverOn(self.queue)).start(next: { [weak self] value in
|
|> deliverOn(self.queue)).start(next: { [weak self] value in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if value {
|
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)))
|
self?.deviceLocationPromise.set(.single((location, heading)))
|
||||||
}))
|
}))
|
||||||
} else {
|
} else {
|
||||||
@ -249,9 +256,9 @@ public final class LiveLocationManagerImpl: LiveLocationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func pollOnce() {
|
public func pollOnce() {
|
||||||
if !self.broadcastToMessageIds.isEmpty {
|
/*if !self.broadcastToMessageIds.isEmpty {
|
||||||
self.pollingOnceValue = true
|
self.pollingOnceValue = true
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public func internalMessageForPeerId(_ peerId: EnginePeer.Id) -> EngineMessage.Id? {
|
public func internalMessageForPeerId(_ peerId: EnginePeer.Id) -> EngineMessage.Id? {
|
||||||
|
@ -179,6 +179,7 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
self.validSize = size
|
self.validSize = size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.animationNode == nil {
|
||||||
if isPreviewing {
|
if isPreviewing {
|
||||||
if self.stillAnimationNode == nil {
|
if self.stillAnimationNode == nil {
|
||||||
let stillAnimationNode = AnimatedStickerNode()
|
let stillAnimationNode = AnimatedStickerNode()
|
||||||
@ -190,7 +191,7 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
stillAnimationNode.bounds = CGRect(origin: CGPoint(), size: animationFrame.size)
|
||||||
stillAnimationNode.updateLayout(size: animationFrame.size)
|
stillAnimationNode.updateLayout(size: animationFrame.size)
|
||||||
stillAnimationNode.started = { [weak self, weak stillAnimationNode] in
|
stillAnimationNode.started = { [weak self, weak stillAnimationNode] in
|
||||||
guard let strongSelf = self, let stillAnimationNode = stillAnimationNode, strongSelf.stillAnimationNode === stillAnimationNode else {
|
guard let strongSelf = self, let stillAnimationNode = stillAnimationNode, strongSelf.stillAnimationNode === stillAnimationNode, strongSelf.animationNode == nil else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
strongSelf.staticAnimationNode.alpha = 0.0
|
strongSelf.staticAnimationNode.alpha = 0.0
|
||||||
@ -232,6 +233,7 @@ final class ReactionNode: ASDisplayNode {
|
|||||||
self.staticAnimationNode.alpha = 1.0
|
self.staticAnimationNode.alpha = 1.0
|
||||||
self.staticAnimationNode.layer.animateAlpha(from: previousAlpha, to: 1.0, duration: 0.08)
|
self.staticAnimationNode.layer.animateAlpha(from: previousAlpha, to: 1.0, duration: 0.08)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !self.didSetupStillAnimation {
|
if !self.didSetupStillAnimation {
|
||||||
if self.animationNode == nil {
|
if self.animationNode == nil {
|
||||||
|
@ -384,7 +384,7 @@ public func themeAutoNightSettingsController(context: AccountContext) -> ViewCon
|
|||||||
|
|
||||||
let forceUpdateLocation: () -> Void = {
|
let forceUpdateLocation: () -> Void = {
|
||||||
let locationCoordinates = Signal<(Double, Double), NoError> { subscriber in
|
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.putNext((location.coordinate.latitude, location.coordinate.longitude))
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
})
|
})
|
||||||
|
@ -866,7 +866,7 @@ public final class AccountViewTracker {
|
|||||||
added = true
|
added = true
|
||||||
updatedReactions = attribute.withUpdatedResults(reactions)
|
updatedReactions = attribute.withUpdatedResults(reactions)
|
||||||
|
|
||||||
if updatedReactions.reactions == attribute.reactions {
|
if updatedReactions == attribute {
|
||||||
return .skip
|
return .skip
|
||||||
}
|
}
|
||||||
attributes[j] = updatedReactions
|
attributes[j] = updatedReactions
|
||||||
|
@ -827,7 +827,13 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
|> mapToSignal { context -> Signal<AccountRecordId?, NoError> in
|
|> mapToSignal { context -> Signal<AccountRecordId?, NoError> in
|
||||||
if let context = context, let liveLocationManager = context.context.liveLocationManager {
|
if let context = context, let liveLocationManager = context.context.liveLocationManager {
|
||||||
let accountId = context.context.account.id
|
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
|
|> distinctUntilChanged
|
||||||
|> map { value -> AccountRecordId? in
|
|> map { value -> AccountRecordId? in
|
||||||
if value {
|
if value {
|
||||||
|
@ -18,6 +18,7 @@ import AccountContext
|
|||||||
import ChatInterfaceState
|
import ChatInterfaceState
|
||||||
import ChatListUI
|
import ChatListUI
|
||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
|
import ReactionSelectionNode
|
||||||
|
|
||||||
extension ChatReplyThreadMessage {
|
extension ChatReplyThreadMessage {
|
||||||
var effectiveTopId: MessageId {
|
var effectiveTopId: MessageId {
|
||||||
@ -2316,6 +2317,77 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
|
|
||||||
let completion: (Bool, ListViewDisplayedItemRange) -> Void = { [weak self] wasTransformed, visibleRange in
|
let completion: (Bool, ListViewDisplayedItemRange) -> Void = { [weak self] wasTransformed, visibleRange in
|
||||||
if let strongSelf = self {
|
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
|
strongSelf.historyView = transition.historyView
|
||||||
|
|
||||||
let loadState: ChatHistoryNodeLoadState
|
let loadState: ChatHistoryNodeLoadState
|
||||||
@ -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.hasActiveTransition = false
|
||||||
strongSelf.dequeueHistoryViewTransitions()
|
strongSelf.dequeueHistoryViewTransitions()
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user