mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various fixes
This commit is contained in:
parent
43517068e8
commit
05f3e47623
@ -10,6 +10,7 @@ import AvatarNode
|
||||
import LocationResources
|
||||
import AppBundle
|
||||
import AccountContext
|
||||
import CoreLocation
|
||||
|
||||
private let avatarFont = avatarPlaceholderFont(size: 24.0)
|
||||
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 enum Mode {
|
||||
case liveLocation(Peer, Bool)
|
||||
case liveLocation(peer: Peer, active: Bool, latitude: Double, longitude: Double, heading: Double?)
|
||||
case location(TelegramMediaMap?)
|
||||
}
|
||||
|
||||
@ -60,9 +86,12 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
||||
private let iconNode: TransformImageNode
|
||||
private let avatarNode: AvatarNode
|
||||
private let pulseNode: ASImageNode
|
||||
private let arrowNode: ASImageNode
|
||||
|
||||
private var pulseImage: UIImage?
|
||||
private var arrowImage: UIImage?
|
||||
private var venueType: String?
|
||||
private var coordinate: (Double, Double)?
|
||||
|
||||
override public init() {
|
||||
let isLayerBacked = !smartInvertColorsEnabled()
|
||||
@ -84,11 +113,19 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
||||
self.pulseNode.displayWithoutProcessing = 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()
|
||||
|
||||
self.isLayerBacked = isLayerBacked
|
||||
|
||||
self.addSubnode(self.pulseNode)
|
||||
self.addSubnode(self.arrowNode)
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
self.addSubnode(self.avatarNode)
|
||||
@ -98,17 +135,24 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
||||
let iconLayout = self.iconNode.asyncLayout()
|
||||
|
||||
let currentPulseImage = self.pulseImage
|
||||
let currentArrowImage = self.arrowImage
|
||||
let currentVenueType = self.venueType
|
||||
|
||||
let currentCoordinate = self.coordinate
|
||||
|
||||
return { [weak self] context, theme, mode in
|
||||
var updatedVenueType: String?
|
||||
|
||||
let backgroundImage: UIImage?
|
||||
var hasPulse = false
|
||||
var heading: Double?
|
||||
var coordinate: (Double, Double)?
|
||||
switch mode {
|
||||
case let .liveLocation(_, active):
|
||||
case let .liveLocation(_, active, latitude, longitude, headingValue):
|
||||
backgroundImage = avatarBackgroundImage
|
||||
hasPulse = active
|
||||
coordinate = (latitude, longitude)
|
||||
heading = headingValue
|
||||
case let .location(location):
|
||||
let venueType = location?.venue?.type ?? ""
|
||||
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 arrowImage: UIImage?
|
||||
if hasPulse {
|
||||
pulseImage = currentPulseImage ?? generateFilledCircleImage(diameter: 120.0, color: UIColor(rgb: 0x007aff, alpha: 0.27))
|
||||
} else {
|
||||
pulseImage = nil
|
||||
}
|
||||
|
||||
if let _ = heading {
|
||||
arrowImage = currentArrowImage ?? generateHeadingArrowImage()
|
||||
} else {
|
||||
arrowImage = nil
|
||||
}
|
||||
|
||||
return (CGSize(width: 62.0, height: 74.0), {
|
||||
if let strongSelf = self {
|
||||
strongSelf.coordinate = coordinate
|
||||
|
||||
if strongSelf.backgroundNode.image !== backgroundImage {
|
||||
strongSelf.backgroundNode.image = backgroundImage
|
||||
}
|
||||
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))
|
||||
switch mode {
|
||||
case let .liveLocation(peer, active):
|
||||
case let .liveLocation(peer, active, _, _, _):
|
||||
strongSelf.avatarNode.setPeer(context: context, theme: theme, peer: peer)
|
||||
strongSelf.avatarNode.isHidden = false
|
||||
strongSelf.iconNode.isHidden = true
|
||||
strongSelf.avatarNode.alpha = active ? 1.0 : 0.6
|
||||
case let .location(location):
|
||||
case .location:
|
||||
strongSelf.iconNode.isHidden = false
|
||||
strongSelf.avatarNode.isHidden = true
|
||||
}
|
||||
@ -147,7 +221,6 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
||||
strongSelf.venueType = updatedVenueType
|
||||
strongSelf.iconNode.setSignal(venueIcon(postbox: context.account.postbox, type: updatedVenueType, background: false))
|
||||
}
|
||||
|
||||
|
||||
let arguments = VenueIconArguments(defaultForegroundColor: theme.chat.inputPanel.actionControlForegroundColor)
|
||||
let iconSize = CGSize(width: 44.0, height: 44.0)
|
||||
@ -170,6 +243,13 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
||||
strongSelf.pulseNode.isHidden = true
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -3,9 +3,7 @@ import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
|
||||
import LegacyComponents
|
||||
|
||||
private final class LocationBroadcastPanelWavesNodeParams: NSObject {
|
||||
private final class LiveLocationWavesNodeParams: NSObject {
|
||||
let color: UIColor
|
||||
let progress: CGFloat
|
||||
|
||||
@ -21,8 +19,8 @@ private func degToRad(_ degrees: CGFloat) -> CGFloat {
|
||||
return degrees * CGFloat.pi / 180.0
|
||||
}
|
||||
|
||||
final class LocationBroadcastPanelWavesNode: ASDisplayNode {
|
||||
var color: UIColor {
|
||||
public final class LiveLocationWavesNode: ASDisplayNode {
|
||||
public var color: UIColor {
|
||||
didSet {
|
||||
self.setNeedsDisplay()
|
||||
}
|
||||
@ -34,7 +32,9 @@ final class LocationBroadcastPanelWavesNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
init(color: UIColor) {
|
||||
var animator: ConstantDisplayLinkAnimator?
|
||||
|
||||
public init(color: UIColor) {
|
||||
self.color = color
|
||||
|
||||
super.init()
|
||||
@ -43,42 +43,72 @@ final class LocationBroadcastPanelWavesNode: ASDisplayNode {
|
||||
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()
|
||||
|
||||
self.pop_removeAnimation(forKey: "indefiniteProgress")
|
||||
|
||||
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")
|
||||
self.updateAnimations(inHierarchy: true)
|
||||
}
|
||||
|
||||
override func didExitHierarchy() {
|
||||
public override func 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 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()!
|
||||
|
||||
if !isRasterizing {
|
||||
@ -87,7 +117,7 @@ final class LocationBroadcastPanelWavesNode: ASDisplayNode {
|
||||
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 length: CGFloat = 9.0
|
||||
|
@ -74,7 +74,7 @@ private func generateLiveLocationIcon(theme: PresentationTheme, stop: Bool) -> U
|
||||
context.scaleBy(x: 1.0, y: -1.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))
|
||||
}
|
||||
})!
|
||||
@ -148,6 +148,7 @@ final class LocationActionListItemNode: ListViewItemNode {
|
||||
private let iconNode: ASImageNode
|
||||
private let venueIconNode: TransformImageNode
|
||||
private var timerNode: ChatMessageLiveLocationTimerNode?
|
||||
private var wavesNode: LiveLocationWavesNode?
|
||||
|
||||
private var item: LocationActionListItem?
|
||||
private var layoutParams: ListViewItemLayoutParams?
|
||||
@ -278,6 +279,16 @@ final class LocationActionListItemNode: ListViewItemNode {
|
||||
strongSelf.venueIconNode.isHidden = false
|
||||
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()))
|
||||
@ -308,6 +319,8 @@ final class LocationActionListItemNode: ListViewItemNode {
|
||||
strongSelf.iconNode.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.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))
|
||||
|
@ -31,12 +31,26 @@ private func generateSmallBackgroundImage(color: UIColor) -> UIImage? {
|
||||
class LocationPinAnnotation: NSObject, MKAnnotation {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
var coordinate: CLLocationCoordinate2D
|
||||
var coordinate: CLLocationCoordinate2D {
|
||||
willSet {
|
||||
self.willChangeValue(forKey: "coordinate")
|
||||
}
|
||||
didSet {
|
||||
self.didChangeValue(forKey: "coordinate")
|
||||
}
|
||||
}
|
||||
let location: TelegramMediaMap?
|
||||
let peer: Peer?
|
||||
let message: Message?
|
||||
let forcedSelection: Bool
|
||||
var heading: Int32?
|
||||
var heading: Int32? {
|
||||
willSet {
|
||||
self.willChangeValue(forKey: "heading")
|
||||
}
|
||||
didSet {
|
||||
self.didChangeValue(forKey: "heading")
|
||||
}
|
||||
}
|
||||
|
||||
var selfPeer: Peer?
|
||||
var title: String? = ""
|
||||
|
@ -74,18 +74,20 @@ func generateHeadingArrowImage() -> UIImage? {
|
||||
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: 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 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.restoreGState()
|
||||
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))
|
||||
})
|
||||
@ -218,10 +220,10 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
||||
} else {
|
||||
indicatorOverlay = InvertedProximityCircle(center: location.coordinate, radius: activeProximityRadius)
|
||||
self.mapView?.addOverlay(indicatorOverlay)
|
||||
self.indicatorOverlay = indicatorOverlay
|
||||
indicatorOverlay.alpha = 1.0
|
||||
self.updateAnimations()
|
||||
}
|
||||
self.indicatorOverlay = indicatorOverlay
|
||||
}
|
||||
} else {
|
||||
if let indicatorOverlay = self.indicatorOverlay {
|
||||
@ -261,7 +263,6 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
||||
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
||||
self?.updateAnimations()
|
||||
})
|
||||
animator.frameInterval = 2
|
||||
self.animator = animator
|
||||
}
|
||||
animator.isPaused = false
|
||||
@ -269,7 +270,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
||||
self.animator?.isPaused = true
|
||||
}
|
||||
|
||||
self.currentInvertedCircleRenderer?.setNeedsDisplay()
|
||||
self.currentInvertedCircleRenderer?.setNeedsDisplay(MKMapRect.world)
|
||||
}
|
||||
|
||||
private var circleOverlay: MKCircle?
|
||||
@ -652,15 +653,33 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
||||
}
|
||||
|
||||
if let updatedAnnotation = dict[annotation.id] {
|
||||
annotation.coordinate = updatedAnnotation.coordinate
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
annotation.coordinate = updatedAnnotation.coordinate
|
||||
}
|
||||
dict[annotation.id] = nil
|
||||
} else {
|
||||
annotationsToRemove.insert(annotation)
|
||||
}
|
||||
}
|
||||
|
||||
mapView.removeAnnotations(Array(annotationsToRemove))
|
||||
mapView.addAnnotations(Array(dict.values))
|
||||
let selectedAnnotation = mapView.selectedAnnotations.first
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,13 +42,13 @@ class LocationViewInteraction {
|
||||
let goToCoordinate: (CLLocationCoordinate2D) -> Void
|
||||
let requestDirections: () -> Void
|
||||
let share: () -> Void
|
||||
let setupProximityNotification: (Bool, MessageId?) -> Void
|
||||
let setupProximityNotification: (Bool, CLLocationCoordinate2D?, MessageId?) -> Void
|
||||
let updateSendActionHighlight: (Bool) -> Void
|
||||
let sendLiveLocation: (CLLocationCoordinate2D, Int32?) -> Void
|
||||
let stopLiveLocation: () -> 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.updateMapMode = updateMapMode
|
||||
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)
|
||||
}
|
||||
}, setupProximityNotification: { [weak self] reset, messageId in
|
||||
}, setupProximityNotification: { [weak self] reset, coordinate, messageId in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -204,8 +204,10 @@ public final class LocationViewController: ViewController {
|
||||
|
||||
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
|
||||
if let strongSelf = self {
|
||||
@ -243,21 +245,18 @@ public final class LocationViewController: ViewController {
|
||||
controller?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
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
|
||||
controller?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
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
|
||||
controller?.dismissAnimated()
|
||||
if let strongSelf = self {
|
||||
params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 8 * 60 * 60))
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
})
|
||||
]),
|
||||
@ -304,7 +303,7 @@ public final class LocationViewController: ViewController {
|
||||
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.controllerNode.updateState { state -> LocationViewState in
|
||||
|
@ -15,6 +15,7 @@ import AccountContext
|
||||
import AppBundle
|
||||
import CoreLocation
|
||||
import Geocoding
|
||||
import DeviceAccess
|
||||
|
||||
func getLocation(from message: Message) -> 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 var presentationData: PresentationData
|
||||
private let presentationDataPromise: Promise<PresentationData>
|
||||
private var subject: Message
|
||||
private let interaction: LocationViewInteraction
|
||||
private let locationManager: LocationManager
|
||||
|
||||
private let listNode: ListView
|
||||
let headerNode: LocationMapHeaderNode
|
||||
@ -212,12 +214,13 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
||||
private var validLayout: (layout: ContainerViewLayout, navigationHeight: 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.presentationData = presentationData
|
||||
self.presentationDataPromise = Promise(presentationData)
|
||||
self.subject = subject
|
||||
self.interaction = interaction
|
||||
self.locationManager = locationManager
|
||||
|
||||
self.state = LocationViewState()
|
||||
self.statePromise = Promise(self.state)
|
||||
@ -276,7 +279,10 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
||||
setupProximityNotificationImpl = { reset in
|
||||
let _ = (liveLocations
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { messages in
|
||||
|> deliverOnMainQueue).start(next: { [weak self] messages in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var ownMessageId: MessageId?
|
||||
for message in messages {
|
||||
if message.localTags.contains(.OutgoingLiveLocation) {
|
||||
@ -284,7 +290,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
||||
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)
|
||||
} else {
|
||||
title = presentationData.strings.Map_ShareLiveLocation
|
||||
subtitle = presentationData.strings.Map_ShareLiveLocation
|
||||
subtitle = presentationData.strings.Map_ShareLiveLocationHelp
|
||||
beginTime = nil
|
||||
timeout = nil
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -480,11 +500,20 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
self.locationManager.manager.startUpdatingHeading()
|
||||
self.locationManager.manager.delegate = self
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable?.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) {
|
||||
|
@ -16,7 +16,6 @@ static_library(
|
||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||
"//submodules/LiveLocationTimerNode:LiveLocationTimerNode",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/LegacyComponents:LegacyComponents",
|
||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/Markdown:Markdown",
|
||||
|
@ -17,7 +17,6 @@ swift_library(
|
||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||
"//submodules/LiveLocationTimerNode:LiveLocationTimerNode",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/LegacyComponents:LegacyComponents",
|
||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/Markdown:Markdown",
|
||||
|
@ -10,6 +10,7 @@ import TelegramUIPreferences
|
||||
import TextFormat
|
||||
import Markdown
|
||||
import LocalizedPeerData
|
||||
import LiveLocationTimerNode
|
||||
|
||||
private let titleFont = Font.regular(12.0)
|
||||
private let subtitleFont = Font.regular(10.0)
|
||||
@ -31,7 +32,7 @@ final class LocationBroadcastNavigationAccessoryPanel: ASDisplayNode {
|
||||
private let contentNode: ASDisplayNode
|
||||
|
||||
private let iconNode: ASImageNode
|
||||
private let wavesNode: LocationBroadcastPanelWavesNode
|
||||
private let wavesNode: LiveLocationWavesNode
|
||||
private let titleNode: TextNode
|
||||
private let subtitleNode: TextNode
|
||||
private let closeButton: HighlightableButtonNode
|
||||
@ -58,7 +59,7 @@ final class LocationBroadcastNavigationAccessoryPanel: ASDisplayNode {
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
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.isUserInteractionEnabled = false
|
||||
|
@ -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)
|
||||
return (mediaMap, nil)
|
||||
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)
|
||||
case let .messageMediaDocument(_, document, ttlSeconds):
|
||||
if let document = document {
|
||||
|
@ -10,7 +10,7 @@ func telegramMediaMapFromApiGeoPoint(_ geo: Api.GeoPoint, title: String?, addres
|
||||
venue = MapVenue(title: title, address: address, provider: provider, id: venueId, type: venueType)
|
||||
}
|
||||
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)
|
||||
case .geoPointEmpty:
|
||||
return TelegramMediaMap(latitude: 0.0, longitude: 0.0, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout)
|
||||
|
@ -184,9 +184,12 @@ public final class PrincipalThemeEssentialGraphics {
|
||||
public let incomingBubbleGradientImage: UIImage?
|
||||
public let outgoingBubbleGradientImage: UIImage?
|
||||
|
||||
public let hasWallpaper: Bool
|
||||
|
||||
init(mediaBox: MediaBox, presentationTheme: PresentationTheme, wallpaper initialWallpaper: TelegramWallpaper, preview: Bool = false, knockoutMode: Bool, bubbleCorners: PresentationChatBubbleCorners) {
|
||||
let theme = presentationTheme.chat
|
||||
var wallpaper = initialWallpaper
|
||||
self.hasWallpaper = !wallpaper.isEmpty
|
||||
|
||||
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
|
||||
|
@ -250,7 +250,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
|
||||
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?.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?) {
|
||||
self.currentParams = (size, contentOrigin, presentationData, graphics, backgroundType, messageSelection)
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
@ -277,17 +280,20 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
if let _ = self.selectionBackgroundNode {
|
||||
} else {
|
||||
let selectionBackgroundNode = ASDisplayNode()
|
||||
self.sourceNode.contentNode.insertSubnode(selectionBackgroundNode, at: 0)
|
||||
self.containerNode.insertSubnode(selectionBackgroundNode, at: 0)
|
||||
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 {
|
||||
selectionBackgroundFrame.origin.y -= 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
|
||||
} else if let selectionBackgroundNode = self.selectionBackgroundNode {
|
||||
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)
|
||||
|
||||
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)
|
||||
for (contentNodeMessage, contentNodeClass, attributes, bubbleAttributes) in contentNodeMessagesAndClasses {
|
||||
@ -1120,7 +1126,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
if addedContentNodes == nil {
|
||||
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))
|
||||
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 {
|
||||
case .never:
|
||||
@ -1616,7 +1622,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
|
||||
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 {
|
||||
let mosaicIndex = i - mosaicRange.lowerBound
|
||||
@ -1769,7 +1775,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
|
||||
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 currentContainerGroupId: UInt32?
|
||||
var currentItemSelection: Bool?
|
||||
@ -1797,7 +1803,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
|
||||
let (_, apply) = finalize(maxContentWidth)
|
||||
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 {
|
||||
contentNodesHeight += size.height
|
||||
@ -1807,8 +1813,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
} else {
|
||||
if i == 0 && !headerSize.height.isZero {
|
||||
contentNodesHeight += properties.headerSpacing
|
||||
totalContentNodesHeight += contentNodesHeight
|
||||
if contentGroupId == nil {
|
||||
contentNodesHeight += properties.headerSpacing
|
||||
}
|
||||
totalContentNodesHeight += properties.headerSpacing
|
||||
}
|
||||
|
||||
if currentContainerGroupId != contentGroupId {
|
||||
@ -1816,17 +1824,22 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
var overlapOffset: CGFloat = 0.0
|
||||
if !contentContainerNodeFrames.isEmpty {
|
||||
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
|
||||
}
|
||||
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
|
||||
currentItemSelection = itemSelection
|
||||
}
|
||||
|
||||
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
|
||||
totalContentNodesHeight += size.height
|
||||
@ -1838,7 +1851,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
if !contentContainerNodeFrames.isEmpty {
|
||||
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 {
|
||||
totalContentNodesHeight -= smallContainerGroupOverlap
|
||||
}
|
||||
@ -2007,9 +2020,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
replyInfoSizeApply: (CGSize, () -> ChatMessageReplyInfoNode?),
|
||||
replyInfoOriginY: CGFloat,
|
||||
removedContentNodeIndices: [Int]?,
|
||||
addedContentNodes: [(Message, ChatMessageBubbleContentNode)]?,
|
||||
addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode)]?,
|
||||
contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)],
|
||||
contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, (ListViewItemUpdateAnimation, Bool) -> Void)],
|
||||
contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool) -> Void)],
|
||||
contentContainerNodeFrames: [(UInt32, CGRect, Bool?)],
|
||||
mosaicStatusOrigin: CGPoint?,
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
container?.isExtractedToContextPreviewUpdated(isExtractedToContextPreview)
|
||||
|
||||
// if !isExtractedToContextPreview, let (rect, size) = strongSelf.absoluteRect {
|
||||
// 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))
|
||||
contentContainer?.sourceNode.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.backgroundColor = UIColor.red.withAlphaComponent(0.5)
|
||||
|
||||
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 {
|
||||
contentContainer?.sourceNode.layoutUpdated?(relativeFrame.size)
|
||||
}
|
||||
|
||||
contentContainer?.update(size: relativeFrame.size, contentOrigin: contentOrigin, index: index, presentationData: item.presentationData, graphics: graphics, backgroundType: backgroundType, messageSelection: itemSelection)
|
||||
|
||||
print("container \(relativeFrame)")
|
||||
|
||||
|
||||
index += 1
|
||||
}
|
||||
|
||||
@ -2359,12 +2370,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
|
||||
if let addedContentNodes = addedContentNodes {
|
||||
for (contentNodeMessage, contentNode) in addedContentNodes {
|
||||
for (contentNodeMessage, isAttachent, contentNode) in addedContentNodes {
|
||||
updatedContentNodes.append(contentNode)
|
||||
|
||||
let contextSourceNode: ContextExtractedContentContainingNode = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == contentNodeMessage.stableId })?.sourceNode ?? strongSelf.mainContextSourceNode
|
||||
|
||||
print(contextSourceNode.debugDescription)
|
||||
let contextSourceNode: ContextExtractedContentContainingNode
|
||||
if isAttachent {
|
||||
contextSourceNode = strongSelf.mainContextSourceNode
|
||||
} else {
|
||||
contextSourceNode = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == contentNodeMessage.stableId })?.sourceNode ?? strongSelf.mainContextSourceNode
|
||||
}
|
||||
contextSourceNode.contentNode.addSubnode(contentNode)
|
||||
|
||||
contentNode.visibility = strongSelf.visibility
|
||||
@ -2378,7 +2392,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
var sortedContentNodes: [ChatMessageBubbleContentNode] = []
|
||||
outer: for (message, nodeClass, _, _) in contentNodeMessagesAndClasses {
|
||||
if let addedContentNodes = addedContentNodes {
|
||||
for (contentNodeMessage, contentNode) in addedContentNodes {
|
||||
for (contentNodeMessage, _, contentNode) in addedContentNodes {
|
||||
if type(of: contentNode) == nodeClass && contentNodeMessage.stableId == message.stableId {
|
||||
sortedContentNodes.append(contentNode)
|
||||
continue outer
|
||||
@ -2399,7 +2413,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
|
||||
var contentNodeIndex = 0
|
||||
for (relativeFrame, _, apply) in contentNodeFramesPropertiesAndApply {
|
||||
for (relativeFrame, _, useContentOrigin, apply) in contentNodeFramesPropertiesAndApply {
|
||||
apply(animation, synchronousLoads)
|
||||
|
||||
if contentNodeIndex >= strongSelf.contentNodes.count {
|
||||
@ -2407,17 +2421,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
}
|
||||
|
||||
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
|
||||
contentNode.frame = contentNodeFrame
|
||||
|
||||
print("frame \(contentNodeFrame.debugDescription)")
|
||||
|
||||
|
||||
if case let .System(duration) = animation {
|
||||
var animateFrame = false
|
||||
var animateAlpha = false
|
||||
if let addedContentNodes = addedContentNodes {
|
||||
if !addedContentNodes.contains(where: { $0.1 === contentNode }) {
|
||||
if !addedContentNodes.contains(where: { $0.2 === contentNode }) {
|
||||
animateFrame = true
|
||||
} else {
|
||||
animateAlpha = true
|
||||
@ -2647,8 +2659,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
if let replyInfoNode = self.replyInfoNode {
|
||||
node.addSubnode(replyInfoNode)
|
||||
}
|
||||
for contentNode in self.contentNodes {
|
||||
node.addSubnode(contentNode)
|
||||
if !self.contentContainers.isEmpty {
|
||||
node.addSubnode(self.contentContainersWrapperNode)
|
||||
} else {
|
||||
for contentNode in self.contentNodes {
|
||||
node.addSubnode(contentNode)
|
||||
}
|
||||
}
|
||||
self.mainContextSourceNode.contentNode.addSubnode(node)
|
||||
self.transitionClippingNode = node
|
||||
@ -2663,8 +2679,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
||||
if let replyInfoNode = self.replyInfoNode {
|
||||
self.mainContextSourceNode.contentNode.addSubnode(replyInfoNode)
|
||||
}
|
||||
for contentNode in self.contentNodes {
|
||||
self.mainContextSourceNode.contentNode.addSubnode(contentNode)
|
||||
if !self.contentContainers.isEmpty {
|
||||
self.mainContextSourceNode.contentNode.addSubnode(self.contentContainersWrapperNode)
|
||||
} else {
|
||||
for contentNode in self.contentNodes {
|
||||
self.mainContextSourceNode.contentNode.addSubnode(contentNode)
|
||||
}
|
||||
}
|
||||
transitionClippingNode.removeFromSupernode()
|
||||
self.transitionClippingNode = nil
|
||||
|
@ -145,7 +145,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var mode: ChatMessageLiveLocationPositionNode.Mode = .location(selectedMedia)
|
||||
if let selectedMedia = selectedMedia, let peer = item.message.author {
|
||||
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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user