Various fixes

This commit is contained in:
Ilya Laktyushin 2020-10-19 07:55:13 +04:00
parent 43517068e8
commit 05f3e47623
15 changed files with 315 additions and 109 deletions

View File

@ -10,6 +10,7 @@ import AvatarNode
import LocationResources import LocationResources
import AppBundle import AppBundle
import AccountContext import AccountContext
import CoreLocation
private let avatarFont = avatarPlaceholderFont(size: 24.0) private let avatarFont = avatarPlaceholderFont(size: 24.0)
private let avatarBackgroundImage = UIImage(bundleImageName: "Chat/Message/LocationPin")?.precomposed() private let avatarBackgroundImage = UIImage(bundleImageName: "Chat/Message/LocationPin")?.precomposed()
@ -50,9 +51,34 @@ private func chatBubbleMapPinImage(_ theme: PresentationTheme, color: UIColor) -
}) })
} }
private let arrowImageSize = CGSize(width: 70.0, height: 70.0)
private func generateHeadingArrowImage() -> UIImage? {
return generateImage(arrowImageSize, contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.saveGState()
let center = CGPoint(x: arrowImageSize.width / 2.0, y: arrowImageSize.height / 2.0)
context.move(to: center)
context.addArc(center: center, radius: arrowImageSize.width / 2.0, startAngle: CGFloat.pi / 2.0 + CGFloat.pi / 8.0, endAngle: CGFloat.pi / 2.0 - CGFloat.pi / 8.0, clockwise: true)
context.clip()
var locations: [CGFloat] = [0.0, 0.4, 1.0]
let colors: [CGColor] = [UIColor(rgb: 0x007aff, alpha: 0.5).cgColor, UIColor(rgb: 0x007aff, alpha: 0.3).cgColor, UIColor(rgb: 0x007aff, alpha: 0.0).cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawRadialGradient(gradient, startCenter: center, startRadius: 5.0, endCenter: center, endRadius: arrowImageSize.width / 2.0, options: .drawsAfterEndLocation)
context.restoreGState()
context.setBlendMode(.clear)
context.fillEllipse(in: CGRect(x: (arrowImageSize.width - 10.0) / 2.0, y: (arrowImageSize.height - 10.0) / 2.0, width: 10.0, height: 10.0))
})
}
public final class ChatMessageLiveLocationPositionNode: ASDisplayNode { public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
public enum Mode { public enum Mode {
case liveLocation(Peer, Bool) case liveLocation(peer: Peer, active: Bool, latitude: Double, longitude: Double, heading: Double?)
case location(TelegramMediaMap?) case location(TelegramMediaMap?)
} }
@ -60,9 +86,12 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
private let iconNode: TransformImageNode private let iconNode: TransformImageNode
private let avatarNode: AvatarNode private let avatarNode: AvatarNode
private let pulseNode: ASImageNode private let pulseNode: ASImageNode
private let arrowNode: ASImageNode
private var pulseImage: UIImage? private var pulseImage: UIImage?
private var arrowImage: UIImage?
private var venueType: String? private var venueType: String?
private var coordinate: (Double, Double)?
override public init() { override public init() {
let isLayerBacked = !smartInvertColorsEnabled() let isLayerBacked = !smartInvertColorsEnabled()
@ -84,11 +113,19 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
self.pulseNode.displayWithoutProcessing = true self.pulseNode.displayWithoutProcessing = true
self.pulseNode.isHidden = true self.pulseNode.isHidden = true
self.arrowNode = ASImageNode()
self.arrowNode.frame = CGRect(origin: CGPoint(), size: arrowImageSize)
self.arrowNode.isLayerBacked = true
self.arrowNode.displaysAsynchronously = false
self.arrowNode.displayWithoutProcessing = true
self.arrowNode.isHidden = true
super.init() super.init()
self.isLayerBacked = isLayerBacked self.isLayerBacked = isLayerBacked
self.addSubnode(self.pulseNode) self.addSubnode(self.pulseNode)
self.addSubnode(self.arrowNode)
self.addSubnode(self.backgroundNode) self.addSubnode(self.backgroundNode)
self.addSubnode(self.iconNode) self.addSubnode(self.iconNode)
self.addSubnode(self.avatarNode) self.addSubnode(self.avatarNode)
@ -98,17 +135,24 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
let iconLayout = self.iconNode.asyncLayout() let iconLayout = self.iconNode.asyncLayout()
let currentPulseImage = self.pulseImage let currentPulseImage = self.pulseImage
let currentArrowImage = self.arrowImage
let currentVenueType = self.venueType let currentVenueType = self.venueType
let currentCoordinate = self.coordinate
return { [weak self] context, theme, mode in return { [weak self] context, theme, mode in
var updatedVenueType: String? var updatedVenueType: String?
let backgroundImage: UIImage? let backgroundImage: UIImage?
var hasPulse = false var hasPulse = false
var heading: Double?
var coordinate: (Double, Double)?
switch mode { switch mode {
case let .liveLocation(_, active): case let .liveLocation(_, active, latitude, longitude, headingValue):
backgroundImage = avatarBackgroundImage backgroundImage = avatarBackgroundImage
hasPulse = active hasPulse = active
coordinate = (latitude, longitude)
heading = headingValue
case let .location(location): case let .location(location):
let venueType = location?.venue?.type ?? "" let venueType = location?.venue?.type ?? ""
let color = venueType.isEmpty ? theme.list.itemAccentColor : venueIconColor(type: venueType) let color = venueType.isEmpty ? theme.list.itemAccentColor : venueIconColor(type: venueType)
@ -118,27 +162,57 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
} }
} }
func degToRad(_ degrees: Double) -> Double {
return degrees * Double.pi / 180.0
}
if heading == nil, let currentCoordinate = currentCoordinate, let coordinate = coordinate {
let lat1 = degToRad(currentCoordinate.0)
let lon1 = degToRad(currentCoordinate.1)
let lat2 = degToRad(coordinate.0)
let lon2 = degToRad(coordinate.1)
let dLat = lat2 - lat1
let dLon = lon2 - lon1
if dLat != 0 && dLon != 0 {
let y = sin(dLon) * cos(lat2)
let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon)
heading = atan2(y, x)
}
}
let pulseImage: UIImage? let pulseImage: UIImage?
let arrowImage: UIImage?
if hasPulse { if hasPulse {
pulseImage = currentPulseImage ?? generateFilledCircleImage(diameter: 120.0, color: UIColor(rgb: 0x007aff, alpha: 0.27)) pulseImage = currentPulseImage ?? generateFilledCircleImage(diameter: 120.0, color: UIColor(rgb: 0x007aff, alpha: 0.27))
} else { } else {
pulseImage = nil pulseImage = nil
} }
if let _ = heading {
arrowImage = currentArrowImage ?? generateHeadingArrowImage()
} else {
arrowImage = nil
}
return (CGSize(width: 62.0, height: 74.0), { return (CGSize(width: 62.0, height: 74.0), {
if let strongSelf = self { if let strongSelf = self {
strongSelf.coordinate = coordinate
if strongSelf.backgroundNode.image !== backgroundImage { if strongSelf.backgroundNode.image !== backgroundImage {
strongSelf.backgroundNode.image = backgroundImage strongSelf.backgroundNode.image = backgroundImage
} }
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 74.0)) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 74.0))
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 9.0), size: CGSize(width: 42.0, height: 42.0)) strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 9.0), size: CGSize(width: 42.0, height: 42.0))
switch mode { switch mode {
case let .liveLocation(peer, active): case let .liveLocation(peer, active, _, _, _):
strongSelf.avatarNode.setPeer(context: context, theme: theme, peer: peer) strongSelf.avatarNode.setPeer(context: context, theme: theme, peer: peer)
strongSelf.avatarNode.isHidden = false strongSelf.avatarNode.isHidden = false
strongSelf.iconNode.isHidden = true strongSelf.iconNode.isHidden = true
strongSelf.avatarNode.alpha = active ? 1.0 : 0.6 strongSelf.avatarNode.alpha = active ? 1.0 : 0.6
case let .location(location): case .location:
strongSelf.iconNode.isHidden = false strongSelf.iconNode.isHidden = false
strongSelf.avatarNode.isHidden = true strongSelf.avatarNode.isHidden = true
} }
@ -147,7 +221,6 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
strongSelf.venueType = updatedVenueType strongSelf.venueType = updatedVenueType
strongSelf.iconNode.setSignal(venueIcon(postbox: context.account.postbox, type: updatedVenueType, background: false)) strongSelf.iconNode.setSignal(venueIcon(postbox: context.account.postbox, type: updatedVenueType, background: false))
} }
let arguments = VenueIconArguments(defaultForegroundColor: theme.chat.inputPanel.actionControlForegroundColor) let arguments = VenueIconArguments(defaultForegroundColor: theme.chat.inputPanel.actionControlForegroundColor)
let iconSize = CGSize(width: 44.0, height: 44.0) let iconSize = CGSize(width: 44.0, height: 44.0)
@ -170,6 +243,13 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
strongSelf.pulseNode.isHidden = true strongSelf.pulseNode.isHidden = true
removePulseAnimations(layer: strongSelf.pulseNode.layer) removePulseAnimations(layer: strongSelf.pulseNode.layer)
} }
strongSelf.arrowImage = arrowImage
strongSelf.arrowNode.image = arrowImage
strongSelf.arrowNode.isHidden = heading == nil || !hasPulse
strongSelf.arrowNode.position = CGPoint(x: 31.0, y: 64.0)
strongSelf.arrowNode.transform = CATransform3DMakeRotation(CGFloat(heading ?? 0.0 / 180.0 * Double.pi), 0.0, 0.0, 1.0)
} }
}) })
} }

