From 05f3e47623d244a8359cfbed938b92c61e627008 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 19 Oct 2020 07:55:13 +0400 Subject: [PATCH] Various fixes --- .../ChatMessageLiveLocationPositionNode.swift | 90 +++++++++++++++- .../Sources/LiveLocationWavesNode.swift} | 92 ++++++++++------ .../Sources/LocationActionListItem.swift | 15 ++- .../Sources/LocationAnnotation.swift | 18 +++- .../LocationUI/Sources/LocationMapNode.swift | 33 ++++-- .../Sources/LocationViewController.swift | 17 ++- .../Sources/LocationViewControllerNode.swift | 43 ++++++-- submodules/TelegramBaseController/BUCK | 1 - submodules/TelegramBaseController/BUILD | 1 - ...ionBroadcastNavigationAccessoryPanel.swift | 5 +- .../Sources/StoreMessage_Telegram.swift | 2 +- .../Sources/TelegramMediaMap.swift | 2 +- .../PresentationThemeEssentialGraphics.swift | 3 + .../Sources/ChatMessageBubbleItemNode.swift | 100 +++++++++++------- .../ChatMessageMapBubbleContentNode.swift | 2 +- 15 files changed, 315 insertions(+), 109 deletions(-) rename submodules/{TelegramBaseController/Sources/LocationBroadcastPanelWavesNode.swift => LiveLocationTimerNode/Sources/LiveLocationWavesNode.swift} (54%) diff --git a/submodules/LiveLocationPositionNode/Sources/ChatMessageLiveLocationPositionNode.swift b/submodules/LiveLocationPositionNode/Sources/ChatMessageLiveLocationPositionNode.swift index ce9b4ead41..0e37146b6a 100644 --- a/submodules/LiveLocationPositionNode/Sources/ChatMessageLiveLocationPositionNode.swift +++ b/submodules/LiveLocationPositionNode/Sources/ChatMessageLiveLocationPositionNode.swift @@ -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) } }) } diff --git a/submodules/TelegramBaseController/Sources/LocationBroadcastPanelWavesNode.swift b/submodules/LiveLocationTimerNode/Sources/LiveLocationWavesNode.swift similarity index 54% rename from submodules/TelegramBaseController/Sources/LocationBroadcastPanelWavesNode.swift rename to submodules/LiveLocationTimerNode/Sources/LiveLocationWavesNode.swift index 5c19d1782d..43390801fd 100644 --- a/submodules/TelegramBaseController/Sources/LocationBroadcastPanelWavesNode.swift +++ b/submodules/LiveLocationTimerNode/Sources/LiveLocationWavesNode.swift @@ -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 diff --git a/submodules/LocationUI/Sources/LocationActionListItem.swift b/submodules/LocationUI/Sources/LocationActionListItem.swift index 80786642ab..8f07e26896 100644 --- a/submodules/LocationUI/Sources/LocationActionListItem.swift +++ b/submodules/LocationUI/Sources/LocationActionListItem.swift @@ -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)) diff --git a/submodules/LocationUI/Sources/LocationAnnotation.swift b/submodules/LocationUI/Sources/LocationAnnotation.swift index a26b03d7e5..a366a8bc42 100644 --- a/submodules/LocationUI/Sources/LocationAnnotation.swift +++ b/submodules/LocationUI/Sources/LocationAnnotation.swift @@ -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? = "" diff --git a/submodules/LocationUI/Sources/LocationMapNode.swift b/submodules/LocationUI/Sources/LocationMapNode.swift index 9506b379ad..647abfa9bf 100644 --- a/submodules/LocationUI/Sources/LocationMapNode.swift +++ b/submodules/LocationUI/Sources/LocationMapNode.swift @@ -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 + } + } + } } } diff --git a/submodules/LocationUI/Sources/LocationViewController.swift b/submodules/LocationUI/Sources/LocationViewController.swift index cd2f52af2f..c99d1036c7 100644 --- a/submodules/LocationUI/Sources/LocationViewController.swift +++ b/submodules/LocationUI/Sources/LocationViewController.swift @@ -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 diff --git a/submodules/LocationUI/Sources/LocationViewControllerNode.swift b/submodules/LocationUI/Sources/LocationViewControllerNode.swift index bfbd7230c4..9f934486be 100644 --- a/submodules/LocationUI/Sources/LocationViewControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationViewControllerNode.swift @@ -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 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) { diff --git a/submodules/TelegramBaseController/BUCK b/submodules/TelegramBaseController/BUCK index 3da1da6331..330f4ac86a 100644 --- a/submodules/TelegramBaseController/BUCK +++ b/submodules/TelegramBaseController/BUCK @@ -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", diff --git a/submodules/TelegramBaseController/BUILD b/submodules/TelegramBaseController/BUILD index db407be8c9..9d65208c11 100644 --- a/submodules/TelegramBaseController/BUILD +++ b/submodules/TelegramBaseController/BUILD @@ -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", diff --git a/submodules/TelegramBaseController/Sources/LocationBroadcastNavigationAccessoryPanel.swift b/submodules/TelegramBaseController/Sources/LocationBroadcastNavigationAccessoryPanel.swift index 59430f3883..fa59e85683 100644 --- a/submodules/TelegramBaseController/Sources/LocationBroadcastNavigationAccessoryPanel.swift +++ b/submodules/TelegramBaseController/Sources/LocationBroadcastNavigationAccessoryPanel.swift @@ -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 diff --git a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift index 099609b9ee..bb818fe39b 100644 --- a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift @@ -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 { diff --git a/submodules/TelegramCore/Sources/TelegramMediaMap.swift b/submodules/TelegramCore/Sources/TelegramMediaMap.swift index df3b7c0648..51443d8a41 100644 --- a/submodules/TelegramCore/Sources/TelegramMediaMap.swift +++ b/submodules/TelegramCore/Sources/TelegramMediaMap.swift @@ -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) diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift index 6e511d83a6..52a20db328 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index cfe9d231d2..d4684b7ce7 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift index eedf4a2ff7..b61aca408e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift @@ -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)