mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 06:10:03 +00:00
Various fixes
This commit is contained in:
parent
43517068e8
commit
05f3e47623
@ -10,6 +10,7 @@ import AvatarNode
|
|||||||
import LocationResources
|
import LocationResources
|
||||||
import AppBundle
|
import AppBundle
|
||||||
import AccountContext
|
import AccountContext
|
||||||
|
import CoreLocation
|
||||||
|
|
||||||
private let avatarFont = avatarPlaceholderFont(size: 24.0)
|
private let avatarFont = avatarPlaceholderFont(size: 24.0)
|
||||||
private let avatarBackgroundImage = UIImage(bundleImageName: "Chat/Message/LocationPin")?.precomposed()
|
private let avatarBackgroundImage = UIImage(bundleImageName: "Chat/Message/LocationPin")?.precomposed()
|
||||||
@ -50,9 +51,34 @@ private func chatBubbleMapPinImage(_ theme: PresentationTheme, color: UIColor) -
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let arrowImageSize = CGSize(width: 70.0, height: 70.0)
|
||||||
|
private func generateHeadingArrowImage() -> UIImage? {
|
||||||
|
return generateImage(arrowImageSize, contextGenerator: { size, context in
|
||||||
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
|
context.clear(bounds)
|
||||||
|
|
||||||
|
context.saveGState()
|
||||||
|
let center = CGPoint(x: arrowImageSize.width / 2.0, y: arrowImageSize.height / 2.0)
|
||||||
|
context.move(to: center)
|
||||||
|
context.addArc(center: center, radius: arrowImageSize.width / 2.0, startAngle: CGFloat.pi / 2.0 + CGFloat.pi / 8.0, endAngle: CGFloat.pi / 2.0 - CGFloat.pi / 8.0, clockwise: true)
|
||||||
|
context.clip()
|
||||||
|
|
||||||
|
var locations: [CGFloat] = [0.0, 0.4, 1.0]
|
||||||
|
let colors: [CGColor] = [UIColor(rgb: 0x007aff, alpha: 0.5).cgColor, UIColor(rgb: 0x007aff, alpha: 0.3).cgColor, UIColor(rgb: 0x007aff, alpha: 0.0).cgColor]
|
||||||
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
|
context.drawRadialGradient(gradient, startCenter: center, startRadius: 5.0, endCenter: center, endRadius: arrowImageSize.width / 2.0, options: .drawsAfterEndLocation)
|
||||||
|
|
||||||
|
context.restoreGState()
|
||||||
|
context.setBlendMode(.clear)
|
||||||
|
context.fillEllipse(in: CGRect(x: (arrowImageSize.width - 10.0) / 2.0, y: (arrowImageSize.height - 10.0) / 2.0, width: 10.0, height: 10.0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
||||||
public enum Mode {
|
public enum Mode {
|
||||||
case liveLocation(Peer, Bool)
|
case liveLocation(peer: Peer, active: Bool, latitude: Double, longitude: Double, heading: Double?)
|
||||||
case location(TelegramMediaMap?)
|
case location(TelegramMediaMap?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,9 +86,12 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
|||||||
private let iconNode: TransformImageNode
|
private let iconNode: TransformImageNode
|
||||||
private let avatarNode: AvatarNode
|
private let avatarNode: AvatarNode
|
||||||
private let pulseNode: ASImageNode
|
private let pulseNode: ASImageNode
|
||||||
|
private let arrowNode: ASImageNode
|
||||||
|
|
||||||
private var pulseImage: UIImage?
|
private var pulseImage: UIImage?
|
||||||
|
private var arrowImage: UIImage?
|
||||||
private var venueType: String?
|
private var venueType: String?
|
||||||
|
private var coordinate: (Double, Double)?
|
||||||
|
|
||||||
override public init() {
|
override public init() {
|
||||||
let isLayerBacked = !smartInvertColorsEnabled()
|
let isLayerBacked = !smartInvertColorsEnabled()
|
||||||
@ -84,11 +113,19 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
|||||||
self.pulseNode.displayWithoutProcessing = true
|
self.pulseNode.displayWithoutProcessing = true
|
||||||
self.pulseNode.isHidden = true
|
self.pulseNode.isHidden = true
|
||||||
|
|
||||||
|
self.arrowNode = ASImageNode()
|
||||||
|
self.arrowNode.frame = CGRect(origin: CGPoint(), size: arrowImageSize)
|
||||||
|
self.arrowNode.isLayerBacked = true
|
||||||
|
self.arrowNode.displaysAsynchronously = false
|
||||||
|
self.arrowNode.displayWithoutProcessing = true
|
||||||
|
self.arrowNode.isHidden = true
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.isLayerBacked = isLayerBacked
|
self.isLayerBacked = isLayerBacked
|
||||||
|
|
||||||
self.addSubnode(self.pulseNode)
|
self.addSubnode(self.pulseNode)
|
||||||
|
self.addSubnode(self.arrowNode)
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubnode(self.backgroundNode)
|
||||||
self.addSubnode(self.iconNode)
|
self.addSubnode(self.iconNode)
|
||||||
self.addSubnode(self.avatarNode)
|
self.addSubnode(self.avatarNode)
|
||||||
@ -98,17 +135,24 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
|||||||
let iconLayout = self.iconNode.asyncLayout()
|
let iconLayout = self.iconNode.asyncLayout()
|
||||||
|
|
||||||
let currentPulseImage = self.pulseImage
|
let currentPulseImage = self.pulseImage
|
||||||
|
let currentArrowImage = self.arrowImage
|
||||||
let currentVenueType = self.venueType
|
let currentVenueType = self.venueType
|
||||||
|
|
||||||
|
let currentCoordinate = self.coordinate
|
||||||
|
|
||||||
return { [weak self] context, theme, mode in
|
return { [weak self] context, theme, mode in
|
||||||
var updatedVenueType: String?
|
var updatedVenueType: String?
|
||||||
|
|
||||||
let backgroundImage: UIImage?
|
let backgroundImage: UIImage?
|
||||||
var hasPulse = false
|
var hasPulse = false
|
||||||
|
var heading: Double?
|
||||||
|
var coordinate: (Double, Double)?
|
||||||
switch mode {
|
switch mode {
|
||||||
case let .liveLocation(_, active):
|
case let .liveLocation(_, active, latitude, longitude, headingValue):
|
||||||
backgroundImage = avatarBackgroundImage
|
backgroundImage = avatarBackgroundImage
|
||||||
hasPulse = active
|
hasPulse = active
|
||||||
|
coordinate = (latitude, longitude)
|
||||||
|
heading = headingValue
|
||||||
case let .location(location):
|
case let .location(location):
|
||||||
let venueType = location?.venue?.type ?? ""
|
let venueType = location?.venue?.type ?? ""
|
||||||
let color = venueType.isEmpty ? theme.list.itemAccentColor : venueIconColor(type: venueType)
|
let color = venueType.isEmpty ? theme.list.itemAccentColor : venueIconColor(type: venueType)
|
||||||
@ -118,27 +162,57 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func degToRad(_ degrees: Double) -> Double {
|
||||||
|
return degrees * Double.pi / 180.0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if heading == nil, let currentCoordinate = currentCoordinate, let coordinate = coordinate {
|
||||||
|
let lat1 = degToRad(currentCoordinate.0)
|
||||||
|
let lon1 = degToRad(currentCoordinate.1)
|
||||||
|
let lat2 = degToRad(coordinate.0)
|
||||||
|
let lon2 = degToRad(coordinate.1)
|
||||||
|
|
||||||
|
let dLat = lat2 - lat1
|
||||||
|
let dLon = lon2 - lon1
|
||||||
|
|
||||||
|
if dLat != 0 && dLon != 0 {
|
||||||
|
let y = sin(dLon) * cos(lat2)
|
||||||
|
let x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon)
|
||||||
|
heading = atan2(y, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let pulseImage: UIImage?
|
let pulseImage: UIImage?
|
||||||
|
let arrowImage: UIImage?
|
||||||
if hasPulse {
|
if hasPulse {
|
||||||
pulseImage = currentPulseImage ?? generateFilledCircleImage(diameter: 120.0, color: UIColor(rgb: 0x007aff, alpha: 0.27))
|
pulseImage = currentPulseImage ?? generateFilledCircleImage(diameter: 120.0, color: UIColor(rgb: 0x007aff, alpha: 0.27))
|
||||||
} else {
|
} else {
|
||||||
pulseImage = nil
|
pulseImage = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let _ = heading {
|
||||||
|
arrowImage = currentArrowImage ?? generateHeadingArrowImage()
|
||||||
|
} else {
|
||||||
|
arrowImage = nil
|
||||||
|
}
|
||||||
|
|
||||||
return (CGSize(width: 62.0, height: 74.0), {
|
return (CGSize(width: 62.0, height: 74.0), {
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
|
strongSelf.coordinate = coordinate
|
||||||
|
|
||||||
if strongSelf.backgroundNode.image !== backgroundImage {
|
if strongSelf.backgroundNode.image !== backgroundImage {
|
||||||
strongSelf.backgroundNode.image = backgroundImage
|
strongSelf.backgroundNode.image = backgroundImage
|
||||||
}
|
}
|
||||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 74.0))
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 62.0, height: 74.0))
|
||||||
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 9.0), size: CGSize(width: 42.0, height: 42.0))
|
strongSelf.avatarNode.frame = CGRect(origin: CGPoint(x: 10.0, y: 9.0), size: CGSize(width: 42.0, height: 42.0))
|
||||||
switch mode {
|
switch mode {
|
||||||
case let .liveLocation(peer, active):
|
case let .liveLocation(peer, active, _, _, _):
|
||||||
strongSelf.avatarNode.setPeer(context: context, theme: theme, peer: peer)
|
strongSelf.avatarNode.setPeer(context: context, theme: theme, peer: peer)
|
||||||
strongSelf.avatarNode.isHidden = false
|
strongSelf.avatarNode.isHidden = false
|
||||||
strongSelf.iconNode.isHidden = true
|
strongSelf.iconNode.isHidden = true
|
||||||
strongSelf.avatarNode.alpha = active ? 1.0 : 0.6
|
strongSelf.avatarNode.alpha = active ? 1.0 : 0.6
|
||||||
case let .location(location):
|
case .location:
|
||||||
strongSelf.iconNode.isHidden = false
|
strongSelf.iconNode.isHidden = false
|
||||||
strongSelf.avatarNode.isHidden = true
|
strongSelf.avatarNode.isHidden = true
|
||||||
}
|
}
|
||||||
@ -147,7 +221,6 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
|||||||
strongSelf.venueType = updatedVenueType
|
strongSelf.venueType = updatedVenueType
|
||||||
strongSelf.iconNode.setSignal(venueIcon(postbox: context.account.postbox, type: updatedVenueType, background: false))
|
strongSelf.iconNode.setSignal(venueIcon(postbox: context.account.postbox, type: updatedVenueType, background: false))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let arguments = VenueIconArguments(defaultForegroundColor: theme.chat.inputPanel.actionControlForegroundColor)
|
let arguments = VenueIconArguments(defaultForegroundColor: theme.chat.inputPanel.actionControlForegroundColor)
|
||||||
let iconSize = CGSize(width: 44.0, height: 44.0)
|
let iconSize = CGSize(width: 44.0, height: 44.0)
|
||||||
@ -170,6 +243,13 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode {
|
|||||||
strongSelf.pulseNode.isHidden = true
|
strongSelf.pulseNode.isHidden = true
|
||||||
removePulseAnimations(layer: strongSelf.pulseNode.layer)
|
removePulseAnimations(layer: strongSelf.pulseNode.layer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strongSelf.arrowImage = arrowImage
|
||||||
|
strongSelf.arrowNode.image = arrowImage
|
||||||
|
strongSelf.arrowNode.isHidden = heading == nil || !hasPulse
|
||||||
|
strongSelf.arrowNode.position = CGPoint(x: 31.0, y: 64.0)
|
||||||
|
|
||||||
|
strongSelf.arrowNode.transform = CATransform3DMakeRotation(CGFloat(heading ?? 0.0 / 180.0 * Double.pi), 0.0, 0.0, 1.0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,7 @@ import UIKit
|
|||||||
import Display
|
import Display
|
||||||
import AsyncDisplayKit
|
import AsyncDisplayKit
|
||||||
|
|
||||||
import LegacyComponents
|
private final class LiveLocationWavesNodeParams: NSObject {
|
||||||
|
|
||||||
private final class LocationBroadcastPanelWavesNodeParams: NSObject {
|
|
||||||
let color: UIColor
|
let color: UIColor
|
||||||
let progress: CGFloat
|
let progress: CGFloat
|
||||||
|
|
||||||
@ -21,8 +19,8 @@ private func degToRad(_ degrees: CGFloat) -> CGFloat {
|
|||||||
return degrees * CGFloat.pi / 180.0
|
return degrees * CGFloat.pi / 180.0
|
||||||
}
|
}
|
||||||
|
|
||||||
final class LocationBroadcastPanelWavesNode: ASDisplayNode {
|
public final class LiveLocationWavesNode: ASDisplayNode {
|
||||||
var color: UIColor {
|
public var color: UIColor {
|
||||||
didSet {
|
didSet {
|
||||||
self.setNeedsDisplay()
|
self.setNeedsDisplay()
|
||||||
}
|
}
|
||||||
@ -34,7 +32,9 @@ final class LocationBroadcastPanelWavesNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(color: UIColor) {
|
var animator: ConstantDisplayLinkAnimator?
|
||||||
|
|
||||||
|
public init(color: UIColor) {
|
||||||
self.color = color
|
self.color = color
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
@ -43,42 +43,72 @@ final class LocationBroadcastPanelWavesNode: ASDisplayNode {
|
|||||||
self.isOpaque = false
|
self.isOpaque = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override func willEnterHierarchy() {
|
deinit {
|
||||||
|
self.animator?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var previousAnimationStart: Double?
|
||||||
|
private func updateAnimations(inHierarchy: Bool) {
|
||||||
|
let timestamp = CACurrentMediaTime()
|
||||||
|
|
||||||
|
let animating: Bool
|
||||||
|
|
||||||
|
if inHierarchy {
|
||||||
|
animating = true
|
||||||
|
|
||||||
|
let animator: ConstantDisplayLinkAnimator
|
||||||
|
if let current = self.animator {
|
||||||
|
animator = current
|
||||||
|
} else {
|
||||||
|
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
||||||
|
self?.updateAnimations(inHierarchy: true)
|
||||||
|
})
|
||||||
|
self.animator = animator
|
||||||
|
}
|
||||||
|
animator.isPaused = false
|
||||||
|
} else {
|
||||||
|
animating = false
|
||||||
|
self.animator?.isPaused = true
|
||||||
|
self.previousAnimationStart = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if animating {
|
||||||
|
let animationDuration: Double = 2.5
|
||||||
|
if var startTimestamp = self.previousAnimationStart {
|
||||||
|
if timestamp > startTimestamp + animationDuration {
|
||||||
|
while timestamp > startTimestamp + animationDuration {
|
||||||
|
startTimestamp += animationDuration
|
||||||
|
}
|
||||||
|
startTimestamp -= animationDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
let t = min(1.0, max(0.0, (timestamp - startTimestamp) / animationDuration))
|
||||||
|
self.effectiveProgress = CGFloat(t)
|
||||||
|
} else {
|
||||||
|
self.previousAnimationStart = timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override func willEnterHierarchy() {
|
||||||
super.willEnterHierarchy()
|
super.willEnterHierarchy()
|
||||||
|
|
||||||
self.pop_removeAnimation(forKey: "indefiniteProgress")
|
self.updateAnimations(inHierarchy: true)
|
||||||
|
|
||||||
let animation = POPBasicAnimation()
|
|
||||||
animation.property = (POPAnimatableProperty.property(withName: "progress", initializer: { property in
|
|
||||||
property?.readBlock = { node, values in
|
|
||||||
values?.pointee = (node as! LocationBroadcastPanelWavesNode).effectiveProgress
|
|
||||||
}
|
|
||||||
property?.writeBlock = { node, values in
|
|
||||||
(node as! LocationBroadcastPanelWavesNode).effectiveProgress = values!.pointee
|
|
||||||
}
|
|
||||||
property?.threshold = 0.01
|
|
||||||
}) as! POPAnimatableProperty)
|
|
||||||
animation.fromValue = CGFloat(0.0) as NSNumber
|
|
||||||
animation.toValue = CGFloat(1.0) as NSNumber
|
|
||||||
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
|
|
||||||
animation.duration = 2.5
|
|
||||||
animation.repeatForever = true
|
|
||||||
self.pop_add(animation, forKey: "indefiniteProgress")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didExitHierarchy() {
|
public override func didExitHierarchy() {
|
||||||
super.didExitHierarchy()
|
super.didExitHierarchy()
|
||||||
|
|
||||||
self.pop_removeAnimation(forKey: "indefiniteProgress")
|
self.updateAnimations(inHierarchy: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
public override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
|
||||||
let t = CACurrentMediaTime()
|
let t = CACurrentMediaTime()
|
||||||
let value: CGFloat = CGFloat(t.truncatingRemainder(dividingBy: 2.0)) / 2.0
|
let value: CGFloat = CGFloat(t.truncatingRemainder(dividingBy: 2.0)) / 2.0
|
||||||
return LocationBroadcastPanelWavesNodeParams(color: self.color, progress: value)
|
return LiveLocationWavesNodeParams(color: self.color, progress: value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
@objc public override class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) {
|
||||||
let context = UIGraphicsGetCurrentContext()!
|
let context = UIGraphicsGetCurrentContext()!
|
||||||
|
|
||||||
if !isRasterizing {
|
if !isRasterizing {
|
||||||
@ -87,7 +117,7 @@ final class LocationBroadcastPanelWavesNode: ASDisplayNode {
|
|||||||
context.fill(bounds)
|
context.fill(bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let parameters = parameters as? LocationBroadcastPanelWavesNodeParams {
|
if let parameters = parameters as? LiveLocationWavesNodeParams {
|
||||||
let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
|
let center = CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0)
|
||||||
let length: CGFloat = 9.0
|
let length: CGFloat = 9.0
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ private func generateLiveLocationIcon(theme: PresentationTheme, stop: Bool) -> U
|
|||||||
context.scaleBy(x: 1.0, y: -1.0)
|
context.scaleBy(x: 1.0, y: -1.0)
|
||||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||||
|
|
||||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Location/SendLocationIcon"), color: .white) {
|
if let image = generateTintedImage(image: UIImage(bundleImageName: "Location/SendLocationIcon"), color: theme.chat.inputPanel.actionControlForegroundColor) {
|
||||||
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size))
|
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size))
|
||||||
}
|
}
|
||||||
})!
|
})!
|
||||||
@ -148,6 +148,7 @@ final class LocationActionListItemNode: ListViewItemNode {
|
|||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
private let venueIconNode: TransformImageNode
|
private let venueIconNode: TransformImageNode
|
||||||
private var timerNode: ChatMessageLiveLocationTimerNode?
|
private var timerNode: ChatMessageLiveLocationTimerNode?
|
||||||
|
private var wavesNode: LiveLocationWavesNode?
|
||||||
|
|
||||||
private var item: LocationActionListItem?
|
private var item: LocationActionListItem?
|
||||||
private var layoutParams: ListViewItemLayoutParams?
|
private var layoutParams: ListViewItemLayoutParams?
|
||||||
@ -278,6 +279,16 @@ final class LocationActionListItemNode: ListViewItemNode {
|
|||||||
strongSelf.venueIconNode.isHidden = false
|
strongSelf.venueIconNode.isHidden = false
|
||||||
strongSelf.venueIconNode.setSignal(venueIcon(postbox: item.account.postbox, type: venue.venue?.type ?? "", background: true))
|
strongSelf.venueIconNode.setSignal(venueIcon(postbox: item.account.postbox, type: venue.venue?.type ?? "", background: true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if updatedIcon == .stopLiveLocation {
|
||||||
|
let wavesNode = LiveLocationWavesNode(color: item.presentationData.theme.chat.inputPanel.actionControlForegroundColor)
|
||||||
|
strongSelf.addSubnode(wavesNode)
|
||||||
|
strongSelf.wavesNode = wavesNode
|
||||||
|
} else if let wavesNode = strongSelf.wavesNode {
|
||||||
|
strongSelf.wavesNode = nil
|
||||||
|
wavesNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
strongSelf.wavesNode?.color = item.presentationData.theme.chat.inputPanel.actionControlForegroundColor
|
||||||
}
|
}
|
||||||
|
|
||||||
let iconApply = iconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: iconSize, height: iconSize), boundingSize: CGSize(width: iconSize, height: iconSize), intrinsicInsets: UIEdgeInsets()))
|
let iconApply = iconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: iconSize, height: iconSize), boundingSize: CGSize(width: iconSize, height: iconSize), intrinsicInsets: UIEdgeInsets()))
|
||||||
@ -308,6 +319,8 @@ final class LocationActionListItemNode: ListViewItemNode {
|
|||||||
strongSelf.iconNode.frame = iconNodeFrame
|
strongSelf.iconNode.frame = iconNodeFrame
|
||||||
strongSelf.venueIconNode.frame = iconNodeFrame
|
strongSelf.venueIconNode.frame = iconNodeFrame
|
||||||
|
|
||||||
|
strongSelf.wavesNode?.frame = CGRect(origin: CGPoint(x: params.leftInset + 11.0, y: floorToScreenPixels((contentSize.height - bottomInset - iconSize) / 2.0) - 4.0), size: CGSize(width: 48.0, height: 48.0))
|
||||||
|
|
||||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height))
|
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentSize.width, height: contentSize.height))
|
||||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset))
|
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top - topHighlightInset), size: CGSize(width: contentSize.width, height: contentSize.height + topHighlightInset))
|
||||||
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
|
strongSelf.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: nodeLayout.contentSize.height - separatorHeight), size: CGSize(width: nodeLayout.size.width, height: separatorHeight))
|
||||||
|
|||||||
@ -31,12 +31,26 @@ private func generateSmallBackgroundImage(color: UIColor) -> UIImage? {
|
|||||||
class LocationPinAnnotation: NSObject, MKAnnotation {
|
class LocationPinAnnotation: NSObject, MKAnnotation {
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let theme: PresentationTheme
|
let theme: PresentationTheme
|
||||||
var coordinate: CLLocationCoordinate2D
|
var coordinate: CLLocationCoordinate2D {
|
||||||
|
willSet {
|
||||||
|
self.willChangeValue(forKey: "coordinate")
|
||||||
|
}
|
||||||
|
didSet {
|
||||||
|
self.didChangeValue(forKey: "coordinate")
|
||||||
|
}
|
||||||
|
}
|
||||||
let location: TelegramMediaMap?
|
let location: TelegramMediaMap?
|
||||||
let peer: Peer?
|
let peer: Peer?
|
||||||
let message: Message?
|
let message: Message?
|
||||||
let forcedSelection: Bool
|
let forcedSelection: Bool
|
||||||
var heading: Int32?
|
var heading: Int32? {
|
||||||
|
willSet {
|
||||||
|
self.willChangeValue(forKey: "heading")
|
||||||
|
}
|
||||||
|
didSet {
|
||||||
|
self.didChangeValue(forKey: "heading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var selfPeer: Peer?
|
var selfPeer: Peer?
|
||||||
var title: String? = ""
|
var title: String? = ""
|
||||||
|
|||||||
@ -74,18 +74,20 @@ func generateHeadingArrowImage() -> UIImage? {
|
|||||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
context.clear(bounds)
|
context.clear(bounds)
|
||||||
|
|
||||||
|
context.saveGState()
|
||||||
let center = CGPoint(x: arrowImageSize.width / 2.0, y: arrowImageSize.height / 2.0)
|
let center = CGPoint(x: arrowImageSize.width / 2.0, y: arrowImageSize.height / 2.0)
|
||||||
context.move(to: center)
|
context.move(to: center)
|
||||||
context.addArc(center: center, radius: arrowImageSize.width / 2.0, startAngle: CGFloat.pi / 2.0 + CGFloat.pi / 8.0, endAngle: CGFloat.pi / 2.0 - CGFloat.pi / 8.0, clockwise: true)
|
context.addArc(center: center, radius: arrowImageSize.width / 2.0, startAngle: CGFloat.pi / 2.0 + CGFloat.pi / 8.0, endAngle: CGFloat.pi / 2.0 - CGFloat.pi / 8.0, clockwise: true)
|
||||||
context.clip()
|
context.clip()
|
||||||
|
|
||||||
var locations: [CGFloat] = [0.0, 0.4, 1.0]
|
var locations: [CGFloat] = [0.0, 0.4, 1.0]
|
||||||
let colors: [CGColor] = [UIColor(rgb: 0x007ee5, alpha: 0.5).cgColor, UIColor(rgb: 0x007ee5, alpha: 0.3).cgColor, UIColor(rgb: 0x007ee5, alpha: 0.0).cgColor]
|
let colors: [CGColor] = [UIColor(rgb: 0x007aff, alpha: 0.5).cgColor, UIColor(rgb: 0x007aff, alpha: 0.3).cgColor, UIColor(rgb: 0x007aff, alpha: 0.0).cgColor]
|
||||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||||
|
|
||||||
context.drawRadialGradient(gradient, startCenter: center, startRadius: 11.0, endCenter: center, endRadius: arrowImageSize.width / 2.0, options: .drawsAfterEndLocation)
|
context.drawRadialGradient(gradient, startCenter: center, startRadius: 11.0, endCenter: center, endRadius: arrowImageSize.width / 2.0, options: .drawsAfterEndLocation)
|
||||||
|
|
||||||
|
context.restoreGState()
|
||||||
context.setBlendMode(.clear)
|
context.setBlendMode(.clear)
|
||||||
context.fillEllipse(in: CGRect(x: (arrowImageSize.width - 22.0) / 2.0, y: (arrowImageSize.height - 22.0) / 2.0, width: 22.0, height: 22.0))
|
context.fillEllipse(in: CGRect(x: (arrowImageSize.width - 22.0) / 2.0, y: (arrowImageSize.height - 22.0) / 2.0, width: 22.0, height: 22.0))
|
||||||
})
|
})
|
||||||
@ -218,10 +220,10 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
} else {
|
} else {
|
||||||
indicatorOverlay = InvertedProximityCircle(center: location.coordinate, radius: activeProximityRadius)
|
indicatorOverlay = InvertedProximityCircle(center: location.coordinate, radius: activeProximityRadius)
|
||||||
self.mapView?.addOverlay(indicatorOverlay)
|
self.mapView?.addOverlay(indicatorOverlay)
|
||||||
|
self.indicatorOverlay = indicatorOverlay
|
||||||
indicatorOverlay.alpha = 1.0
|
indicatorOverlay.alpha = 1.0
|
||||||
self.updateAnimations()
|
self.updateAnimations()
|
||||||
}
|
}
|
||||||
self.indicatorOverlay = indicatorOverlay
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let indicatorOverlay = self.indicatorOverlay {
|
if let indicatorOverlay = self.indicatorOverlay {
|
||||||
@ -261,7 +263,6 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
animator = ConstantDisplayLinkAnimator(update: { [weak self] in
|
||||||
self?.updateAnimations()
|
self?.updateAnimations()
|
||||||
})
|
})
|
||||||
animator.frameInterval = 2
|
|
||||||
self.animator = animator
|
self.animator = animator
|
||||||
}
|
}
|
||||||
animator.isPaused = false
|
animator.isPaused = false
|
||||||
@ -269,7 +270,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
self.animator?.isPaused = true
|
self.animator?.isPaused = true
|
||||||
}
|
}
|
||||||
|
|
||||||
self.currentInvertedCircleRenderer?.setNeedsDisplay()
|
self.currentInvertedCircleRenderer?.setNeedsDisplay(MKMapRect.world)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var circleOverlay: MKCircle?
|
private var circleOverlay: MKCircle?
|
||||||
@ -652,15 +653,33 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let updatedAnnotation = dict[annotation.id] {
|
if let updatedAnnotation = dict[annotation.id] {
|
||||||
annotation.coordinate = updatedAnnotation.coordinate
|
UIView.animate(withDuration: 0.2) {
|
||||||
|
annotation.coordinate = updatedAnnotation.coordinate
|
||||||
|
}
|
||||||
dict[annotation.id] = nil
|
dict[annotation.id] = nil
|
||||||
} else {
|
} else {
|
||||||
annotationsToRemove.insert(annotation)
|
annotationsToRemove.insert(annotation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mapView.removeAnnotations(Array(annotationsToRemove))
|
let selectedAnnotation = mapView.selectedAnnotations.first
|
||||||
mapView.addAnnotations(Array(dict.values))
|
var updated = false
|
||||||
|
if !annotationsToRemove.isEmpty {
|
||||||
|
mapView.removeAnnotations(Array(annotationsToRemove))
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
if !dict.isEmpty {
|
||||||
|
mapView.addAnnotations(Array(dict.values))
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
if let selectedAnnotation = selectedAnnotation as? LocationPinAnnotation, updated {
|
||||||
|
for annotation in self.annotations {
|
||||||
|
if annotation.id == selectedAnnotation.id {
|
||||||
|
mapView.selectAnnotation(annotation, animated: false)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -42,13 +42,13 @@ class LocationViewInteraction {
|
|||||||
let goToCoordinate: (CLLocationCoordinate2D) -> Void
|
let goToCoordinate: (CLLocationCoordinate2D) -> Void
|
||||||
let requestDirections: () -> Void
|
let requestDirections: () -> Void
|
||||||
let share: () -> Void
|
let share: () -> Void
|
||||||
let setupProximityNotification: (Bool, MessageId?) -> Void
|
let setupProximityNotification: (Bool, CLLocationCoordinate2D?, MessageId?) -> Void
|
||||||
let updateSendActionHighlight: (Bool) -> Void
|
let updateSendActionHighlight: (Bool) -> Void
|
||||||
let sendLiveLocation: (CLLocationCoordinate2D, Int32?) -> Void
|
let sendLiveLocation: (CLLocationCoordinate2D, Int32?) -> Void
|
||||||
let stopLiveLocation: () -> Void
|
let stopLiveLocation: () -> Void
|
||||||
let updateRightBarButton: (LocationViewRightBarButton) -> Void
|
let updateRightBarButton: (LocationViewRightBarButton) -> Void
|
||||||
|
|
||||||
init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping () -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (Bool, MessageId?) -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32?) -> Void, stopLiveLocation: @escaping () -> Void, updateRightBarButton: @escaping (LocationViewRightBarButton) -> Void) {
|
init(toggleMapModeSelection: @escaping () -> Void, updateMapMode: @escaping (LocationMapMode) -> Void, goToUserLocation: @escaping () -> Void, goToCoordinate: @escaping (CLLocationCoordinate2D) -> Void, requestDirections: @escaping () -> Void, share: @escaping () -> Void, setupProximityNotification: @escaping (Bool, CLLocationCoordinate2D?, MessageId?) -> Void, updateSendActionHighlight: @escaping (Bool) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32?) -> Void, stopLiveLocation: @escaping () -> Void, updateRightBarButton: @escaping (LocationViewRightBarButton) -> Void) {
|
||||||
self.toggleMapModeSelection = toggleMapModeSelection
|
self.toggleMapModeSelection = toggleMapModeSelection
|
||||||
self.updateMapMode = updateMapMode
|
self.updateMapMode = updateMapMode
|
||||||
self.goToUserLocation = goToUserLocation
|
self.goToUserLocation = goToUserLocation
|
||||||
@ -166,7 +166,7 @@ public final class LocationViewController: ViewController {
|
|||||||
})
|
})
|
||||||
strongSelf.present(OpenInActionSheetController(context: context, item: .location(location: location, withDirections: false), additionalAction: shareAction, openUrl: params.openUrl), in: .window(.root), with: nil)
|
strongSelf.present(OpenInActionSheetController(context: context, item: .location(location: location, withDirections: false), additionalAction: shareAction, openUrl: params.openUrl), in: .window(.root), with: nil)
|
||||||
}
|
}
|
||||||
}, setupProximityNotification: { [weak self] reset, messageId in
|
}, setupProximityNotification: { [weak self] reset, coordinate, messageId in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -204,8 +204,10 @@ public final class LocationViewController: ViewController {
|
|||||||
|
|
||||||
CURRENT_DISTANCE = Double(distance)
|
CURRENT_DISTANCE = Double(distance)
|
||||||
}
|
}
|
||||||
} else {
|
} else if let coordinate = coordinate {
|
||||||
|
strongSelf.present(textAlertController(context: strongSelf.context, title: strongSelf.presentationData.strings.Location_LiveLocationRequired_Title, text: strongSelf.presentationData.strings.Location_LiveLocationRequired_Description, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Location_LiveLocationRequired_ShareLocation, action: { [weak self] in
|
||||||
|
self?.interaction?.sendLiveLocation(coordinate, distance)
|
||||||
|
}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {})]), in: .window(.root))
|
||||||
}
|
}
|
||||||
}, willDismiss: { [weak self] in
|
}, willDismiss: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -243,21 +245,18 @@ public final class LocationViewController: ViewController {
|
|||||||
controller?.dismissAnimated()
|
controller?.dismissAnimated()
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 15 * 60))
|
params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 15 * 60))
|
||||||
strongSelf.dismiss()
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor1Hour, color: .accent, action: { [weak self, weak controller] in
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor1Hour, color: .accent, action: { [weak self, weak controller] in
|
||||||
controller?.dismissAnimated()
|
controller?.dismissAnimated()
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 60 * 60 - 1))
|
params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 60 * 60 - 1))
|
||||||
strongSelf.dismiss()
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor8Hours, color: .accent, action: { [weak self, weak controller] in
|
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor8Hours, color: .accent, action: { [weak self, weak controller] in
|
||||||
controller?.dismissAnimated()
|
controller?.dismissAnimated()
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 8 * 60 * 60))
|
params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 8 * 60 * 60))
|
||||||
strongSelf.dismiss()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
@ -304,7 +303,7 @@ public final class LocationViewController: ViewController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self.displayNode = LocationViewControllerNode(context: self.context, presentationData: self.presentationData, subject: self.subject, interaction: interaction)
|
self.displayNode = LocationViewControllerNode(context: self.context, presentationData: self.presentationData, subject: self.subject, interaction: interaction, locationManager: self.locationManager)
|
||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
|
|
||||||
self.controllerNode.updateState { state -> LocationViewState in
|
self.controllerNode.updateState { state -> LocationViewState in
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import AccountContext
|
|||||||
import AppBundle
|
import AppBundle
|
||||||
import CoreLocation
|
import CoreLocation
|
||||||
import Geocoding
|
import Geocoding
|
||||||
|
import DeviceAccess
|
||||||
|
|
||||||
func getLocation(from message: Message) -> TelegramMediaMap? {
|
func getLocation(from message: Message) -> TelegramMediaMap? {
|
||||||
return message.media.first(where: { $0 is TelegramMediaMap } ) as? TelegramMediaMap
|
return message.media.first(where: { $0 is TelegramMediaMap } ) as? TelegramMediaMap
|
||||||
@ -191,12 +192,13 @@ struct LocationViewState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class LocationViewControllerNode: ViewControllerTracingNode {
|
final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationManagerDelegate {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private let presentationDataPromise: Promise<PresentationData>
|
private let presentationDataPromise: Promise<PresentationData>
|
||||||
private var subject: Message
|
private var subject: Message
|
||||||
private let interaction: LocationViewInteraction
|
private let interaction: LocationViewInteraction
|
||||||
|
private let locationManager: LocationManager
|
||||||
|
|
||||||
private let listNode: ListView
|
private let listNode: ListView
|
||||||
let headerNode: LocationMapHeaderNode
|
let headerNode: LocationMapHeaderNode
|
||||||
@ -212,12 +214,13 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
|||||||
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
|
private var validLayout: (layout: ContainerViewLayout, navigationHeight: CGFloat)?
|
||||||
private var listOffset: CGFloat?
|
private var listOffset: CGFloat?
|
||||||
|
|
||||||
init(context: AccountContext, presentationData: PresentationData, subject: Message, interaction: LocationViewInteraction) {
|
init(context: AccountContext, presentationData: PresentationData, subject: Message, interaction: LocationViewInteraction, locationManager: LocationManager) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.presentationDataPromise = Promise(presentationData)
|
self.presentationDataPromise = Promise(presentationData)
|
||||||
self.subject = subject
|
self.subject = subject
|
||||||
self.interaction = interaction
|
self.interaction = interaction
|
||||||
|
self.locationManager = locationManager
|
||||||
|
|
||||||
self.state = LocationViewState()
|
self.state = LocationViewState()
|
||||||
self.statePromise = Promise(self.state)
|
self.statePromise = Promise(self.state)
|
||||||
@ -276,7 +279,10 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
|||||||
setupProximityNotificationImpl = { reset in
|
setupProximityNotificationImpl = { reset in
|
||||||
let _ = (liveLocations
|
let _ = (liveLocations
|
||||||
|> take(1)
|
|> take(1)
|
||||||
|> deliverOnMainQueue).start(next: { messages in
|
|> deliverOnMainQueue).start(next: { [weak self] messages in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
var ownMessageId: MessageId?
|
var ownMessageId: MessageId?
|
||||||
for message in messages {
|
for message in messages {
|
||||||
if message.localTags.contains(.OutgoingLiveLocation) {
|
if message.localTags.contains(.OutgoingLiveLocation) {
|
||||||
@ -284,7 +290,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
interaction.setupProximityNotification(reset, ownMessageId)
|
interaction.setupProximityNotification(reset, strongSelf.headerNode.mapNode.currentUserLocation?.coordinate, ownMessageId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,15 +357,29 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
|||||||
timeout = Double(liveBroadcastingTimeout)
|
timeout = Double(liveBroadcastingTimeout)
|
||||||
} else {
|
} else {
|
||||||
title = presentationData.strings.Map_ShareLiveLocation
|
title = presentationData.strings.Map_ShareLiveLocation
|
||||||
subtitle = presentationData.strings.Map_ShareLiveLocation
|
subtitle = presentationData.strings.Map_ShareLiveLocationHelp
|
||||||
beginTime = nil
|
beginTime = nil
|
||||||
timeout = nil
|
timeout = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.append(.toggleLiveLocation(presentationData.theme, title, subtitle, userLocation?.coordinate, beginTime, timeout))
|
entries.append(.toggleLiveLocation(presentationData.theme, title, subtitle, userLocation?.coordinate, beginTime, timeout))
|
||||||
if effectiveLiveLocations.isEmpty {
|
|
||||||
effectiveLiveLocations = [subject]
|
var sortedLiveLocations: [Message] = []
|
||||||
|
|
||||||
|
var effectiveSubject: Message?
|
||||||
|
for message in effectiveLiveLocations {
|
||||||
|
if message.id.peerId == subject.id.peerId {
|
||||||
|
effectiveSubject = message
|
||||||
|
} else {
|
||||||
|
sortedLiveLocations.append(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if let effectiveSubject = effectiveSubject {
|
||||||
|
sortedLiveLocations.insert(effectiveSubject, at: 0)
|
||||||
|
} else {
|
||||||
|
sortedLiveLocations.insert(subject, at: 0)
|
||||||
|
}
|
||||||
|
effectiveLiveLocations = sortedLiveLocations
|
||||||
}
|
}
|
||||||
|
|
||||||
for message in effectiveLiveLocations {
|
for message in effectiveLiveLocations {
|
||||||
@ -480,11 +500,20 @@ final class LocationViewControllerNode: ViewControllerTracingNode {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.locationManager.manager.startUpdatingHeading()
|
||||||
|
self.locationManager.manager.delegate = self
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.disposable?.dispose()
|
self.disposable?.dispose()
|
||||||
self.geocodingDisposable.dispose()
|
self.geocodingDisposable.dispose()
|
||||||
|
|
||||||
|
self.locationManager.manager.stopUpdatingHeading()
|
||||||
|
}
|
||||||
|
|
||||||
|
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
|
||||||
|
self.headerNode.mapNode.userHeading = CGFloat(newHeading.magneticHeading)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePresentationData(_ presentationData: PresentationData) {
|
func updatePresentationData(_ presentationData: PresentationData) {
|
||||||
|
|||||||
@ -16,7 +16,6 @@ static_library(
|
|||||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||||
"//submodules/LiveLocationTimerNode:LiveLocationTimerNode",
|
"//submodules/LiveLocationTimerNode:LiveLocationTimerNode",
|
||||||
"//submodules/AccountContext:AccountContext",
|
"//submodules/AccountContext:AccountContext",
|
||||||
"//submodules/LegacyComponents:LegacyComponents",
|
|
||||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||||
"//submodules/Markdown:Markdown",
|
"//submodules/Markdown:Markdown",
|
||||||
|
|||||||
@ -17,7 +17,6 @@ swift_library(
|
|||||||
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
"//submodules/TelegramStringFormatting:TelegramStringFormatting",
|
||||||
"//submodules/LiveLocationTimerNode:LiveLocationTimerNode",
|
"//submodules/LiveLocationTimerNode:LiveLocationTimerNode",
|
||||||
"//submodules/AccountContext:AccountContext",
|
"//submodules/AccountContext:AccountContext",
|
||||||
"//submodules/LegacyComponents:LegacyComponents",
|
|
||||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||||
"//submodules/Markdown:Markdown",
|
"//submodules/Markdown:Markdown",
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import TelegramUIPreferences
|
|||||||
import TextFormat
|
import TextFormat
|
||||||
import Markdown
|
import Markdown
|
||||||
import LocalizedPeerData
|
import LocalizedPeerData
|
||||||
|
import LiveLocationTimerNode
|
||||||
|
|
||||||
private let titleFont = Font.regular(12.0)
|
private let titleFont = Font.regular(12.0)
|
||||||
private let subtitleFont = Font.regular(10.0)
|
private let subtitleFont = Font.regular(10.0)
|
||||||
@ -31,7 +32,7 @@ final class LocationBroadcastNavigationAccessoryPanel: ASDisplayNode {
|
|||||||
private let contentNode: ASDisplayNode
|
private let contentNode: ASDisplayNode
|
||||||
|
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
private let wavesNode: LocationBroadcastPanelWavesNode
|
private let wavesNode: LiveLocationWavesNode
|
||||||
private let titleNode: TextNode
|
private let titleNode: TextNode
|
||||||
private let subtitleNode: TextNode
|
private let subtitleNode: TextNode
|
||||||
private let closeButton: HighlightableButtonNode
|
private let closeButton: HighlightableButtonNode
|
||||||
@ -58,7 +59,7 @@ final class LocationBroadcastNavigationAccessoryPanel: ASDisplayNode {
|
|||||||
self.iconNode.displaysAsynchronously = false
|
self.iconNode.displaysAsynchronously = false
|
||||||
self.iconNode.image = PresentationResourcesRootController.navigationLiveLocationIcon(self.theme)
|
self.iconNode.image = PresentationResourcesRootController.navigationLiveLocationIcon(self.theme)
|
||||||
|
|
||||||
self.wavesNode = LocationBroadcastPanelWavesNode(color: self.theme.rootController.navigationBar.accentTextColor)
|
self.wavesNode = LiveLocationWavesNode(color: self.theme.rootController.navigationBar.accentTextColor)
|
||||||
|
|
||||||
self.titleNode = TextNode()
|
self.titleNode = TextNode()
|
||||||
self.titleNode.isUserInteractionEnabled = false
|
self.titleNode.isUserInteractionEnabled = false
|
||||||
|
|||||||
@ -261,7 +261,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
|
|||||||
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId, venueType: venueType, liveBroadcastingTimeout: nil, heading: nil)
|
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId, venueType: venueType, liveBroadcastingTimeout: nil, heading: nil)
|
||||||
return (mediaMap, nil)
|
return (mediaMap, nil)
|
||||||
case let .messageMediaGeoLive(geo, heading, period):
|
case let .messageMediaGeoLive(geo, heading, period):
|
||||||
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period, heading: heading)
|
let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil, venueType: nil, liveBroadcastingTimeout: period, heading: heading > 0 ? heading : nil)
|
||||||
return (mediaMap, nil)
|
return (mediaMap, nil)
|
||||||
case let .messageMediaDocument(_, document, ttlSeconds):
|
case let .messageMediaDocument(_, document, ttlSeconds):
|
||||||
if let document = document {
|
if let document = document {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ func telegramMediaMapFromApiGeoPoint(_ geo: Api.GeoPoint, title: String?, addres
|
|||||||
venue = MapVenue(title: title, address: address, provider: provider, id: venueId, type: venueType)
|
venue = MapVenue(title: title, address: address, provider: provider, id: venueId, type: venueType)
|
||||||
}
|
}
|
||||||
switch geo {
|
switch geo {
|
||||||
case let .geoPoint(_, long, lat, accessHash, accuracyRadius):
|
case let .geoPoint(_, long, lat, _, accuracyRadius):
|
||||||
return TelegramMediaMap(latitude: lat, longitude: long, heading: heading.flatMap { Double($0) }, accuracyRadius: accuracyRadius.flatMap { Double($0) }, geoPlace: nil, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout)
|
return TelegramMediaMap(latitude: lat, longitude: long, heading: heading.flatMap { Double($0) }, accuracyRadius: accuracyRadius.flatMap { Double($0) }, geoPlace: nil, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout)
|
||||||
case .geoPointEmpty:
|
case .geoPointEmpty:
|
||||||
return TelegramMediaMap(latitude: 0.0, longitude: 0.0, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout)
|
return TelegramMediaMap(latitude: 0.0, longitude: 0.0, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: venue, liveBroadcastingTimeout: liveBroadcastingTimeout)
|
||||||
|
|||||||
@ -184,9 +184,12 @@ public final class PrincipalThemeEssentialGraphics {
|
|||||||
public let incomingBubbleGradientImage: UIImage?
|
public let incomingBubbleGradientImage: UIImage?
|
||||||
public let outgoingBubbleGradientImage: UIImage?
|
public let outgoingBubbleGradientImage: UIImage?
|
||||||
|
|
||||||
|
public let hasWallpaper: Bool
|
||||||
|
|
||||||
init(mediaBox: MediaBox, presentationTheme: PresentationTheme, wallpaper initialWallpaper: TelegramWallpaper, preview: Bool = false, knockoutMode: Bool, bubbleCorners: PresentationChatBubbleCorners) {
|
init(mediaBox: MediaBox, presentationTheme: PresentationTheme, wallpaper initialWallpaper: TelegramWallpaper, preview: Bool = false, knockoutMode: Bool, bubbleCorners: PresentationChatBubbleCorners) {
|
||||||
let theme = presentationTheme.chat
|
let theme = presentationTheme.chat
|
||||||
var wallpaper = initialWallpaper
|
var wallpaper = initialWallpaper
|
||||||
|
self.hasWallpaper = !wallpaper.isEmpty
|
||||||
|
|
||||||
let incoming: PresentationThemeBubbleColorComponents = wallpaper.isEmpty ? theme.message.incoming.bubble.withoutWallpaper : theme.message.incoming.bubble.withWallpaper
|
let incoming: PresentationThemeBubbleColorComponents = wallpaper.isEmpty ? theme.message.incoming.bubble.withoutWallpaper : theme.message.incoming.bubble.withWallpaper
|
||||||
let outgoing: PresentationThemeBubbleColorComponents = wallpaper.isEmpty ? theme.message.outgoing.bubble.withoutWallpaper : theme.message.outgoing.bubble.withWallpaper
|
let outgoing: PresentationThemeBubbleColorComponents = wallpaper.isEmpty ? theme.message.outgoing.bubble.withoutWallpaper : theme.message.outgoing.bubble.withWallpaper
|
||||||
|
|||||||
@ -250,7 +250,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let currentParams = self.currentParams {
|
if let currentParams = self.currentParams {
|
||||||
var backgroundFrame = CGRect(x: currentParams.contentOrigin.x - offset, y: currentParams.contentOrigin.y, width: currentParams.size.width + offset, height: currentParams.size.height)
|
let backgroundFrame = CGRect(x: currentParams.contentOrigin.x - offset, y: 0.0, width: currentParams.size.width + offset, height: currentParams.size.height)
|
||||||
self.backgroundNode?.updateLayout(size: backgroundFrame.size, transition: .immediate)
|
self.backgroundNode?.updateLayout(size: backgroundFrame.size, transition: .immediate)
|
||||||
self.backgroundNode?.frame = backgroundFrame
|
self.backgroundNode?.frame = backgroundFrame
|
||||||
}
|
}
|
||||||
@ -262,6 +262,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isExtractedToContextPreviewUpdated(_ isExtractedToContextPreview: Bool) {
|
||||||
|
}
|
||||||
|
|
||||||
func update(size: CGSize, contentOrigin: CGPoint, index: Int, presentationData: ChatPresentationData, graphics: PrincipalThemeEssentialGraphics, backgroundType: ChatMessageBackgroundType, messageSelection: Bool?) {
|
func update(size: CGSize, contentOrigin: CGPoint, index: Int, presentationData: ChatPresentationData, graphics: PrincipalThemeEssentialGraphics, backgroundType: ChatMessageBackgroundType, messageSelection: Bool?) {
|
||||||
self.currentParams = (size, contentOrigin, presentationData, graphics, backgroundType, messageSelection)
|
self.currentParams = (size, contentOrigin, presentationData, graphics, backgroundType, messageSelection)
|
||||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||||
@ -277,17 +280,20 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
if let _ = self.selectionBackgroundNode {
|
if let _ = self.selectionBackgroundNode {
|
||||||
} else {
|
} else {
|
||||||
let selectionBackgroundNode = ASDisplayNode()
|
let selectionBackgroundNode = ASDisplayNode()
|
||||||
self.sourceNode.contentNode.insertSubnode(selectionBackgroundNode, at: 0)
|
self.containerNode.insertSubnode(selectionBackgroundNode, at: 0)
|
||||||
self.selectionBackgroundNode = selectionBackgroundNode
|
self.selectionBackgroundNode = selectionBackgroundNode
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectionBackgroundFrame = bounds.offsetBy(dx: contentOrigin.x, dy: contentOrigin.y)
|
var selectionBackgroundFrame = bounds.offsetBy(dx: contentOrigin.x, dy: 0.0)
|
||||||
if index == 0 && contentOrigin.y > 0.0 {
|
if index == 0 && contentOrigin.y > 0.0 {
|
||||||
selectionBackgroundFrame.origin.y -= contentOrigin.y
|
selectionBackgroundFrame.origin.y -= contentOrigin.y
|
||||||
selectionBackgroundFrame.size.height += contentOrigin.y
|
selectionBackgroundFrame.size.height += contentOrigin.y
|
||||||
}
|
}
|
||||||
|
|
||||||
self.selectionBackgroundNode?.backgroundColor = messageTheme.accentTextColor.withAlphaComponent(0.08)
|
let bubbleColor = graphics.hasWallpaper ? messageTheme.bubble.withWallpaper.fill : messageTheme.bubble.withoutWallpaper.fill
|
||||||
|
let selectionColor = bubbleColor.withAlphaComponent(1.0).mixedWith(messageTheme.accentTextColor.withAlphaComponent(1.0), alpha: 0.08)
|
||||||
|
|
||||||
|
self.selectionBackgroundNode?.backgroundColor = selectionColor
|
||||||
self.selectionBackgroundNode?.frame = selectionBackgroundFrame
|
self.selectionBackgroundNode?.frame = selectionBackgroundFrame
|
||||||
} else if let selectionBackgroundNode = self.selectionBackgroundNode {
|
} else if let selectionBackgroundNode = self.selectionBackgroundNode {
|
||||||
self.selectionBackgroundNode = nil
|
self.selectionBackgroundNode = nil
|
||||||
@ -1102,7 +1108,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset)
|
let maximumContentWidth = floor(tmpWidth - layoutConstants.bubble.edgeInset - layoutConstants.bubble.edgeInset - layoutConstants.bubble.contentInsets.left - layoutConstants.bubble.contentInsets.right - avatarInset)
|
||||||
|
|
||||||
var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, BubbleItemAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = []
|
var contentPropertiesAndPrepareLayouts: [(Message, Bool, ChatMessageEntryAttributes, BubbleItemAttributes, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))))] = []
|
||||||
var addedContentNodes: [(Message, ChatMessageBubbleContentNode)]?
|
var addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode)]?
|
||||||
|
|
||||||
let (contentNodeMessagesAndClasses, needSeparateContainers) = contentNodeMessagesAndClassesForItem(item)
|
let (contentNodeMessagesAndClasses, needSeparateContainers) = contentNodeMessagesAndClassesForItem(item)
|
||||||
for (contentNodeMessage, contentNodeClass, attributes, bubbleAttributes) in contentNodeMessagesAndClasses {
|
for (contentNodeMessage, contentNodeClass, attributes, bubbleAttributes) in contentNodeMessagesAndClasses {
|
||||||
@ -1120,7 +1126,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
if addedContentNodes == nil {
|
if addedContentNodes == nil {
|
||||||
addedContentNodes = []
|
addedContentNodes = []
|
||||||
}
|
}
|
||||||
addedContentNodes!.append((contentNodeMessage, contentNode))
|
addedContentNodes!.append((contentNodeMessage, bubbleAttributes.isAttachment, contentNode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1279,7 +1285,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
let (properties, unboundSize, maxNodeWidth, nodeLayout) = prepareLayout(contentItem, layoutConstants, prepareContentPosition, itemSelection, CGSize(width: maximumContentWidth, height: CGFloat.greatestFiniteMagnitude))
|
let (properties, unboundSize, maxNodeWidth, nodeLayout) = prepareLayout(contentItem, layoutConstants, prepareContentPosition, itemSelection, CGSize(width: maximumContentWidth, height: CGFloat.greatestFiniteMagnitude))
|
||||||
maximumNodeWidth = min(maximumNodeWidth, maxNodeWidth)
|
maximumNodeWidth = min(maximumNodeWidth, maxNodeWidth)
|
||||||
|
|
||||||
contentPropertiesAndLayouts.append((unboundSize, properties, prepareContentPosition, bubbleAttributes, nodeLayout, needSeparateContainers ? message.stableId : nil, itemSelection))
|
contentPropertiesAndLayouts.append((unboundSize, properties, prepareContentPosition, bubbleAttributes, nodeLayout, needSeparateContainers && !bubbleAttributes.isAttachment ? message.stableId : nil, itemSelection))
|
||||||
|
|
||||||
switch properties.hidesBackground {
|
switch properties.hidesBackground {
|
||||||
case .never:
|
case .never:
|
||||||
@ -1616,7 +1622,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i in 0 ..< contentPropertiesAndLayouts.count {
|
for i in 0 ..< contentPropertiesAndLayouts.count {
|
||||||
let (_, contentNodeProperties, preparePosition, _, contentNodeLayout, contentGroupId, itemSelection) = contentPropertiesAndLayouts[i]
|
let (_, contentNodeProperties, preparePosition, attributes, contentNodeLayout, contentGroupId, itemSelection) = contentPropertiesAndLayouts[i]
|
||||||
|
|
||||||
if let mosaicRange = mosaicRange, mosaicRange.contains(i), let (framesAndPositions, size) = calculatedGroupFramesAndSize {
|
if let mosaicRange = mosaicRange, mosaicRange.contains(i), let (framesAndPositions, size) = calculatedGroupFramesAndSize {
|
||||||
let mosaicIndex = i - mosaicRange.lowerBound
|
let mosaicIndex = i - mosaicRange.lowerBound
|
||||||
@ -1769,7 +1775,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
var contentSize = CGSize(width: maxContentWidth, height: 0.0)
|
var contentSize = CGSize(width: maxContentWidth, height: 0.0)
|
||||||
var contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, (ListViewItemUpdateAnimation, Bool) -> Void)] = []
|
var contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool) -> Void)] = []
|
||||||
var contentContainerNodeFrames: [(UInt32, CGRect, Bool?)] = []
|
var contentContainerNodeFrames: [(UInt32, CGRect, Bool?)] = []
|
||||||
var currentContainerGroupId: UInt32?
|
var currentContainerGroupId: UInt32?
|
||||||
var currentItemSelection: Bool?
|
var currentItemSelection: Bool?
|
||||||
@ -1797,7 +1803,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
|
|
||||||
let (_, apply) = finalize(maxContentWidth)
|
let (_, apply) = finalize(maxContentWidth)
|
||||||
let contentNodeFrame = framesAndPositions[mosaicIndex].0.offsetBy(dx: 0.0, dy: contentNodesHeight)
|
let contentNodeFrame = framesAndPositions[mosaicIndex].0.offsetBy(dx: 0.0, dy: contentNodesHeight)
|
||||||
contentNodeFramesPropertiesAndApply.append((contentNodeFrame, properties, apply))
|
contentNodeFramesPropertiesAndApply.append((contentNodeFrame, properties, false, apply))
|
||||||
|
|
||||||
if mosaicIndex == mosaicRange.upperBound - 1 {
|
if mosaicIndex == mosaicRange.upperBound - 1 {
|
||||||
contentNodesHeight += size.height
|
contentNodesHeight += size.height
|
||||||
@ -1807,8 +1813,10 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if i == 0 && !headerSize.height.isZero {
|
if i == 0 && !headerSize.height.isZero {
|
||||||
contentNodesHeight += properties.headerSpacing
|
if contentGroupId == nil {
|
||||||
totalContentNodesHeight += contentNodesHeight
|
contentNodesHeight += properties.headerSpacing
|
||||||
|
}
|
||||||
|
totalContentNodesHeight += properties.headerSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentContainerGroupId != contentGroupId {
|
if currentContainerGroupId != contentGroupId {
|
||||||
@ -1816,17 +1824,22 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
var overlapOffset: CGFloat = 0.0
|
var overlapOffset: CGFloat = 0.0
|
||||||
if !contentContainerNodeFrames.isEmpty {
|
if !contentContainerNodeFrames.isEmpty {
|
||||||
overlapOffset = smallContainerGroupOverlap
|
overlapOffset = smallContainerGroupOverlap
|
||||||
|
}
|
||||||
|
contentContainerNodeFrames.append((containerGroupId, CGRect(x: 0.0, y: headerSize.height + totalContentNodesHeight - contentNodesHeight - overlapOffset, width: maxContentWidth, height: contentNodesHeight), currentItemSelection))
|
||||||
|
if !overlapOffset.isZero {
|
||||||
totalContentNodesHeight -= smallContainerGroupOverlap
|
totalContentNodesHeight -= smallContainerGroupOverlap
|
||||||
}
|
}
|
||||||
contentContainerNodeFrames.append((containerGroupId, CGRect(x: 0.0, y: totalContentNodesHeight - contentNodesHeight - overlapOffset, width: maxContentWidth, height: contentNodesHeight), currentItemSelection))
|
if contentGroupId == nil {
|
||||||
|
totalContentNodesHeight += 3.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
contentNodesHeight = 0.0
|
contentNodesHeight = contentGroupId == nil ? totalContentNodesHeight : 0.0
|
||||||
currentContainerGroupId = contentGroupId
|
currentContainerGroupId = contentGroupId
|
||||||
currentItemSelection = itemSelection
|
currentItemSelection = itemSelection
|
||||||
}
|
}
|
||||||
|
|
||||||
let (size, apply) = finalize(maxContentWidth)
|
let (size, apply) = finalize(maxContentWidth)
|
||||||
contentNodeFramesPropertiesAndApply.append((CGRect(origin: CGPoint(x: 0.0, y: contentNodesHeight), size: size), properties, apply))
|
contentNodeFramesPropertiesAndApply.append((CGRect(origin: CGPoint(x: 0.0, y: contentNodesHeight), size: size), properties, contentGroupId == nil, apply))
|
||||||
|
|
||||||
contentNodesHeight += size.height
|
contentNodesHeight += size.height
|
||||||
totalContentNodesHeight += size.height
|
totalContentNodesHeight += size.height
|
||||||
@ -1838,7 +1851,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
if !contentContainerNodeFrames.isEmpty {
|
if !contentContainerNodeFrames.isEmpty {
|
||||||
overlapOffset = smallContainerGroupOverlap
|
overlapOffset = smallContainerGroupOverlap
|
||||||
}
|
}
|
||||||
contentContainerNodeFrames.append((containerGroupId, CGRect(x: 0.0, y: totalContentNodesHeight - contentNodesHeight - overlapOffset, width: maxContentWidth, height: contentNodesHeight), currentItemSelection))
|
contentContainerNodeFrames.append((containerGroupId, CGRect(x: 0.0, y: headerSize.height + totalContentNodesHeight - contentNodesHeight - overlapOffset, width: maxContentWidth, height: contentNodesHeight), currentItemSelection))
|
||||||
if !overlapOffset.isZero {
|
if !overlapOffset.isZero {
|
||||||
totalContentNodesHeight -= smallContainerGroupOverlap
|
totalContentNodesHeight -= smallContainerGroupOverlap
|
||||||
}
|
}
|
||||||
@ -2007,9 +2020,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
replyInfoSizeApply: (CGSize, () -> ChatMessageReplyInfoNode?),
|
replyInfoSizeApply: (CGSize, () -> ChatMessageReplyInfoNode?),
|
||||||
replyInfoOriginY: CGFloat,
|
replyInfoOriginY: CGFloat,
|
||||||
removedContentNodeIndices: [Int]?,
|
removedContentNodeIndices: [Int]?,
|
||||||
addedContentNodes: [(Message, ChatMessageBubbleContentNode)]?,
|
addedContentNodes: [(Message, Bool, ChatMessageBubbleContentNode)]?,
|
||||||
contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)],
|
contentNodeMessagesAndClasses: [(Message, AnyClass, ChatMessageEntryAttributes, BubbleItemAttributes)],
|
||||||
contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, (ListViewItemUpdateAnimation, Bool) -> Void)],
|
contentNodeFramesPropertiesAndApply: [(CGRect, ChatMessageBubbleContentProperties, Bool, (ListViewItemUpdateAnimation, Bool) -> Void)],
|
||||||
contentContainerNodeFrames: [(UInt32, CGRect, Bool?)],
|
contentContainerNodeFrames: [(UInt32, CGRect, Bool?)],
|
||||||
mosaicStatusOrigin: CGPoint?,
|
mosaicStatusOrigin: CGPoint?,
|
||||||
mosaicStatusSizeAndApply: (CGSize, (Bool) -> ChatMessageDateAndStatusNode)?,
|
mosaicStatusSizeAndApply: (CGSize, (Bool) -> ChatMessageDateAndStatusNode)?,
|
||||||
@ -2263,10 +2276,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contextSourceNode.isExtractedToContextPreviewUpdated = { [weak strongSelf, weak contextSourceNode] isExtractedToContextPreview in
|
contextSourceNode.isExtractedToContextPreviewUpdated = { [weak strongSelf, weak container, weak contextSourceNode] isExtractedToContextPreview in
|
||||||
guard let strongSelf = strongSelf, let strongContextSourceNode = contextSourceNode else {
|
guard let strongSelf = strongSelf, let strongContextSourceNode = contextSourceNode else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
container?.isExtractedToContextPreviewUpdated(isExtractedToContextPreview)
|
||||||
|
|
||||||
// if !isExtractedToContextPreview, let (rect, size) = strongSelf.absoluteRect {
|
// if !isExtractedToContextPreview, let (rect, size) = strongSelf.absoluteRect {
|
||||||
// strongSelf.updateAbsoluteRect(rect, within: size)
|
// strongSelf.updateAbsoluteRect(rect, within: size)
|
||||||
@ -2305,22 +2320,18 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
let containerFrame = CGRect(origin: relativeFrame.origin, size: CGSize(width: params.width, height: relativeFrame.size.height))
|
let containerFrame = CGRect(origin: relativeFrame.origin, size: CGSize(width: params.width, height: relativeFrame.size.height))
|
||||||
contentContainer?.sourceNode.frame = CGRect(origin: CGPoint(), size: containerFrame.size)
|
contentContainer?.sourceNode.frame = CGRect(origin: CGPoint(), size: containerFrame.size)
|
||||||
contentContainer?.sourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: containerFrame.size)
|
contentContainer?.sourceNode.contentNode.frame = CGRect(origin: CGPoint(), size: containerFrame.size)
|
||||||
// contentContainer?.sourceNode.backgroundColor = UIColor.blue.withAlphaComponent(0.5)
|
|
||||||
|
|
||||||
contentContainer?.containerNode.frame = containerFrame
|
contentContainer?.containerNode.frame = containerFrame
|
||||||
// contentContainer?.containerNode.backgroundColor = UIColor.red.withAlphaComponent(0.5)
|
|
||||||
|
|
||||||
contentContainer?.sourceNode.contentRect = CGRect(origin: CGPoint(x: backgroundFrame.minX + incomingOffset, y: 0.0), size: relativeFrame.size)
|
contentContainer?.sourceNode.contentRect = CGRect(origin: CGPoint(x: backgroundFrame.minX + incomingOffset, y: 0.0), size: relativeFrame.size)
|
||||||
contentContainer?.containerNode.targetNodeForActivationProgressContentRect = relativeFrame.offsetBy(dx: backgroundFrame.minX + incomingOffset, dy: 0.0)
|
contentContainer?.containerNode.targetNodeForActivationProgressContentRect = CGRect(origin: CGPoint(x: backgroundFrame.minX + incomingOffset, y: 0.0), size: relativeFrame.size)
|
||||||
|
|
||||||
if previousContextFrame?.size != contentContainer?.containerNode.bounds.size || previousContextContentFrame != contentContainer?.sourceNode.contentRect {
|
if previousContextFrame?.size != contentContainer?.containerNode.bounds.size || previousContextContentFrame != contentContainer?.sourceNode.contentRect {
|
||||||
contentContainer?.sourceNode.layoutUpdated?(relativeFrame.size)
|
contentContainer?.sourceNode.layoutUpdated?(relativeFrame.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
contentContainer?.update(size: relativeFrame.size, contentOrigin: contentOrigin, index: index, presentationData: item.presentationData, graphics: graphics, backgroundType: backgroundType, messageSelection: itemSelection)
|
contentContainer?.update(size: relativeFrame.size, contentOrigin: contentOrigin, index: index, presentationData: item.presentationData, graphics: graphics, backgroundType: backgroundType, messageSelection: itemSelection)
|
||||||
|
|
||||||
print("container \(relativeFrame)")
|
|
||||||
|
|
||||||
index += 1
|
index += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2359,12 +2370,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let addedContentNodes = addedContentNodes {
|
if let addedContentNodes = addedContentNodes {
|
||||||
for (contentNodeMessage, contentNode) in addedContentNodes {
|
for (contentNodeMessage, isAttachent, contentNode) in addedContentNodes {
|
||||||
updatedContentNodes.append(contentNode)
|
updatedContentNodes.append(contentNode)
|
||||||
|
|
||||||
let contextSourceNode: ContextExtractedContentContainingNode = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == contentNodeMessage.stableId })?.sourceNode ?? strongSelf.mainContextSourceNode
|
let contextSourceNode: ContextExtractedContentContainingNode
|
||||||
|
if isAttachent {
|
||||||
print(contextSourceNode.debugDescription)
|
contextSourceNode = strongSelf.mainContextSourceNode
|
||||||
|
} else {
|
||||||
|
contextSourceNode = strongSelf.contentContainers.first(where: { $0.contentMessageStableId == contentNodeMessage.stableId })?.sourceNode ?? strongSelf.mainContextSourceNode
|
||||||
|
}
|
||||||
contextSourceNode.contentNode.addSubnode(contentNode)
|
contextSourceNode.contentNode.addSubnode(contentNode)
|
||||||
|
|
||||||
contentNode.visibility = strongSelf.visibility
|
contentNode.visibility = strongSelf.visibility
|
||||||
@ -2378,7 +2392,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
var sortedContentNodes: [ChatMessageBubbleContentNode] = []
|
var sortedContentNodes: [ChatMessageBubbleContentNode] = []
|
||||||
outer: for (message, nodeClass, _, _) in contentNodeMessagesAndClasses {
|
outer: for (message, nodeClass, _, _) in contentNodeMessagesAndClasses {
|
||||||
if let addedContentNodes = addedContentNodes {
|
if let addedContentNodes = addedContentNodes {
|
||||||
for (contentNodeMessage, contentNode) in addedContentNodes {
|
for (contentNodeMessage, _, contentNode) in addedContentNodes {
|
||||||
if type(of: contentNode) == nodeClass && contentNodeMessage.stableId == message.stableId {
|
if type(of: contentNode) == nodeClass && contentNodeMessage.stableId == message.stableId {
|
||||||
sortedContentNodes.append(contentNode)
|
sortedContentNodes.append(contentNode)
|
||||||
continue outer
|
continue outer
|
||||||
@ -2399,7 +2413,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
var contentNodeIndex = 0
|
var contentNodeIndex = 0
|
||||||
for (relativeFrame, _, apply) in contentNodeFramesPropertiesAndApply {
|
for (relativeFrame, _, useContentOrigin, apply) in contentNodeFramesPropertiesAndApply {
|
||||||
apply(animation, synchronousLoads)
|
apply(animation, synchronousLoads)
|
||||||
|
|
||||||
if contentNodeIndex >= strongSelf.contentNodes.count {
|
if contentNodeIndex >= strongSelf.contentNodes.count {
|
||||||
@ -2407,17 +2421,15 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
let contentNode = strongSelf.contentNodes[contentNodeIndex]
|
let contentNode = strongSelf.contentNodes[contentNodeIndex]
|
||||||
let contentNodeFrame = relativeFrame.offsetBy(dx: contentOrigin.x, dy: contentOrigin.y)
|
let contentNodeFrame = relativeFrame.offsetBy(dx: contentOrigin.x, dy: useContentOrigin ? contentOrigin.y : 0.0)
|
||||||
let previousContentNodeFrame = contentNode.frame
|
let previousContentNodeFrame = contentNode.frame
|
||||||
contentNode.frame = contentNodeFrame
|
contentNode.frame = contentNodeFrame
|
||||||
|
|
||||||
print("frame \(contentNodeFrame.debugDescription)")
|
|
||||||
|
|
||||||
if case let .System(duration) = animation {
|
if case let .System(duration) = animation {
|
||||||
var animateFrame = false
|
var animateFrame = false
|
||||||
var animateAlpha = false
|
var animateAlpha = false
|
||||||
if let addedContentNodes = addedContentNodes {
|
if let addedContentNodes = addedContentNodes {
|
||||||
if !addedContentNodes.contains(where: { $0.1 === contentNode }) {
|
if !addedContentNodes.contains(where: { $0.2 === contentNode }) {
|
||||||
animateFrame = true
|
animateFrame = true
|
||||||
} else {
|
} else {
|
||||||
animateAlpha = true
|
animateAlpha = true
|
||||||
@ -2647,8 +2659,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
if let replyInfoNode = self.replyInfoNode {
|
if let replyInfoNode = self.replyInfoNode {
|
||||||
node.addSubnode(replyInfoNode)
|
node.addSubnode(replyInfoNode)
|
||||||
}
|
}
|
||||||
for contentNode in self.contentNodes {
|
if !self.contentContainers.isEmpty {
|
||||||
node.addSubnode(contentNode)
|
node.addSubnode(self.contentContainersWrapperNode)
|
||||||
|
} else {
|
||||||
|
for contentNode in self.contentNodes {
|
||||||
|
node.addSubnode(contentNode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.mainContextSourceNode.contentNode.addSubnode(node)
|
self.mainContextSourceNode.contentNode.addSubnode(node)
|
||||||
self.transitionClippingNode = node
|
self.transitionClippingNode = node
|
||||||
@ -2663,8 +2679,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
|
|||||||
if let replyInfoNode = self.replyInfoNode {
|
if let replyInfoNode = self.replyInfoNode {
|
||||||
self.mainContextSourceNode.contentNode.addSubnode(replyInfoNode)
|
self.mainContextSourceNode.contentNode.addSubnode(replyInfoNode)
|
||||||
}
|
}
|
||||||
for contentNode in self.contentNodes {
|
if !self.contentContainers.isEmpty {
|
||||||
self.mainContextSourceNode.contentNode.addSubnode(contentNode)
|
self.mainContextSourceNode.contentNode.addSubnode(self.contentContainersWrapperNode)
|
||||||
|
} else {
|
||||||
|
for contentNode in self.contentNodes {
|
||||||
|
self.mainContextSourceNode.contentNode.addSubnode(contentNode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
transitionClippingNode.removeFromSupernode()
|
transitionClippingNode.removeFromSupernode()
|
||||||
self.transitionClippingNode = nil
|
self.transitionClippingNode = nil
|
||||||
|
|||||||
@ -145,7 +145,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode {
|
|||||||
var mode: ChatMessageLiveLocationPositionNode.Mode = .location(selectedMedia)
|
var mode: ChatMessageLiveLocationPositionNode.Mode = .location(selectedMedia)
|
||||||
if let selectedMedia = selectedMedia, let peer = item.message.author {
|
if let selectedMedia = selectedMedia, let peer = item.message.author {
|
||||||
if selectedMedia.liveBroadcastingTimeout != nil {
|
if selectedMedia.liveBroadcastingTimeout != nil {
|
||||||
mode = .liveLocation(peer, activeLiveBroadcastingTimeout != nil)
|
mode = .liveLocation(peer: peer, active: activeLiveBroadcastingTimeout != nil, latitude: selectedMedia.latitude, longitude: selectedMedia.longitude, heading: selectedMedia.heading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let (pinSize, pinApply) = makePinLayout(item.context, item.presentationData.theme.theme, mode)
|
let (pinSize, pinApply) = makePinLayout(item.context, item.presentationData.theme.theme, mode)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user