View File

@ -3,9 +3,7 @@ import UIKit
import Display import Display
import AsyncDisplayKit import AsyncDisplayKit
import LegacyComponents private final class LiveLocationWavesNodeParams: NSObject {
private final class LocationBroadcastPanelWavesNodeParams: NSObject {
let color: UIColor let color: UIColor
let progress: CGFloat let progress: CGFloat
@ -21,8 +19,8 @@ private func degToRad(_ degrees: CGFloat) -> CGFloat {
return degrees * CGFloat.pi / 180.0 return degrees * CGFloat.pi / 180.0
} }
final class LocationBroadcastPanelWavesNode: ASDisplayNode { public final class LiveLocationWavesNode: ASDisplayNode {
var color: UIColor { public var color: UIColor {
didSet { didSet {
self.setNeedsDisplay() self.setNeedsDisplay()
} }
@ -34,7 +32,9 @@ final class LocationBroadcastPanelWavesNode: ASDisplayNode {
} }
} }
init(color: UIColor) { var animator: ConstantDisplayLinkAnimator?
public init(color: UIColor) {
self.color = color self.color = color
super.init() super.init()
@ -43,42 +43,72 @@ final class LocationBroadcastPanelWavesNode: ASDisplayNode {
self.isOpaque = false self.isOpaque = false
} }
override func willEnterHierarchy() { deinit {
self.animator?.invalidate()
}
private var previousAnimationStart: Double?
private func updateAnimations(inHierarchy: Bool) {
let timestamp = CACurrentMediaTime()
let animating: Bool
if inHierarchy {
animating = true
let animator: ConstantDisplayLinkAnimator
if let current = self.animator {
animator = current
} else {
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
self?.updateAnimations(inHierarchy: true)
})
self.animator = animator
}
animator.isPaused = false
} else {
animating = false
self.animator?.isPaused = true
self.previousAnimationStart = nil
}
if animating {
let animationDuration: Double = 2.5
if var startTimestamp = self.previousAnimationStart {
if timestamp > startTimestamp + animationDuration {
while timestamp > startTimestamp + animationDuration {
startTimestamp += animationDuration
}
startTimestamp -= animationDuration
}
let t = min(1.0, max(0.0, (timestamp - startTimestamp) / animationDuration))
self.effectiveProgress = CGFloat(t)
} else {
self.previousAnimationStart = timestamp
}
}
}
public override func willEnterHierarchy() {
super.willEnterHierarchy() super.willEnterHierarchy()
self.pop_removeAnimation(forKey: "indefiniteProgress") self.updateAnimations(inHierarchy: true)
let animation = POPBasicAnimation()
animation.property = (POPAnimatableProperty.property(withName: "progress", initializer: { property in
property?.readBlock = { node, values in
values?.pointee = (node as! LocationBroadcastPanelWavesNode).effectiveProgress
}
property?.writeBlock = { node, values in
(node as! LocationBroadcastPanelWavesNode).effectiveProgress = values!.pointee
}
property?.threshold = 0.01
}) as! POPAnimatableProperty)
animation.fromValue = CGFloat(0.0) as NSNumber
animation.toValue = CGFloat(1.0) as NSNumber
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
animation.duration = 2.5
animation.repeatForever = true
self.pop_add(animation, forKey: "indefiniteProgress")
} }
override func didExitHierarchy() { public override func didExitHierarchy() {
super.didExitHierarchy() super.didExitHierarchy()
self.pop_removeAnimation(forKey: "indefiniteProgress") self.updateAnimations(inHierarchy: false)
} }
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { public override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
let t = CACurrentMediaTime() let t = CACurrentMediaTime()
let value: CGFloat = CGFloat(t.truncatingRemainder(dividingBy: 2.0)) / 2.0 let value: CGFloat = CGFloat(t.truncatingRemainder(dividingBy: 2.0)) / 2.0
return LocationBroadcastPanelWavesNodeParams(color: self.color, progress: value) return LiveLocationWavesNodeParams(color: self.color, progress: value)
} }
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { @objc public override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
let context = UIGraphicsGetCurrentContext()! let context = UIGraphicsGetCurrentContext()!
if !isRasterizing { if !isRasterizing {
@ -87,7 +117,7 @@ final class LocationBroadcastPanelWavesNode: ASDisplayNode {
context.fill(bounds) context.fill(bounds)
} }
if let parameters = parameters as? LocationBroadcastPanelWavesNodeParams { if let parameters = parameters as? LiveLocationWavesNodeParams {
let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0) let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
let length: CGFloat = 9.0 let length: CGFloat = 9.0

View File

@ -74,7 +74,7 @@ private func generateLiveLocationIcon(theme: PresentationTheme, stop: Bool) -> U
context.scaleBy(x: 1.0, y: -1.0) context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
if let image = generateTintedImage(image: UIImage(bundleImageName: "Location/SendLocationIcon"), color: .white) { if let image = generateTintedImage(image: UIImage(bundleImageName: "Location/SendLocationIcon"), color: theme.chat.inputPanel.actionControlForegroundColor) {
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)) context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size))
} }
})! })!
@ -148,6 +148,7 @@ final class LocationActionListItemNode: ListViewItemNode {
private let iconNode: ASImageNode private let iconNode: ASImageNode
private let venueIconNode: TransformImageNode private let venueIconNode: TransformImageNode
private var timerNode: ChatMessageLiveLocationTimerNode? private var timerNode: ChatMessageLiveLocationTimerNode?
private var wavesNode: LiveLocationWavesNode?
private var item: LocationActionListItem? private var item: LocationActionListItem?
private var layoutParams: ListViewItemLayoutParams? private var layoutParams: ListViewItemLayoutParams?
@ -278,6 +279,16 @@ final class LocationActionListItemNode: ListViewItemNode {
strongSelf.venueIconNode.isHidden = false strongSelf.venueIconNode.isHidden = false
strongSelf.venueIconNode.setSignal(venueIcon(postbox: item.account.postbox, type: venue.venue?.type ?? "", background: true)) strongSelf.venueIconNode.setSignal(venueIcon(postbox: item.account.postbox, type: venue.venue?.type ?? "", background: true))
} }
if updatedIcon == .stopLiveLocation {
let wavesNode = LiveLocationWavesNode(color: item.presentationData.theme.chat.inputPanel.actionControlForegroundColor)
strongSelf.addSubnode(wavesNode)
strongSelf.wavesNode = wavesNode
} else if let wavesNode = strongSelf.wavesNode {
strongSelf.wavesNode = nil
wavesNode.removeFromSupernode()
}
strongSelf.wavesNode?.color = item.presentationData.theme.chat.inputPanel.actionControlForegroundColor
} }
let iconApply = iconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: iconSize, height: iconSize), boundingSize: CGSize(width: iconSize, height: iconSize), intrinsicInsets: UIEdgeInsets())) let iconApply = iconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: iconSize, height: iconSize), boundingSize: CGSize(width: iconSize, height: iconSize), intrinsicInsets: UIEdgeInsets()))
@ -308,6 +319,8 @@ final class LocationActionListItemNode: ListViewItemNode {
strongSelf.iconNode.frame = iconNodeFrame strongSelf.iconNode.frame = iconNodeFrame
strongSelf.venueIconNode.frame = iconNodeFrame strongSelf.venueIconNode.frame = iconNodeFrame
strongSelf.wavesNode?.frame = CGRect(origin: CGPoint(x: params.leftInset + 11.0, y: floorToScreenPixels((contentSize.height - bottomInset - iconSize) / 2.0) - 4.0), size: CGSize(width: 48.0, height: 48.0))
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height)) strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height))
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset)) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset))
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight)) strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))

View File

@ -31,12 +31,26 @@ private func generateSmallBackgroundImage(color: UIColor) -> UIImage? {
class LocationPinAnnotation: NSObject, MKAnnotation { class LocationPinAnnotation: NSObject, MKAnnotation {
let context: AccountContext let context: AccountContext
let theme: PresentationTheme let theme: PresentationTheme
var coordinate: CLLocationCoordinate2D var coordinate: CLLocationCoordinate2D {
willSet {
self.willChangeValue(forKey: "coordinate")
}
didSet {
self.didChangeValue(forKey: "coordinate")
}
}
let location: TelegramMediaMap? let location: TelegramMediaMap?
let peer: Peer? let peer: Peer?
let message: Message? let message: Message?
let forcedSelection: Bool let forcedSelection: Bool
var heading: Int32? var heading: Int32? {
willSet {
self.willChangeValue(forKey: "heading")
}
didSet {
self.didChangeValue(forKey: "heading")
}
}
var selfPeer: Peer? var selfPeer: Peer?
var title: String? = "" var title: String? = ""

View File

@ -74,18 +74,20 @@ func generateHeadingArrowImage() -> UIImage? {
let bounds = CGRect(origin: CGPoint(), size: size) let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds) context.clear(bounds)
context.saveGState()
let center = CGPoint(x: arrowImageSize.width / 2.0, y: arrowImageSize.height / 2.0) let center = CGPoint(x: arrowImageSize.width / 2.0, y: arrowImageSize.height / 2.0)
context.move(to: center) context.move(to: center)
context.addArc(center: center, radius: arrowImageSize.width / 2.0, startAngle: CGFloat.pi / 2.0 + CGFloat.pi / 8.0, endAngle: CGFloat.pi / 2.0 - CGFloat.pi / 8.0, clockwise: true) context.addArc(center: center, radius: arrowImageSize.width / 2.0, startAngle: CGFloat.pi / 2.0 + CGFloat.pi / 8.0, endAngle: CGFloat.pi / 2.0 - CGFloat.pi / 8.0, clockwise: true)
context.clip() context.clip()
var locations: [CGFloat] = [0.0, 0.4, 1.0] var locations: [CGFloat] = [0.0, 0.4, 1.0]
let colors: [CGColor] = [UIColor(rgb: 0x007ee5, alpha: 0.5).cgColor, UIColor(rgb: 0x007ee5, alpha: 0.3).cgColor, UIColor(rgb: 0x007ee5, alpha: 0.0).cgColor] let colors: [CGColor] = [UIColor(rgb: 0x007aff, alpha: 0.5).cgColor, UIColor(rgb: 0x007aff, alpha: 0.3).cgColor, UIColor(rgb: 0x007aff, alpha: 0.0).cgColor]
let colorSpace = CGColorSpaceCreateDeviceRGB() let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)! let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
context.drawRadialGradient(gradient, startCenter: center, startRadius: 11.0, endCenter: center, endRadius: arrowImageSize.width / 2.0, options: .drawsAfterEndLocation) context.drawRadialGradient(gradient, startCenter: center, startRadius: 11.0, endCenter: center, endRadius: arrowImageSize.width / 2.0, options: .drawsAfterEndLocation)
context.restoreGState()
context.setBlendMode(.clear) context.setBlendMode(.clear)
context.fillEllipse(in: CGRect(x: (arrowImageSize.width - 22.0) / 2.0, y: (arrowImageSize.height - 22.0) / 2.0, width: 22.0, height: 22.0)) context.fillEllipse(in: CGRect(x: (arrowImageSize.width - 22.0) / 2.0, y: (arrowImageSize.height - 22.0) / 2.0, width: 22.0, height: 22.0))
}) })
@ -218,10 +220,10 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
} else { } else {
indicatorOverlay = InvertedProximityCircle(center: location.coordinate, radius: activeProximityRadius) indicatorOverlay = InvertedProximityCircle(center: location.coordinate, radius: activeProximityRadius)
self.mapView?.addOverlay(indicatorOverlay) self.mapView?.addOverlay(indicatorOverlay)
self.indicatorOverlay = indicatorOverlay
indicatorOverlay.alpha = 1.0 indicatorOverlay.alpha = 1.0
self.updateAnimations() self.updateAnimations()
} }
self.indicatorOverlay = indicatorOverlay
} }
} else { } else {
if let indicatorOverlay = self.indicatorOverlay { if let indicatorOverlay = self.indicatorOverlay {
@ -261,7 +263,6 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
animator = ConstantDisplayLinkAnimator(update: { [weak self] in animator = ConstantDisplayLinkAnimator(update: { [weak self] in
self?.updateAnimations() self?.updateAnimations()
}) })
animator.frameInterval = 2
self.animator = animator self.animator = animator
} }
animator.isPaused = false animator.isPaused = false
@ -269,7 +270,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
self.animator?.isPaused = true self.animator?.isPaused = true
} }
self.currentInvertedCircleRenderer?.setNeedsDisplay() self.currentInvertedCircleRenderer?.setNeedsDisplay(MKMapRect.world)
} }
private var circleOverlay: MKCircle? private var circleOverlay: MKCircle?
@ -652,15 +653,33 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
} }
if let updatedAnnotation = dict[annotation.id] { if let updatedAnnotation = dict[annotation.id] {
annotation.coordinate = updatedAnnotation.coordinate UIView.animate(withDuration: 0.2) {
annotation.coordinate = updatedAnnotation.coordinate
}
dict[annotation.id] = nil dict[annotation.id] = nil
} else { } else {
annotationsToRemove.insert(annotation) annotationsToRemove.insert(annotation)
} }
} }
mapView.removeAnnotations(Array(annotationsToRemove)) let selectedAnnotation = mapView.selectedAnnotations.first
mapView.addAnnotations(Array(dict.values)) var updated = false
if !annotationsToRemove.isEmpty {
mapView.removeAnnotations(Array(annotationsToRemove))
updated = true
}
if !dict.isEmpty {
mapView.addAnnotations(Array(dict.values))
updated = true
}
if let selectedAnnotation = selectedAnnotation as? LocationPinAnnotation, updated {
for annotation in self.annotations {
if annotation.id == selectedAnnotation.id {
mapView.selectAnnotation(annotation, animated: false)
break
}
}
}
} }
} }

View File

@ -42,13 +42,13 @@ class LocationViewInteraction {
let goToCoordinate: (CLLocationCoordinate2D) -> Void let goToCoordinate: (CLLocationCoordinate2D) -> Void
let requestDirections: () -> Void let requestDirections: () -> Void
let share: () -> Void let share: () -> Void
let setupProximityNotification: (Bool, MessageId?) -> Void let setupProximityNotification: (Bool, CLLocationCoordinate2D?, MessageId?) -> Void
let updateSendActionHighlight: (Bool) -> Void let updateSendActionHighlight: (Bool) -> Void
let sendLiveLocation: (CLLocationCoordinate2D, Int32?) -> Void let sendLiveLocation: (CLLocationCoordinate2D, Int32?) -> Void
let stopLiveLocation: () -> Void let stopLiveLocation: () -> Void
let updateRightBarButton: (LocationViewRightBarButton) -> Void let updateRightBarButton: (LocationViewRightBarButton) -> Void
init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping () -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (Bool, MessageId?) -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32?) -> Void, stopLiveLocation: @escaping () -> Void, updateRightBarButton: @escaping (LocationViewRightBarButton) -> Void) { init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping () -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (Bool, CLLocationCoordinate2D?, MessageId?) -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32?) -> Void, stopLiveLocation: @escaping () -> Void, updateRightBarButton: @escaping (LocationViewRightBarButton) -> Void) {
self.toggleMapModeSelection = toggleMapModeSelection self.toggleMapModeSelection = toggleMapModeSelection
self.updateMapMode = updateMapMode self.updateMapMode = updateMapMode
self.goToUserLocation = goToUserLocation self.goToUserLocation = goToUserLocation
@ -166,7 +166,7 @@ public final class LocationViewController: ViewController {
}) })
strongSelf.present(OpenInActionSheetController(context: context, item: .location(location: location, withDirections: false), additionalAction: shareAction, openUrl: params.openUrl), in: .window(.root), with: nil) strongSelf.present(OpenInActionSheetController(context: context, item: .location(location: location, withDirections: false), additionalAction: shareAction, openUrl: params.openUrl), in: .window(.root), with: nil)
} }
}, setupProximityNotification: { [weak self] reset, messageId in }, setupProximityNotification: { [weak self] reset, coordinate, messageId in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -204,8 +204,10 @@ public final class LocationViewController: ViewController {
CURRENT_DISTANCE = Double(distance) CURRENT_DISTANCE = Double(distance)
} }
} else { } else if let coordinate = coordinate {
strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.Location_LiveLocationRequired_Title, text: strongSelf.presentationData.strings.Location_LiveLocationRequired_Description, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Location_LiveLocationRequired_ShareLocation, action: { [weak self] in
self?.interaction?.sendLiveLocation(coordinate, distance)
}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})]), in: .window(.root))
} }
}, willDismiss: { [weak self] in }, willDismiss: { [weak self] in
if let strongSelf = self { if let strongSelf = self {
@ -243,21 +245,18 @@ public final class LocationViewController: ViewController {
controller?.dismissAnimated() controller?.dismissAnimated()
if let strongSelf = self { if let strongSelf = self {
params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 15 * 60)) params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 15 * 60))
strongSelf.dismiss()
} }
}), }),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor1Hour, color: .accent, action: { [weak self, weak controller] in ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor1Hour, color: .accent, action: { [weak self, weak controller] in
controller?.dismissAnimated() controller?.dismissAnimated()
if let strongSelf = self { if let strongSelf = self {
params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 60 * 60 - 1)) params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 60 * 60 - 1))
strongSelf.dismiss()
} }
}), }),
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor8Hours, color: .accent, action: { [weak self, weak controller] in ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor8Hours, color: .accent, action: { [weak self, weak controller] in
controller?.dismissAnimated() controller?.dismissAnimated()
if let strongSelf = self { if let strongSelf = self {
params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 8 * 60 * 60)) params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 8 * 60 * 60))
strongSelf.dismiss()
} }
}) })
]), ]),
@ -304,7 +303,7 @@ public final class LocationViewController: ViewController {
return return
} }
self.displayNode = LocationViewControllerNode(context: self.context, presentationData: self.presentationData, subject: self.subject, interaction: interaction) self.displayNode = LocationViewControllerNode(context: self.context, presentationData: self.presentationData, subject: self.subject, interaction: interaction, locationManager: self.locationManager)
self.displayNodeDidLoad() self.displayNodeDidLoad()
self.controllerNode.updateState { state -> LocationViewState in self.controllerNode.updateState { state -> LocationViewState in

View File

@ -15,6 +15,7 @@ import AccountContext
import AppBundle import AppBundle
import CoreLocation import CoreLocation
import Geocoding import Geocoding
import DeviceAccess
func getLocation(from message: Message) -> TelegramMediaMap? { func getLocation(from message: Message) -> TelegramMediaMap? {
return message.media.first(where: { $0 is TelegramMediaMap } ) as? TelegramMediaMap return message.media.first(where: { $0 is TelegramMediaMap } ) as? TelegramMediaMap
@ -191,12 +192,13 @@ struct LocationViewState {
} }
} }
final class LocationViewControllerNode: ViewControllerTracingNode { final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationManagerDelegate {
private let context: AccountContext private let context: AccountContext
private var presentationData: PresentationData private var presentationData: PresentationData
private let presentationDataPromise: Promise<PresentationData> private let presentationDataPromise: Promise<PresentationData>
private var subject: Message private var subject: Message
private let interaction: LocationViewInteraction private let interaction: LocationViewInteraction
private let locationManager: LocationManager
private let listNode: ListView private let listNode: ListView
let headerNode: LocationMapHeaderNode let headerNode: LocationMapHeaderNode
@ -212,12 +214,13 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)? private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
private var listOffset: CGFloat? private var listOffset: CGFloat?
init(context: AccountContext, presentationData: PresentationData, subject: Message, interaction: LocationViewInteraction) { init(context: AccountContext, presentationData: PresentationData, subject: Message, interaction: LocationViewInteraction, locationManager: LocationManager) {
self.context = context self.context = context
self.presentationData = presentationData self.presentationData = presentationData
self.presentationDataPromise = Promise(presentationData) self.presentationDataPromise = Promise(presentationData)
self.subject = subject self.subject = subject
self.interaction = interaction self.interaction = interaction
self.locationManager = locationManager
self.state = LocationViewState() self.state = LocationViewState()
self.statePromise = Promise(self.state) self.statePromise = Promise(self.state)
@ -276,7 +279,10 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
setupProximityNotificationImpl = { reset in setupProximityNotificationImpl = { reset in
let _ = (liveLocations let _ = (liveLocations
|> take(1) |> take(1)
|> deliverOnMainQueue).start(next: { messages in |> deliverOnMainQueue).start(next: { [weak self] messages in
guard let strongSelf = self else {
return
}
var ownMessageId: MessageId? var ownMessageId: MessageId?
for message in messages { for message in messages {
if message.localTags.contains(.OutgoingLiveLocation) { if message.localTags.contains(.OutgoingLiveLocation) {
@ -284,7 +290,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
break break
} }
} }
interaction.setupProximityNotification(reset, ownMessageId) interaction.setupProximityNotification(reset, strongSelf.headerNode.mapNode.currentUserLocation?.coordinate, ownMessageId)
}) })
} }
@ -351,15 +357,29 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
timeout = Double(liveBroadcastingTimeout) timeout = Double(liveBroadcastingTimeout)
} else { } else {
title = presentationData.strings.Map_ShareLiveLocation title = presentationData.strings.Map_ShareLiveLocation
subtitle = presentationData.strings.Map_ShareLiveLocation subtitle = presentationData.strings.Map_ShareLiveLocationHelp
beginTime = nil beginTime = nil
timeout = nil timeout = nil
} }
entries.append(.toggleLiveLocation(presentationData.theme, title, subtitle, userLocation?.coordinate, beginTime, timeout)) entries.append(.toggleLiveLocation(presentationData.theme, title, subtitle, userLocation?.coordinate, beginTime, timeout))
if effectiveLiveLocations.isEmpty {
effectiveLiveLocations = [subject] var sortedLiveLocations: [Message] = []
var effectiveSubject: Message?
for message in effectiveLiveLocations {
if message.id.peerId == subject.id.peerId {
effectiveSubject = message
} else {
sortedLiveLocations.append(message)
}
} }
if let effectiveSubject = effectiveSubject {
sortedLiveLocations.insert(effectiveSubject, at: 0)
} else {
sortedLiveLocations.insert(subject, at: 0)
}
effectiveLiveLocations = sortedLiveLocations
} }
for message in effectiveLiveLocations { for message in effectiveLiveLocations {
@ -480,11 +500,20 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
return state return state
} }
} }
self.locationManager.manager.startUpdatingHeading()
self.locationManager.manager.delegate = self
} }
deinit { deinit {
self.disposable?.dispose() self.disposable?.dispose()
self.geocodingDisposable.dispose() self.geocodingDisposable.dispose()
self.locationManager.manager.stopUpdatingHeading()
}
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
self.headerNode.mapNode.userHeading = CGFloat(newHeading.magneticHeading)
} }
func updatePresentationData(_ presentationData: PresentationData) { func updatePresentationData(_ presentationData: PresentationData) {

View File

@ -16,7 +16,6 @@ static_library(
"//submodules/TelegramStringFormatting:TelegramStringFormatting", "//submodules/TelegramStringFormatting:TelegramStringFormatting",
"//submodules/LiveLocationTimerNode:LiveLocationTimerNode", "//submodules/LiveLocationTimerNode:LiveLocationTimerNode",
"//submodules/AccountContext:AccountContext", "//submodules/AccountContext:AccountContext",
"//submodules/LegacyComponents:LegacyComponents",
"//submodules/OverlayStatusController:OverlayStatusController", "//submodules/OverlayStatusController:OverlayStatusController",
"//submodules/PresentationDataUtils:PresentationDataUtils", "//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/Markdown:Markdown", "//submodules/Markdown:Markdown",

View File

@ -17,7 +17,6 @@ swift_library(
"//submodules/TelegramStringFormatting:TelegramStringFormatting", "//submodules/TelegramStringFormatting:TelegramStringFormatting",
"//submodules/LiveLocationTimerNode:LiveLocationTimerNode", "//submodules/LiveLocationTimerNode:LiveLocationTimerNode",
"//submodules/AccountContext:AccountContext", "//submodules/AccountContext:AccountContext",
"//submodules/LegacyComponents:LegacyComponents",
"//submodules/OverlayStatusController:OverlayStatusController", "//submodules/OverlayStatusController:OverlayStatusController",
"//submodules/PresentationDataUtils:PresentationDataUtils", "//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/Markdown:Markdown", "//submodules/Markdown:Markdown",

View File

@ -10,6 +10,7 @@ import TelegramUIPreferences
import TextFormat import TextFormat
import Markdown import Markdown
import LocalizedPeerData import LocalizedPeerData
import LiveLocationTimerNode
private let titleFont = Font.regular(12.0) private let titleFont = Font.regular(12.0)
private let subtitleFont = Font.regular(10.0) private let subtitleFont = Font.regular(10.0)
@ -31,7 +32,7 @@ final class LocationBroadcastNavigationAccessoryPanel: ASDisplayNode {
private let contentNode: ASDisplayNode private let contentNode: ASDisplayNode
private let iconNode: ASImageNode private let iconNode: ASImageNode
private let wavesNode: LocationBroadcastPanelWavesNode private let wavesNode: LiveLocationWavesNode
private let titleNode: TextNode private let titleNode: TextNode
private let subtitleNode: TextNode private let subtitleNode: TextNode
private let closeButton: HighlightableButtonNode private let closeButton: HighlightableButtonNode
@ -58,7 +59,7 @@ final class LocationBroadcastNavigationAccessoryPanel: ASDisplayNode {
self.iconNode.displaysAsynchronously = false self.iconNode.displaysAsynchronously = false
self.iconNode.image = PresentationResourcesRootController.navigationLiveLocationIcon(self.theme) self.iconNode.image = PresentationResourcesRootController.navigationLiveLocationIcon(self.theme)
self.wavesNode = LocationBroadcastPanelWavesNode(color: self.theme.rootController.navigationBar.accentTextColor) self.wavesNode = LiveLocationWavesNode(color: self.theme.rootController.navigationBar.accentTextColor)
self.titleNode = TextNode() self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false self.titleNode.isUserInteractionEnabled = false

View File

@ -261,7 +261,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId, venueType: venueType, liveBroadcastingTimeout: nil, heading: nil) let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId, venueType: venueType, liveBroadcastingTimeout: nil, heading: nil)
return (mediaMap, nil) return (mediaMap, nil)
case let .messageMediaGeoLive(geo, heading, period): case let .messageMediaGeoLive(geo, heading, period):
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period, heading: heading) let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period, heading: heading > 0 ? heading : nil)
return (mediaMap, nil) return (mediaMap, nil)
case let .messageMediaDocument(_, document, ttlSeconds): case let .messageMediaDocument(_, document, ttlSeconds):
if let document = document { if let document = document {

View File

@ -10,7 +10,7 @@ func telegramMediaMapFromApiGeoPoint(_ geo: Api.GeoPoint, title: String?, addres
venue = MapVenue(title: title, address: address, provider: provider, id: venueId, type: venueType) venue = MapVenue(title: title, address: address, provider: provider, id: venueId, type: venueType)
} }
switch geo { switch geo {
case let .geoPoint(_, long, lat, accessHash, accuracyRadius): case let .geoPoint(_, long, lat, _, accuracyRadius):
return TelegramMediaMap(latitude: lat, longitude: long, heading: heading.flatMap { Double($0) }, accuracyRadius: accuracyRadius.flatMap { Double($0) }, geoPlace: nil, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout) return TelegramMediaMap(latitude: lat, longitude: long, heading: heading.flatMap { Double($0) }, accuracyRadius: accuracyRadius.flatMap { Double($0) }, geoPlace: nil, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout)
case .geoPointEmpty: case .geoPointEmpty:
return TelegramMediaMap(latitude: 0.0, longitude: 0.0, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout) return TelegramMediaMap(latitude: 0.0, longitude: 0.0, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout)

View File

@ -184,9 +184,12 @@ public final class PrincipalThemeEssentialGraphics {
public let incomingBubbleGradientImage: UIImage? public let incomingBubbleGradientImage: UIImage?
public let outgoingBubbleGradientImage: UIImage? public let outgoingBubbleGradientImage: UIImage?
public let hasWallpaper: Bool
init(mediaBox: MediaBox, presentationTheme: PresentationTheme, wallpaper initialWallpaper: TelegramWallpaper, preview: Bool = false, knockoutMode: Bool, bubbleCorners: PresentationChatBubbleCorners) { init(mediaBox: MediaBox, presentationTheme: PresentationTheme, wallpaper initialWallpaper: TelegramWallpaper, preview: Bool = false, knockoutMode: Bool, bubbleCorners: PresentationChatBubbleCorners) {
let theme = presentationTheme.chat let theme = presentationTheme.chat
var wallpaper = initialWallpaper var wallpaper = initialWallpaper
self.hasWallpaper = !wallpaper.isEmpty
let incoming: PresentationThemeBubbleColorComponents = wallpaper.isEmpty ? theme.message.incoming.bubble.withoutWallpaper : theme.message.incoming.bubble.withWallpaper let incoming: PresentationThemeBubbleColorComponents = wallpaper.isEmpty ? theme.message.incoming.bubble.withoutWallpaper : theme.message.incoming.bubble.withWallpaper
let outgoing: PresentationThemeBubbleColorComponents = wallpaper.isEmpty ? theme.message.outgoing.bubble.withoutWallpaper : theme.message.outgoing.bubble.withWallpaper let outgoing: PresentationThemeBubbleColorComponents = wallpaper.isEmpty ? theme.message.outgoing.bubble.withoutWallpaper : theme.message.outgoing.bubble.withWallpaper

View File

@ -250,7 +250,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
if let currentParams = self.currentParams { if let currentParams = self.currentParams {
var backgroundFrame = CGRect(x: currentParams.contentOrigin.x - offset, y: currentParams.contentOrigin.y, width: currentParams.size.width + offset, height: currentParams.size.height) let backgroundFrame = CGRect(x: currentParams.contentOrigin.x - offset, y: 0.0, width: currentParams.size.width + offset, height: currentParams.size.height)
self.backgroundNode?.updateLayout(size: backgroundFrame.size, transition: .immediate) self.backgroundNode?.updateLayout(size: backgroundFrame.size, transition: .immediate)
self.backgroundNode?.frame = backgroundFrame self.backgroundNode?.frame = backgroundFrame
} }
@ -262,6 +262,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
} }
func isExtractedToContextPreviewUpdated(_ isExtractedToContextPreview: Bool) {
}
func update(size: CGSize, contentOrigin: CGPoint, index: Int, presentationData: ChatPresentationData, graphics: PrincipalThemeEssentialGraphics, backgroundType: ChatMessageBackgroundType, messageSelection: Bool?) { func update(size: CGSize, contentOrigin: CGPoint, index: Int, presentationData: ChatPresentationData, graphics: PrincipalThemeEssentialGraphics, backgroundType: ChatMessageBackgroundType, messageSelection: Bool?) {
self.currentParams = (size, contentOrigin, presentationData, graphics, backgroundType, messageSelection) self.currentParams = (size, contentOrigin, presentationData, graphics, backgroundType, messageSelection)
let bounds = CGRect(origin: CGPoint(), size: size) let bounds = CGRect(origin: CGPoint(), size: size)
@ -277,17 +280,20 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if let _ = self.selectionBackgroundNode { if let _ = self.selectionBackgroundNode {
} else { } else {
let selectionBackgroundNode = ASDisplayNode() let selectionBackgroundNode = ASDisplayNode()
self.sourceNode.contentNode.insertSubnode(selectionBackgroundNode, at: 0) self.containerNode.insertSubnode(selectionBackgroundNode, at: 0)
self.selectionBackgroundNode = selectionBackgroundNode self.selectionBackgroundNode = selectionBackgroundNode
} }
var selectionBackgroundFrame = bounds.offsetBy(dx: contentOrigin.x, dy: contentOrigin.y) var selectionBackgroundFrame = bounds.offsetBy(dx: contentOrigin.x, dy: 0.0)
if index == 0 && contentOrigin.y > 0.0 { if index == 0 && contentOrigin.y > 0.0 {
selectionBackgroundFrame.origin.y -= contentOrigin.y selectionBackgroundFrame.origin.y -= contentOrigin.y
selectionBackgroundFrame.size.height += contentOrigin.y selectionBackgroundFrame.size.height += contentOrigin.y
} }
self.selectionBackgroundNode?.backgroundColor = messageTheme.accentTextColor.withAlphaComponent(0.08) let bubbleColor = graphics.hasWallpaper ? messageTheme.bubble.withWallpaper.fill : messageTheme.bubble.withoutWallpaper.fill
let selectionColor = bubbleColor.withAlphaComponent(1.0).mixedWith(messageTheme.accentTextColor.withAlphaComponent(1.0), alpha: 0.08)
self.selectionBackgroundNode?.backgroundColor = selectionColor
self.selectionBackgroundNode?.frame = selectionBackgroundFrame self.selectionBackgroundNode?.frame = selectionBackgroundFrame
} else if let selectionBackgroundNode = self.selectionBackgroundNode { } else if let selectionBackgroundNode = self.selectionBackgroundNode {
self.selectionBackgroundNode = nil self.selectionBackgroundNode = nil
@ -1102,7 +1108,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset) let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset)
var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, BubbleItemAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = [] var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, BubbleItemAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = []
var addedContentNodes: [(Message, ChatMessageBubbleContentNode)]? var addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode)]?
let (contentNodeMessagesAndClasses, needSeparateContainers) = contentNodeMessagesAndClassesForItem(item) let (contentNodeMessagesAndClasses, needSeparateContainers) = contentNodeMessagesAndClassesForItem(item)
for (contentNodeMessage, contentNodeClass, attributes, bubbleAttributes) in contentNodeMessagesAndClasses { for (contentNodeMessage, contentNodeClass, attributes, bubbleAttributes) in contentNodeMessagesAndClasses {
@ -1120,7 +1126,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if addedContentNodes == nil { if addedContentNodes == nil {
addedContentNodes = [] addedContentNodes = []
} }
addedContentNodes!.append((contentNodeMessage, contentNode)) addedContentNodes!.append((contentNodeMessage, bubbleAttributes.isAttachment, contentNode))
} }
} }
@ -1279,7 +1285,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let (properties, unboundSize, maxNodeWidth, nodeLayout) = prepareLayout(contentItem, layoutConstants, prepareContentPosition, itemSelection, CGSize(width: maximumContentWidth, height: CGFloat.greatestFiniteMagnitude)) let (properties, unboundSize, maxNodeWidth, nodeLayout) = prepareLayout(contentItem, layoutConstants, prepareContentPosition, itemSelection, CGSize(width: maximumContentWidth, height: CGFloat.greatestFiniteMagnitude))
maximumNodeWidth = min(maximumNodeWidth, maxNodeWidth) maximumNodeWidth = min(maximumNodeWidth, maxNodeWidth)
contentPropertiesAndLayouts.append((unboundSize, properties, prepareContentPosition, bubbleAttributes, nodeLayout, needSeparateContainers ? message.stableId : nil, itemSelection)) contentPropertiesAndLayouts.append((unboundSize, properties, prepareContentPosition, bubbleAttributes, nodeLayout, needSeparateContainers && !bubbleAttributes.isAttachment ? message.stableId : nil, itemSelection))
switch properties.hidesBackground { switch properties.hidesBackground {
case .never: case .never:
@ -1616,7 +1622,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
for i in 0 ..< contentPropertiesAndLayouts.count { for i in 0 ..< contentPropertiesAndLayouts.count {
let (_, contentNodeProperties, preparePosition, _, contentNodeLayout, contentGroupId, itemSelection) = contentPropertiesAndLayouts[i] let (_, contentNodeProperties, preparePosition, attributes, contentNodeLayout, contentGroupId, itemSelection) = contentPropertiesAndLayouts[i]
if let mosaicRange = mosaicRange, mosaicRange.contains(i), let (framesAndPositions, size) = calculatedGroupFramesAndSize { if let mosaicRange = mosaicRange, mosaicRange.contains(i), let (framesAndPositions, size) = calculatedGroupFramesAndSize {
let mosaicIndex = i - mosaicRange.lowerBound let mosaicIndex = i - mosaicRange.lowerBound
@ -1769,7 +1775,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
var contentSize = CGSize(width: maxContentWidth, height: 0.0) var contentSize = CGSize(width: maxContentWidth, height: 0.0)
var contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, (ListViewItemUpdateAnimation, Bool) -> Void)] = [] var contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool) -> Void)] = []
var contentContainerNodeFrames: [(UInt32, CGRect, Bool?)] = [] var contentContainerNodeFrames: [(UInt32, CGRect, Bool?)] = []
var currentContainerGroupId: UInt32? var currentContainerGroupId: UInt32?
var currentItemSelection: Bool? var currentItemSelection: Bool?
@ -1797,7 +1803,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let (_, apply) = finalize(maxContentWidth) let (_, apply) = finalize(maxContentWidth)
let contentNodeFrame = framesAndPositions[mosaicIndex].0.offsetBy(dx: 0.0, dy: contentNodesHeight) let contentNodeFrame = framesAndPositions[mosaicIndex].0.offsetBy(dx: 0.0, dy: contentNodesHeight)
contentNodeFramesPropertiesAndApply.append((contentNodeFrame, properties, apply)) contentNodeFramesPropertiesAndApply.append((contentNodeFrame, properties, false, apply))
if mosaicIndex == mosaicRange.upperBound - 1 { if mosaicIndex == mosaicRange.upperBound - 1 {
contentNodesHeight += size.height contentNodesHeight += size.height
@ -1807,8 +1813,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
} else { } else {
if i == 0 && !headerSize.height.isZero { if i == 0 && !headerSize.height.isZero {
contentNodesHeight += properties.headerSpacing if contentGroupId == nil {
totalContentNodesHeight += contentNodesHeight contentNodesHeight += properties.headerSpacing
}
totalContentNodesHeight += properties.headerSpacing
} }
if currentContainerGroupId != contentGroupId { if currentContainerGroupId != contentGroupId {
@ -1816,17 +1824,22 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
var overlapOffset: CGFloat = 0.0 var overlapOffset: CGFloat = 0.0
if !contentContainerNodeFrames.isEmpty { if !contentContainerNodeFrames.isEmpty {
overlapOffset = smallContainerGroupOverlap overlapOffset = smallContainerGroupOverlap
}
contentContainerNodeFrames.append((containerGroupId, CGRect(x: 0.0, y: headerSize.height + totalContentNodesHeight - contentNodesHeight - overlapOffset, width: maxContentWidth, height: contentNodesHeight), currentItemSelection))
if !overlapOffset.isZero {
totalContentNodesHeight -= smallContainerGroupOverlap totalContentNodesHeight -= smallContainerGroupOverlap
} }
contentContainerNodeFrames.append((containerGroupId, CGRect(x: 0.0, y: totalContentNodesHeight - contentNodesHeight - overlapOffset, width: maxContentWidth, height: contentNodesHeight), currentItemSelection)) if contentGroupId == nil {
totalContentNodesHeight += 3.0
}
} }
contentNodesHeight = 0.0 contentNodesHeight = contentGroupId == nil ? totalContentNodesHeight : 0.0
currentContainerGroupId = contentGroupId currentContainerGroupId = contentGroupId
currentItemSelection = itemSelection currentItemSelection = itemSelection
} }
let (size, apply) = finalize(maxContentWidth) let (size, apply) = finalize(maxContentWidth)
contentNodeFramesPropertiesAndApply.append((CGRect(origin: CGPoint(x: 0.0, y: contentNodesHeight), size: size), properties, apply)) contentNodeFramesPropertiesAndApply.append((CGRect(origin: CGPoint(x: 0.0, y: contentNodesHeight), size: size), properties, contentGroupId == nil, apply))
contentNodesHeight += size.height contentNodesHeight += size.height
totalContentNodesHeight += size.height totalContentNodesHeight += size.height
@ -1838,7 +1851,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if !contentContainerNodeFrames.isEmpty { if !contentContainerNodeFrames.isEmpty {
overlapOffset = smallContainerGroupOverlap overlapOffset = smallContainerGroupOverlap
} }
contentContainerNodeFrames.append((containerGroupId, CGRect(x: 0.0, y: totalContentNodesHeight - contentNodesHeight - overlapOffset, width: maxContentWidth, height: contentNodesHeight), currentItemSelection)) contentContainerNodeFrames.append((containerGroupId, CGRect(x: 0.0, y: headerSize.height + totalContentNodesHeight - contentNodesHeight - overlapOffset, width: maxContentWidth, height: contentNodesHeight), currentItemSelection))
if !overlapOffset.isZero { if !overlapOffset.isZero {
totalContentNodesHeight -= smallContainerGroupOverlap totalContentNodesHeight -= smallContainerGroupOverlap
} }
@ -2007,9 +2020,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
replyInfoSizeApply: (CGSize, () -> ChatMessageReplyInfoNode?), replyInfoSizeApply: (CGSize, () -> ChatMessageReplyInfoNode?),
replyInfoOriginY: CGFloat, replyInfoOriginY: CGFloat,
removedContentNodeIndices: [Int]?, removedContentNodeIndices: [Int]?,
addedContentNodes: [(Message, ChatMessageBubbleContentNode)]?, addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode)]?,
contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)], contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)],
contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, (ListViewItemUpdateAnimation, Bool) -> Void)], contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool) -> Void)],
contentContainerNodeFrames: [(UInt32, CGRect, Bool?)], contentContainerNodeFrames: [(UInt32, CGRect, Bool?)],
mosaicStatusOrigin: CGPoint?, mosaicStatusOrigin: CGPoint?,
mosaicStatusSizeAndApply: (CGSize, (Bool) -> ChatMessageDateAndStatusNode)?, mosaicStatusSizeAndApply: (CGSize, (Bool) -> ChatMessageDateAndStatusNode)?,
@ -2263,10 +2276,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
} }
} }
contextSourceNode.isExtractedToContextPreviewUpdated = { [weak strongSelf, weak contextSourceNode] isExtractedToContextPreview in contextSourceNode.isExtractedToContextPreviewUpdated = { [weak strongSelf, weak container, weak contextSourceNode] isExtractedToContextPreview in
guard let strongSelf = strongSelf, let strongContextSourceNode = contextSourceNode else { guard let strongSelf = strongSelf, let strongContextSourceNode = contextSourceNode else {
return return
} }
container?.isExtractedToContextPreviewUpdated(isExtractedToContextPreview)
// if !isExtractedToContextPreview, let (rect, size) = strongSelf.absoluteRect { // if !isExtractedToContextPreview, let (rect, size) = strongSelf.absoluteRect {
// strongSelf.updateAbsoluteRect(rect, within: size) // strongSelf.updateAbsoluteRect(rect, within: size)
@ -2305,22 +2320,18 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let containerFrame = CGRect(origin: relativeFrame.origin, size: CGSize(width: params.width, height: relativeFrame.size.height)) let containerFrame = CGRect(origin: relativeFrame.origin, size: CGSize(width: params.width, height: relativeFrame.size.height))
contentContainer?.sourceNode.frame = CGRect(origin: CGPoint(), size: containerFrame.size) contentContainer?.sourceNode.frame = CGRect(origin: CGPoint(), size: containerFrame.size)
contentContainer?.sourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: containerFrame.size) contentContainer?.sourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: containerFrame.size)
// contentContainer?.sourceNode.backgroundColor = UIColor.blue.withAlphaComponent(0.5)
contentContainer?.containerNode.frame = containerFrame contentContainer?.containerNode.frame = containerFrame
// contentContainer?.containerNode.backgroundColor = UIColor.red.withAlphaComponent(0.5)
contentContainer?.sourceNode.contentRect = CGRect(origin: CGPoint(x: backgroundFrame.minX + incomingOffset, y: 0.0), size: relativeFrame.size) contentContainer?.sourceNode.contentRect = CGRect(origin: CGPoint(x: backgroundFrame.minX + incomingOffset, y: 0.0), size: relativeFrame.size)
contentContainer?.containerNode.targetNodeForActivationProgressContentRect = relativeFrame.offsetBy(dx: backgroundFrame.minX + incomingOffset, dy: 0.0) contentContainer?.containerNode.targetNodeForActivationProgressContentRect = CGRect(origin: CGPoint(x: backgroundFrame.minX + incomingOffset, y: 0.0), size: relativeFrame.size)
if previousContextFrame?.size != contentContainer?.containerNode.bounds.size || previousContextContentFrame != contentContainer?.sourceNode.contentRect { if previousContextFrame?.size != contentContainer?.containerNode.bounds.size || previousContextContentFrame != contentContainer?.sourceNode.contentRect {
contentContainer?.sourceNode.layoutUpdated?(relativeFrame.size) contentContainer?.sourceNode.layoutUpdated?(relativeFrame.size)
} }
contentContainer?.update(size: relativeFrame.size, contentOrigin: contentOrigin, index: index, presentationData: item.presentationData, graphics: graphics, backgroundType: backgroundType, messageSelection: itemSelection) contentContainer?.update(size: relativeFrame.size, contentOrigin: contentOrigin, index: index, presentationData: item.presentationData, graphics: graphics, backgroundType: backgroundType, messageSelection: itemSelection)
print("container \(relativeFrame)")
index += 1 index += 1
} }
@ -2359,12 +2370,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
if let addedContentNodes = addedContentNodes { if let addedContentNodes = addedContentNodes {
for (contentNodeMessage, contentNode) in addedContentNodes { for (contentNodeMessage, isAttachent, contentNode) in addedContentNodes {
updatedContentNodes.append(contentNode) updatedContentNodes.append(contentNode)
let contextSourceNode: ContextExtractedContentContainingNode = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == contentNodeMessage.stableId })?.sourceNode ?? strongSelf.mainContextSourceNode let contextSourceNode: ContextExtractedContentContainingNode
if isAttachent {
print(contextSourceNode.debugDescription) contextSourceNode = strongSelf.mainContextSourceNode
} else {
contextSourceNode = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == contentNodeMessage.stableId })?.sourceNode ?? strongSelf.mainContextSourceNode
}
contextSourceNode.contentNode.addSubnode(contentNode) contextSourceNode.contentNode.addSubnode(contentNode)
contentNode.visibility = strongSelf.visibility contentNode.visibility = strongSelf.visibility
@ -2378,7 +2392,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
var sortedContentNodes: [ChatMessageBubbleContentNode] = [] var sortedContentNodes: [ChatMessageBubbleContentNode] = []
outer: for (message, nodeClass, _, _) in contentNodeMessagesAndClasses { outer: for (message, nodeClass, _, _) in contentNodeMessagesAndClasses {
if let addedContentNodes = addedContentNodes { if let addedContentNodes = addedContentNodes {
for (contentNodeMessage, contentNode) in addedContentNodes { for (contentNodeMessage, _, contentNode) in addedContentNodes {
if type(of: contentNode) == nodeClass && contentNodeMessage.stableId == message.stableId { if type(of: contentNode) == nodeClass && contentNodeMessage.stableId == message.stableId {
sortedContentNodes.append(contentNode) sortedContentNodes.append(contentNode)
continue outer continue outer
@ -2399,7 +2413,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
var contentNodeIndex = 0 var contentNodeIndex = 0
for (relativeFrame, _, apply) in contentNodeFramesPropertiesAndApply { for (relativeFrame, _, useContentOrigin, apply) in contentNodeFramesPropertiesAndApply {
apply(animation, synchronousLoads) apply(animation, synchronousLoads)
if contentNodeIndex >= strongSelf.contentNodes.count { if contentNodeIndex >= strongSelf.contentNodes.count {
@ -2407,17 +2421,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
} }
let contentNode = strongSelf.contentNodes[contentNodeIndex] let contentNode = strongSelf.contentNodes[contentNodeIndex]
let contentNodeFrame = relativeFrame.offsetBy(dx: contentOrigin.x, dy: contentOrigin.y) let contentNodeFrame = relativeFrame.offsetBy(dx: contentOrigin.x, dy: useContentOrigin ? contentOrigin.y : 0.0)
let previousContentNodeFrame = contentNode.frame let previousContentNodeFrame = contentNode.frame
contentNode.frame = contentNodeFrame contentNode.frame = contentNodeFrame
print("frame \(contentNodeFrame.debugDescription)")
if case let .System(duration) = animation { if case let .System(duration) = animation {
var animateFrame = false var animateFrame = false
var animateAlpha = false var animateAlpha = false
if let addedContentNodes = addedContentNodes { if let addedContentNodes = addedContentNodes {
if !addedContentNodes.contains(where: { $0.1 === contentNode }) { if !addedContentNodes.contains(where: { $0.2 === contentNode }) {
animateFrame = true animateFrame = true
} else { } else {
animateAlpha = true animateAlpha = true
@ -2647,8 +2659,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if let replyInfoNode = self.replyInfoNode { if let replyInfoNode = self.replyInfoNode {
node.addSubnode(replyInfoNode) node.addSubnode(replyInfoNode)
} }
for contentNode in self.contentNodes { if !self.contentContainers.isEmpty {
node.addSubnode(contentNode) node.addSubnode(self.contentContainersWrapperNode)
} else {
for contentNode in self.contentNodes {
node.addSubnode(contentNode)
}
} }
self.mainContextSourceNode.contentNode.addSubnode(node) self.mainContextSourceNode.contentNode.addSubnode(node)
self.transitionClippingNode = node self.transitionClippingNode = node
@ -2663,8 +2679,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
if let replyInfoNode = self.replyInfoNode { if let replyInfoNode = self.replyInfoNode {
self.mainContextSourceNode.contentNode.addSubnode(replyInfoNode) self.mainContextSourceNode.contentNode.addSubnode(replyInfoNode)
} }
for contentNode in self.contentNodes { if !self.contentContainers.isEmpty {
self.mainContextSourceNode.contentNode.addSubnode(contentNode) self.mainContextSourceNode.contentNode.addSubnode(self.contentContainersWrapperNode)
} else {
for contentNode in self.contentNodes {
self.mainContextSourceNode.contentNode.addSubnode(contentNode)
}
} }
transitionClippingNode.removeFromSupernode() transitionClippingNode.removeFromSupernode()
self.transitionClippingNode = nil self.transitionClippingNode = nil

View File

@ -145,7 +145,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
var mode: ChatMessageLiveLocationPositionNode.Mode = .location(selectedMedia) var mode: ChatMessageLiveLocationPositionNode.Mode = .location(selectedMedia)
if let selectedMedia = selectedMedia, let peer = item.message.author { if let selectedMedia = selectedMedia, let peer = item.message.author {
if selectedMedia.liveBroadcastingTimeout != nil { if selectedMedia.liveBroadcastingTimeout != nil {
mode = .liveLocation(peer, activeLiveBroadcastingTimeout != nil) mode = .liveLocation(peer: peer, active: activeLiveBroadcastingTimeout != nil, latitude: selectedMedia.latitude, longitude: selectedMedia.longitude, heading: selectedMedia.heading)
} }
} }
let (pinSize, pinApply) = makePinLayout(item.context, item.presentationData.theme.theme, mode) let (pinSize, pinApply) = makePinLayout(item.context, item.presentationData.theme.theme, mode)