From 9e2447d78ba4a735e6def263f55a72646bbeebfa Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 23 Oct 2020 19:11:32 +0400 Subject: [PATCH] Live location fixes --- .../Sources/LiveLocationManager.swift | 4 +- .../LocationDistancePickerScreen.swift | 8 +- .../Sources/LocationMapHeaderNode.swift | 6 +- .../LocationUI/Sources/LocationMapNode.swift | 4 +- .../LocationUI/Sources/LocationUtils.swift | 4 +- .../Sources/LocationViewController.swift | 79 +++--- .../Sources/SemanticStatusNode.swift | 243 +++++++++++++----- .../Sources/CallControllerButton.swift | 5 +- .../PendingMessageUploadedContent.swift | 5 +- .../Sources/RequestEditMessage.swift | 30 ++- .../PresentationThemeEssentialGraphics.swift | 6 + .../Sources/ChatMessageBubbleBackdrop.swift | 4 +- .../Sources/ChatMessageBubbleItemNode.swift | 98 +++++-- .../ChatMessageInteractiveFileNode.swift | 32 +-- 14 files changed, 347 insertions(+), 181 deletions(-) diff --git a/submodules/LiveLocationManager/Sources/LiveLocationManager.swift b/submodules/LiveLocationManager/Sources/LiveLocationManager.swift index 6a38d86270..15f03860c7 100644 --- a/submodules/LiveLocationManager/Sources/LiveLocationManager.swift +++ b/submodules/LiveLocationManager/Sources/LiveLocationManager.swift @@ -158,7 +158,7 @@ public final class LiveLocationManagerImpl: LiveLocationManager { let addedStopped = stopMessageIds.subtracting(self.stopMessageIds) self.stopMessageIds = stopMessageIds for id in addedStopped { - self.editMessageDisposables.set((requestEditLiveLocation(postbox: self.postbox, network: self.network, stateManager: self.stateManager, messageId: id, coordinate: nil, heading: nil) + self.editMessageDisposables.set((requestEditLiveLocation(postbox: self.postbox, network: self.network, stateManager: self.stateManager, messageId: id, stop: true, coordinate: nil, heading: nil, proximityNotificationRadius: nil) |> deliverOn(self.queue)).start(completed: { [weak self] in if let strongSelf = self { strongSelf.editMessageDisposables.set(nil, forKey: id) @@ -213,7 +213,7 @@ public final class LiveLocationManagerImpl: LiveLocationManager { let ids = self.broadcastToMessageIds let remainingIds = Atomic>(value: Set(ids.keys)) for id in ids.keys { - self.editMessageDisposables.set((requestEditLiveLocation(postbox: self.postbox, network: self.network, stateManager: self.stateManager, messageId: id, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude, accuracyRadius: Int32(accuracyRadius)), heading: Int32(heading ?? 0)) + self.editMessageDisposables.set((requestEditLiveLocation(postbox: self.postbox, network: self.network, stateManager: self.stateManager, messageId: id, stop: false, coordinate: (latitude: coordinate.latitude, longitude: coordinate.longitude, accuracyRadius: Int32(accuracyRadius)), heading: Int32(heading ?? 0), proximityNotificationRadius: nil) |> deliverOn(self.queue)).start(completed: { [weak self] in if let strongSelf = self { strongSelf.editMessageDisposables.set(nil, forKey: id) diff --git a/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift b/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift index 3de2345ce9..69896dd3c8 100644 --- a/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift +++ b/submodules/LocationUI/Sources/LocationDistancePickerScreen.swift @@ -375,8 +375,12 @@ class LocationDistancePickerScreenNode: ViewControllerTracingNode, UIScrollViewD } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { - self.updateDoneButtonTitle() - self.update() + if pickerView.selectedRow(inComponent: 0) == 0 && pickerView.selectedRow(inComponent: 1) == 0 { + pickerView.selectRow(1, inComponent: 1, animated: true) + } else { + self.updateDoneButtonTitle() + self.update() + } } func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { diff --git a/submodules/LocationUI/Sources/LocationMapHeaderNode.swift b/submodules/LocationUI/Sources/LocationMapHeaderNode.swift index 0d53c3aa46..82482a3213 100644 --- a/submodules/LocationUI/Sources/LocationMapHeaderNode.swift +++ b/submodules/LocationUI/Sources/LocationMapHeaderNode.swift @@ -154,9 +154,9 @@ final class LocationMapHeaderNode: ASDisplayNode { self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected) self.infoButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted]) self.locationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/TrackIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) - self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) - self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected) - self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/InfoActiveIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted]) + self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Location/NotificationIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .normal) + self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: .selected) + self.notificationButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Title Panels/MuteIcon"), color: presentationData.theme.rootController.navigationBar.buttonColor), for: [.selected, .highlighted]) self.placesBackgroundNode.image = generateBackgroundImage(theme: presentationData.theme) self.shadowNode.image = generateShadowImage(theme: presentationData.theme, highlighted: false) } diff --git a/submodules/LocationUI/Sources/LocationMapNode.swift b/submodules/LocationUI/Sources/LocationMapNode.swift index 94eedbbb70..b2d33fb2a1 100644 --- a/submodules/LocationUI/Sources/LocationMapNode.swift +++ b/submodules/LocationUI/Sources/LocationMapNode.swift @@ -133,7 +133,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate { var radius: Double var alpha: CGFloat { didSet { - self.alphaTransition = (oldValue, CACurrentMediaTime(), 0.5) + self.alphaTransition = (oldValue, CACurrentMediaTime(), 0.3) } } var alphaTransition: (from: CGFloat, startTimestamp: Double, duration: Double)? @@ -151,7 +151,7 @@ final class LocationMapNode: ASDisplayNode, MKMapViewDelegate { class InvertedProximityCircleRenderer: MKOverlayRenderer { var radius: Double = 0.0 - var fillColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.4) + var fillColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.5) override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) { guard let overlay = self.overlay as? InvertedProximityCircle else { diff --git a/submodules/LocationUI/Sources/LocationUtils.swift b/submodules/LocationUI/Sources/LocationUtils.swift index 37bac8d3f7..30475fa14a 100644 --- a/submodules/LocationUI/Sources/LocationUtils.swift +++ b/submodules/LocationUI/Sources/LocationUtils.swift @@ -7,8 +7,8 @@ import TelegramStringFormatting import MapKit extension TelegramMediaMap { - convenience init(coordinate: CLLocationCoordinate2D, liveBroadcastingTimeout: Int32? = nil) { - self.init(latitude: coordinate.latitude, longitude: coordinate.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: liveBroadcastingTimeout, liveProximityNotificationRadius: nil) + convenience init(coordinate: CLLocationCoordinate2D, liveBroadcastingTimeout: Int32? = nil, proximityNotificationRadius: Int32? = nil) { + self.init(latitude: coordinate.latitude, longitude: coordinate.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: nil, liveBroadcastingTimeout: liveBroadcastingTimeout, liveProximityNotificationRadius: proximityNotificationRadius) } var coordinate: CLLocationCoordinate2D { diff --git a/submodules/LocationUI/Sources/LocationViewController.swift b/submodules/LocationUI/Sources/LocationViewController.swift index 8ba1197c52..173b564879 100644 --- a/submodules/LocationUI/Sources/LocationViewController.swift +++ b/submodules/LocationUI/Sources/LocationViewController.swift @@ -173,8 +173,7 @@ public final class LocationViewController: ViewController { if reset { if let messageId = messageId { - -// let _ = cancelProximityNotification(postbox: context.account.postbox, network: context.account.network, messageId: messageId).start() + let _ = requestEditLiveLocation(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, messageId: messageId, stop: false, coordinate: nil, heading: nil, proximityNotificationRadius: 0).start() } } else { strongSelf.controllerNode.setProximityIndicator(radius: 0) @@ -189,8 +188,10 @@ public final class LocationViewController: ViewController { return } - if let coordinate = coordinate { - self?.interaction?.sendLiveLocation(coordinate, distance) + if let messageId = messageId { + let _ = requestEditLiveLocation(postbox: context.account.postbox, network: context.account.network, stateManager: context.account.stateManager, messageId: messageId, stop: false, coordinate: nil, heading: nil, proximityNotificationRadius: distance).start() + } else if let coordinate = coordinate { + strongSelf.interaction?.sendLiveLocation(coordinate, distance) } completion() }, willDismiss: { [weak self] in @@ -218,42 +219,44 @@ public final class LocationViewController: ViewController { return } - let _ = (context.account.postbox.loadedPeerWithId(subject.id.peerId) - |> deliverOnMainQueue).start(next: { [weak self] peer in - let controller = ActionSheetController(presentationData: strongSelf.presentationData) - var title = strongSelf.presentationData.strings.Map_LiveLocationGroupDescription - if let user = peer as? TelegramUser { - title = strongSelf.presentationData.strings.Map_LiveLocationPrivateDescription(user.compactDisplayTitle).0 - } - - let sendLiveLocationImpl: (Int32) -> Void = { [weak self, weak controller] period in - controller?.dismissAnimated() - if let strongSelf = self { - params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: period)) + if let distance = distance { + params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: 30 * 60, proximityNotificationRadius: distance)) + } else { + let _ = (context.account.postbox.loadedPeerWithId(subject.id.peerId) + |> deliverOnMainQueue).start(next: { [weak self] peer in + let controller = ActionSheetController(presentationData: strongSelf.presentationData) + var title = strongSelf.presentationData.strings.Map_LiveLocationGroupDescription + if let user = peer as? TelegramUser { + title = strongSelf.presentationData.strings.Map_LiveLocationPrivateDescription(user.compactDisplayTitle).0 } - } - - controller.setItemGroups([ - ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: title), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor15Minutes, color: .accent, action: { - sendLiveLocationImpl(15 * 60) - }), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor1Hour, color: .accent, action: { - sendLiveLocationImpl(60 * 60 - 1) - }), - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor8Hours, color: .accent, action: { - sendLiveLocationImpl(8 * 60 * 60) - }) - ]), - ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak controller] in - controller?.dismissAnimated() - }) + + let sendLiveLocationImpl: (Int32) -> Void = { [weak self, weak controller] period in + controller?.dismissAnimated() + params.sendLiveLocation(TelegramMediaMap(coordinate: coordinate, liveBroadcastingTimeout: period)) + } + + controller.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: title), + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor15Minutes, color: .accent, action: { + sendLiveLocationImpl(15 * 60) + }), + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor1Hour, color: .accent, action: { + sendLiveLocationImpl(60 * 60 - 1) + }), + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Map_LiveLocationFor8Hours, color: .accent, action: { + sendLiveLocationImpl(8 * 60 * 60) + }) + ]), + ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak controller] in + controller?.dismissAnimated() + }) + ]) ]) - ]) - strongSelf.present(controller, in: .window(.root)) - }) + strongSelf.present(controller, in: .window(.root)) + }) + } } }, stopLiveLocation: { [weak self] in params.stopLiveLocation(nil) diff --git a/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift b/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift index f4d383da96..fae7e30d7c 100644 --- a/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift +++ b/submodules/SemanticStatusNode/Sources/SemanticStatusNode.swift @@ -566,33 +566,117 @@ private extension SemanticStatusNodeState { private final class SemanticStatusNodeTransitionDrawingState { let transition: CGFloat - let drawingState: SemanticStatusNodeStateDrawingState + let drawingState: SemanticStatusNodeStateDrawingState? + let appearanceState: SemanticStatusNodeAppearanceDrawingState? - init(transition: CGFloat, drawingState: SemanticStatusNodeStateDrawingState) { + init(transition: CGFloat, drawingState: SemanticStatusNodeStateDrawingState?, appearanceState: SemanticStatusNodeAppearanceDrawingState?) { self.transition = transition self.drawingState = drawingState + self.appearanceState = appearanceState + } +} + +private final class SemanticStatusNodeAppearanceContext { + let background: UIColor + let foreground: UIColor + let backgroundImage: UIImage? + let overlayForeground: UIColor? + let cutout: CGRect? + + init(background: UIColor, foreground: UIColor, backgroundImage: UIImage?, overlayForeground: UIColor?, cutout: CGRect?) { + self.background = background + self.foreground = foreground + self.backgroundImage = backgroundImage + self.overlayForeground = overlayForeground + self.cutout = cutout + } + + func drawingState(transitionFraction: CGFloat) -> SemanticStatusNodeAppearanceDrawingState { + return SemanticStatusNodeAppearanceDrawingState(transitionFraction: transitionFraction, background: self.background, foreground: self.foreground, backgroundImage: self.backgroundImage, overlayForeground: self.overlayForeground, cutout: self.cutout) + } + + func withUpdatedBackground(_ background: UIColor) -> SemanticStatusNodeAppearanceContext { + return SemanticStatusNodeAppearanceContext(background: background, foreground: self.foreground, backgroundImage: self.backgroundImage, overlayForeground: self.overlayForeground, cutout: cutout) + } + + func withUpdatedForeground(_ foreground: UIColor) -> SemanticStatusNodeAppearanceContext { + return SemanticStatusNodeAppearanceContext(background: self.background, foreground: foreground, backgroundImage: self.backgroundImage, overlayForeground: self.overlayForeground, cutout: cutout) + } + + func withUpdatedBackgroundImage(_ backgroundImage: UIImage?) -> SemanticStatusNodeAppearanceContext { + return SemanticStatusNodeAppearanceContext(background: self.background, foreground: self.foreground, backgroundImage: backgroundImage, overlayForeground: self.overlayForeground, cutout: cutout) + } + + func withUpdatedOverlayForeground(_ overlayForeground: UIColor?) -> SemanticStatusNodeAppearanceContext { + return SemanticStatusNodeAppearanceContext(background: self.background, foreground: self.foreground, backgroundImage: self.backgroundImage, overlayForeground: overlayForeground, cutout: cutout) + } + + func withUpdatedCutout(_ cutout: CGRect?) -> SemanticStatusNodeAppearanceContext { + return SemanticStatusNodeAppearanceContext(background: self.background, foreground: self.foreground, backgroundImage: self.backgroundImage, overlayForeground: self.overlayForeground, cutout: cutout) + } +} + +private final class SemanticStatusNodeAppearanceDrawingState { + let transitionFraction: CGFloat + let background: UIColor + let foreground: UIColor + let backgroundImage: UIImage? + let overlayForeground: UIColor? + let cutout: CGRect? + + var effectiveForegroundColor: UIColor { + if let _ = self.backgroundImage, let overlayForeground = self.overlayForeground { + return overlayForeground + } else { + return self.foreground + } + } + + init(transitionFraction: CGFloat, background: UIColor, foreground: UIColor, backgroundImage: UIImage?, overlayForeground: UIColor?, cutout: CGRect?) { + self.transitionFraction = transitionFraction + self.background = background + self.foreground = foreground + self.backgroundImage = backgroundImage + self.overlayForeground = overlayForeground + self.cutout = cutout + } + + func drawBackground(context: CGContext, size: CGSize) { + let bounds = CGRect(origin: CGPoint(), size: size) + + if let backgroundImage = self.backgroundImage?.cgImage { + context.saveGState() + context.translateBy(x: 0.0, y: bounds.height) + context.scaleBy(x: 1.0, y: -1.0) + context.setAlpha(self.transitionFraction) + context.draw(backgroundImage, in: bounds) + context.restoreGState() + } else { + context.setFillColor(self.background.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: bounds.size)) + } + } + + func drawForeground(context: CGContext, size: CGSize) { + if let cutout = self.cutout { + let size = CGSize(width: cutout.width * self.transitionFraction, height: cutout.height * self.transitionFraction) + let rect = CGRect(origin: CGPoint(x: cutout.midX - size.width / 2.0, y: cutout.midY - size.height / 2.0), size: size) + + context.setBlendMode(.clear) + context.fillEllipse(in: rect) + } } } private final class SemanticStatusNodeDrawingState: NSObject { - let background: UIColor - let foreground: UIColor - let hollow: Bool let transitionState: SemanticStatusNodeTransitionDrawingState? let drawingState: SemanticStatusNodeStateDrawingState - let backgroundImage: UIImage? - let overlayForeground: UIColor? - let cutout: SemanticStatusNode.Cutout? + let appearanceState: SemanticStatusNodeAppearanceDrawingState - init(background: UIColor, foreground: UIColor, hollow: Bool, transitionState: SemanticStatusNodeTransitionDrawingState?, drawingState: SemanticStatusNodeStateDrawingState, backgroundImage: UIImage?, overlayForeground: UIColor?, cutout: SemanticStatusNode.Cutout?) { - self.background = background - self.foreground = foreground - self.hollow = hollow + init(transitionState: SemanticStatusNodeTransitionDrawingState?, drawingState: SemanticStatusNodeStateDrawingState, appearanceState: SemanticStatusNodeAppearanceDrawingState) { self.transitionState = transitionState self.drawingState = drawingState - self.backgroundImage = backgroundImage - self.overlayForeground = overlayForeground - self.cutout = cutout + self.appearanceState = appearanceState super.init() } @@ -601,65 +685,86 @@ private final class SemanticStatusNodeDrawingState: NSObject { private final class SemanticStatusNodeTransitionContext { let startTime: Double let duration: Double - let previousStateContext: SemanticStatusNodeStateContext + let previousStateContext: SemanticStatusNodeStateContext? + let previousAppearanceContext: SemanticStatusNodeAppearanceContext? let completion: () -> Void - init(startTime: Double, duration: Double, previousStateContext: SemanticStatusNodeStateContext, completion: @escaping () -> Void) { + init(startTime: Double, duration: Double, previousStateContext: SemanticStatusNodeStateContext?, previousAppearanceContext: SemanticStatusNodeAppearanceContext?, completion: @escaping () -> Void) { self.startTime = startTime self.duration = duration self.previousStateContext = previousStateContext + self.previousAppearanceContext = previousAppearanceContext self.completion = completion } } public final class SemanticStatusNode: ASControlNode { - final class Cutout { - - } - public var backgroundNodeColor: UIColor { - didSet { - if !self.backgroundNodeColor.isEqual(oldValue) { + get { + return self.appearanceContext.background + } + set { + if !self.appearanceContext.background.isEqual(newValue) { + self.appearanceContext = self.appearanceContext.withUpdatedBackground(newValue) self.setNeedsDisplay() } } } public var foregroundNodeColor: UIColor { - didSet { - if !self.foregroundNodeColor.isEqual(oldValue) { + get { + return self.appearanceContext.foreground + } + set { + if !self.appearanceContext.foreground.isEqual(newValue) { + self.appearanceContext = self.appearanceContext.withUpdatedForeground(newValue) self.setNeedsDisplay() } } } public var overlayForegroundNodeColor: UIColor? { - didSet { - if !(self.overlayForegroundNodeColor?.isEqual(oldValue) ?? true) { + get { + return self.appearanceContext.overlayForeground + } + set { + if !(self.appearanceContext.overlayForeground?.isEqual(newValue) ?? false) { + self.appearanceContext = self.appearanceContext.withUpdatedOverlayForeground(newValue) self.setNeedsDisplay() } } } - private let hollow: Bool + public var cutout: CGRect? { + get { + return self.appearanceContext.cutout + } + set { + if self.appearanceContext.cutout != newValue { + self.transitionContext = SemanticStatusNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousStateContext: nil, previousAppearanceContext: self.appearanceContext, completion: {}) + self.appearanceContext = self.appearanceContext.withUpdatedCutout(newValue) + + self.updateAnimations() + self.setNeedsDisplay() + } + } + } private var animator: ConstantDisplayLinkAnimator? private var hasState: Bool = false public private(set) var state: SemanticStatusNodeState - private var transtionContext: SemanticStatusNodeTransitionContext? + private var transitionContext: SemanticStatusNodeTransitionContext? private var stateContext: SemanticStatusNodeStateContext + private var appearanceContext: SemanticStatusNodeAppearanceContext private var disposable: Disposable? private var backgroundNodeImage: UIImage? - public init(backgroundNodeColor: UIColor, foregroundNodeColor: UIColor, image: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? = nil, overlayForegroundNodeColor: UIColor? = nil, hollow: Bool = false) { - self.backgroundNodeColor = backgroundNodeColor - self.foregroundNodeColor = foregroundNodeColor - self.overlayForegroundNodeColor = overlayForegroundNodeColor - self.hollow = hollow + public init(backgroundNodeColor: UIColor, foregroundNodeColor: UIColor, image: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? = nil, overlayForegroundNodeColor: UIColor? = nil, cutout: CGRect? = nil) { self.state = .none self.stateContext = self.state.context(current: nil) + self.appearanceContext = SemanticStatusNodeAppearanceContext(background: backgroundNodeColor, foreground: foregroundNodeColor, backgroundImage: nil, overlayForeground: overlayForegroundNodeColor, cutout: cutout) super.init() @@ -667,14 +772,22 @@ public final class SemanticStatusNode: ASControlNode { self.displaysAsynchronously = true if let image = image { + let start = CACurrentMediaTime() self.disposable = (image |> deliverOnMainQueue).start(next: { [weak self] transform in guard let strongSelf = self else { return } let context = transform(TransformImageArguments(corners: ImageCorners(radius: strongSelf.bounds.width / 2.0), imageSize: strongSelf.bounds.size, boundingSize: strongSelf.bounds.size, intrinsicInsets: UIEdgeInsets())) - self?.backgroundNodeImage = context?.generateImage() - self?.setNeedsDisplay() + + let previousAppearanceContext = strongSelf.appearanceContext + strongSelf.appearanceContext = strongSelf.appearanceContext.withUpdatedBackgroundImage(context?.generateImage()) + + if CACurrentMediaTime() - start > 0.2 { + strongSelf.transitionContext = SemanticStatusNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousStateContext: nil, previousAppearanceContext: previousAppearanceContext, completion: {}) + strongSelf.updateAnimations() + } + strongSelf.setNeedsDisplay() }) } } @@ -687,9 +800,9 @@ public final class SemanticStatusNode: ASControlNode { var animate = false let timestamp = CACurrentMediaTime() - if let transtionContext = self.transtionContext { + if let transtionContext = self.transitionContext { if transtionContext.startTime + transtionContext.duration < timestamp { - self.transtionContext = nil + self.transitionContext = nil transtionContext.completion() } else { animate = true @@ -724,13 +837,13 @@ public final class SemanticStatusNode: ASControlNode { self.hasState = true animated = false } - if self.state != state { + if self.state != state || self.appearanceContext.cutout != cutout { self.state = state let previousStateContext = self.stateContext self.stateContext = self.state.context(current: self.stateContext) if animated && previousStateContext !== self.stateContext { - self.transtionContext = SemanticStatusNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousStateContext: previousStateContext, completion: completion) + self.transitionContext = SemanticStatusNodeTransitionContext(startTime: CACurrentMediaTime(), duration: 0.18, previousStateContext: previousStateContext, previousAppearanceContext: nil, completion: completion) } else { completion() } @@ -745,15 +858,23 @@ public final class SemanticStatusNode: ASControlNode { override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { var transitionState: SemanticStatusNodeTransitionDrawingState? var transitionFraction: CGFloat = 1.0 - if let transitionContext = self.transtionContext { + var appearanceTransitionFraction: CGFloat = 1.0 + + if let transitionContext = self.transitionContext { let timestamp = CACurrentMediaTime() var t = CGFloat((timestamp - transitionContext.startTime) / transitionContext.duration) t = min(1.0, max(0.0, t)) - transitionFraction = t - transitionState = SemanticStatusNodeTransitionDrawingState(transition: t, drawingState: transitionContext.previousStateContext.drawingState(transitionFraction: 1.0 - t)) + + if let _ = transitionContext.previousStateContext { + transitionFraction = t + } + if let _ = transitionContext.previousAppearanceContext { + appearanceTransitionFraction = t + } + transitionState = SemanticStatusNodeTransitionDrawingState(transition: t, drawingState: transitionContext.previousStateContext?.drawingState(transitionFraction: 1.0 - t), appearanceState: transitionContext.previousAppearanceContext?.drawingState(transitionFraction: 1.0 - t)) } - return SemanticStatusNodeDrawingState(background: self.backgroundNodeColor, foreground: self.foregroundNodeColor, hollow: self.hollow, transitionState: transitionState, drawingState: self.stateContext.drawingState(transitionFraction: transitionFraction), backgroundImage: self.backgroundNodeImage, overlayForeground: self.overlayForegroundNodeColor, cutout: nil) + return SemanticStatusNodeDrawingState(transitionState: transitionState, drawingState: self.stateContext.drawingState(transitionFraction: transitionFraction), appearanceState: self.appearanceContext.drawingState(transitionFraction: appearanceTransitionFraction)) } @objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { @@ -768,32 +889,20 @@ public final class SemanticStatusNode: ASControlNode { guard let parameters = parameters as? SemanticStatusNodeDrawingState else { return } - - var foregroundColor = parameters.foreground - if let backgroundImage = parameters.backgroundImage?.cgImage { - context.saveGState() - context.translateBy(x: 0.0, y: bounds.height) - context.scaleBy(x: 1.0, y: -1.0) - context.draw(backgroundImage, in: bounds) - context.restoreGState() - - if let overlayForegroundColor = parameters.overlayForeground { - foregroundColor = overlayForegroundColor - } else { - foregroundColor = .white - } - } else { - context.setFillColor(parameters.background.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: bounds.size)) + + if let transitionAppearanceState = parameters.transitionState?.appearanceState { + transitionAppearanceState.drawBackground(context: context, size: bounds.size) } - if let transitionState = parameters.transitionState { - transitionState.drawingState.draw(context: context, size: bounds.size, foregroundColor: foregroundColor) + parameters.appearanceState.drawBackground(context: context, size: bounds.size) + + if let transitionDrawingState = parameters.transitionState?.drawingState { + transitionDrawingState.draw(context: context, size: bounds.size, foregroundColor: parameters.appearanceState.effectiveForegroundColor) } - parameters.drawingState.draw(context: context, size: bounds.size, foregroundColor: foregroundColor) + parameters.drawingState.draw(context: context, size: bounds.size, foregroundColor: parameters.appearanceState.effectiveForegroundColor) - if parameters.hollow { - context.setBlendMode(.clear) - context.fillEllipse(in: bounds.insetBy(dx: 8.0, dy: 8.0)) + if let transitionAppearanceState = parameters.transitionState?.appearanceState { + transitionAppearanceState.drawForeground(context: context, size: bounds.size) } + parameters.appearanceState.drawForeground(context: context, size: bounds.size) } } diff --git a/submodules/TelegramCallsUI/Sources/CallControllerButton.swift b/submodules/TelegramCallsUI/Sources/CallControllerButton.swift index 6f8c40f51a..d560a70105 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerButton.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerButton.swift @@ -133,14 +133,15 @@ final class CallControllerButtonItemNode: HighlightTrackingButtonNode { self.currentContent = content if content.hasProgress { + let statusFrame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize)) if self.statusNode == nil { - let statusNode = SemanticStatusNode(backgroundNodeColor: .white, foregroundNodeColor: .clear, hollow: true) + let statusNode = SemanticStatusNode(backgroundNodeColor: .white, foregroundNodeColor: .clear, cutout: statusFrame.insetBy(dx: 8.0, dy: 8.0)) self.statusNode = statusNode self.contentContainer.insertSubnode(statusNode, belowSubnode: self.contentNode) statusNode.transitionToState(.progress(value: nil, cancelEnabled: false, appearance: SemanticStatusNodeState.ProgressAppearance(inset: 4.0, lineWidth: 3.0)), animated: false, completion: {}) } if let statusNode = self.statusNode { - statusNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize)) + statusNode.frame = statusFrame if transition.isAnimated { statusNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) statusNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) diff --git a/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift index 77f826c1fd..bcd4f4d3f1 100644 --- a/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessageUploadedContent.swift @@ -146,12 +146,15 @@ func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods: if let _ = map.heading { flags |= 1 << 2 } + if let _ = map.liveProximityNotificationRadius { + flags |= 1 << 3 + } var geoFlags: Int32 = 0 if let _ = map.accuracyRadius { geoFlags |= 1 << 0 } if let liveBroadcastingTimeout = map.liveBroadcastingTimeout { - input = .inputMediaGeoLive(flags: flags, geoPoint: Api.InputGeoPoint.inputGeoPoint(flags: geoFlags, lat: map.latitude, long: map.longitude, accuracyRadius: map.accuracyRadius.flatMap({ Int32($0) })), heading: map.heading, period: liveBroadcastingTimeout, proximityNotificationRadius: nil) + input = .inputMediaGeoLive(flags: flags, geoPoint: Api.InputGeoPoint.inputGeoPoint(flags: geoFlags, lat: map.latitude, long: map.longitude, accuracyRadius: map.accuracyRadius.flatMap({ Int32($0) })), heading: map.heading, period: liveBroadcastingTimeout, proximityNotificationRadius: map.liveProximityNotificationRadius.flatMap({ Int32($0) })) } else if let venue = map.venue { input = .inputMediaVenue(geoPoint: Api.InputGeoPoint.inputGeoPoint(flags: geoFlags, lat: map.latitude, long: map.longitude, accuracyRadius: map.accuracyRadius.flatMap({ Int32($0) })), title: venue.title, address: venue.address ?? "", provider: venue.provider ?? "", venueId: venue.id ?? "", venueType: venue.type ?? "") } else { diff --git a/submodules/TelegramCore/Sources/RequestEditMessage.swift b/submodules/TelegramCore/Sources/RequestEditMessage.swift index d3c766231f..b0a1cbca02 100644 --- a/submodules/TelegramCore/Sources/RequestEditMessage.swift +++ b/submodules/TelegramCore/Sources/RequestEditMessage.swift @@ -255,7 +255,7 @@ private func requestEditMessageInternal(postbox: Postbox, network: Network, stat } } -public func requestEditLiveLocation(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId, coordinate: (latitude: Double, longitude: Double, accuracyRadius: Int32?)?, heading: Int32?) -> Signal { +public func requestEditLiveLocation(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId, stop: Bool, coordinate: (latitude: Double, longitude: Double, accuracyRadius: Int32?)?, heading: Int32?, proximityNotificationRadius: Int32?) -> Signal { return postbox.transaction { transaction -> (Api.InputPeer, TelegramMediaMap)? in guard let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else { return nil @@ -275,19 +275,33 @@ public func requestEditLiveLocation(postbox: Postbox, network: Network, stateMan return .complete() } let inputMedia: Api.InputMedia - if let coordinate = coordinate, let liveBroadcastingTimeout = media.liveBroadcastingTimeout { + if let liveBroadcastingTimeout = media.liveBroadcastingTimeout, !stop { var flags: Int32 = 1 << 1 - if let _ = media.heading { + let inputGeoPoint: Api.InputGeoPoint + if let coordinate = coordinate { + var geoFlags: Int32 = 0 + if let _ = coordinate.accuracyRadius { + geoFlags |= 1 << 0 + } + inputGeoPoint = .inputGeoPoint(flags: geoFlags, lat: coordinate.latitude, long: coordinate.longitude, accuracyRadius: coordinate.accuracyRadius.flatMap({ Int32($0) })) + } else { + var geoFlags: Int32 = 0 + if let _ = media.accuracyRadius { + geoFlags |= 1 << 0 + } + inputGeoPoint = .inputGeoPoint(flags: geoFlags, lat: media.latitude, long: media.longitude, accuracyRadius: media.accuracyRadius.flatMap({ Int32($0) })) + } + if let _ = heading { flags |= 1 << 2 } - var geoFlags: Int32 = 0 - if let _ = coordinate.accuracyRadius { - geoFlags |= 1 << 0 + if let _ = proximityNotificationRadius { + flags |= 1 << 3 } - inputMedia = .inputMediaGeoLive(flags: flags, geoPoint: .inputGeoPoint(flags: geoFlags, lat: coordinate.latitude, long: coordinate.longitude, accuracyRadius: coordinate.accuracyRadius.flatMap({ Int32($0) })), heading: heading, period: liveBroadcastingTimeout, proximityNotificationRadius: nil) + inputMedia = .inputMediaGeoLive(flags: flags, geoPoint: inputGeoPoint, heading: heading, period: liveBroadcastingTimeout, proximityNotificationRadius: proximityNotificationRadius) } else { inputMedia = .inputMediaGeoLive(flags: 1 << 0, geoPoint: .inputGeoPoint(flags: 0, lat: media.latitude, long: media.longitude, accuracyRadius: nil), heading: nil, period: nil, proximityNotificationRadius: nil) } + return network.request(Api.functions.messages.editMessage(flags: 1 << 14, peer: inputPeer, id: messageId.id, message: nil, media: inputMedia, replyMarkup: nil, entities: nil, scheduleDate: nil)) |> map(Optional.init) |> `catch` { _ -> Signal in @@ -297,7 +311,7 @@ public func requestEditLiveLocation(postbox: Postbox, network: Network, stateMan if let updates = updates { stateManager.addUpdates(updates) } - if coordinate == nil { + if coordinate == nil && proximityNotificationRadius == nil { return postbox.transaction { transaction -> Void in transaction.updateMessage(messageId, update: { currentMessage in var storeForwardInfo: StoreMessageForwardInfo? diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift index 52a20db328..681d0e91df 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift @@ -77,6 +77,7 @@ public func chatBubbleActionButtonImage(fillColor: UIColor, strokeColor: UIColor public final class PrincipalThemeEssentialGraphics { public let chatMessageBackgroundIncomingMaskImage: UIImage + public let chatMessageBackgroundIncomingExtractedMaskImage: UIImage public let chatMessageBackgroundIncomingImage: UIImage public let chatMessageBackgroundIncomingExtractedImage: UIImage public let chatMessageBackgroundIncomingOutlineImage: UIImage @@ -110,6 +111,7 @@ public final class PrincipalThemeEssentialGraphics { public let chatMessageBackgroundIncomingMergedSideHighlightedImage: UIImage public let chatMessageBackgroundOutgoingMaskImage: UIImage + public let chatMessageBackgroundOutgoingExtractedMaskImage: UIImage public let chatMessageBackgroundOutgoingImage: UIImage public let chatMessageBackgroundOutgoingExtractedImage: UIImage public let chatMessageBackgroundOutgoingOutlineImage: UIImage @@ -248,12 +250,14 @@ public final class PrincipalThemeEssentialGraphics { let emptyImage = UIImage() if preview { self.chatMessageBackgroundIncomingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: UIColor.black, strokeColor: UIColor.clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) + self.chatMessageBackgroundIncomingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: UIColor.black, strokeColor: UIColor.clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundIncomingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) self.chatMessageBackgroundIncomingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) self.chatMessageBackgroundIncomingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundIncomingExtractedOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) self.chatMessageBackgroundIncomingShadowImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyShadow: true) self.chatMessageBackgroundOutgoingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) + self.chatMessageBackgroundOutgoingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundOutgoingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) @@ -350,6 +354,7 @@ public final class PrincipalThemeEssentialGraphics { self.radialIndicatorFileIconOutgoing = emptyImage } else { self.chatMessageBackgroundIncomingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) + self.chatMessageBackgroundIncomingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: .black, strokeColor: .clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundIncomingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) self.chatMessageBackgroundIncomingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) self.chatMessageBackgroundIncomingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.fill, strokeColor: incoming.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true, onlyOutline: true) @@ -378,6 +383,7 @@ public final class PrincipalThemeEssentialGraphics { self.chatMessageBackgroundIncomingMergedBothHighlightedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: true, fillColor: incoming.highlightedFill, strokeColor: incoming.stroke, neighbors: .both, theme: theme, wallpaper: wallpaper, knockout: incomingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .none, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) + self.chatMessageBackgroundOutgoingExtractedMaskImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: .black, strokeColor: .clear, neighbors: .extracted, theme: theme, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true) self.chatMessageBackgroundOutgoingImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingExtractedImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .extracted, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true) self.chatMessageBackgroundOutgoingOutlineImage = messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: false, fillColor: outgoing.fill, strokeColor: outgoing.stroke, neighbors: .none, theme: theme, wallpaper: wallpaper, knockout: outgoingKnockout, extendedEdges: true, onlyOutline: true) diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleBackdrop.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleBackdrop.swift index d307bb17f2..925d55c693 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleBackdrop.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleBackdrop.swift @@ -28,7 +28,7 @@ func bubbleMaskForType(_ type: ChatMessageBackgroundType, graphics: PrincipalThe case .Side: image = graphics.chatMessageBackgroundIncomingMergedSideMaskImage case .Extracted: - image = nil + image = graphics.chatMessageBackgroundIncomingExtractedMaskImage } case let .outgoing(mergeType): switch mergeType { @@ -47,7 +47,7 @@ func bubbleMaskForType(_ type: ChatMessageBackgroundType, graphics: PrincipalThe case .Side: image = graphics.chatMessageBackgroundOutgoingMergedSideMaskImage case .Extracted: - image = nil + image = graphics.chatMessageBackgroundOutgoingExtractedMaskImage } } return image diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index a2346cffdd..061fabaad8 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -214,10 +214,11 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode let contentMessageStableId: UInt32 let sourceNode: ContextExtractedContentContainingNode let containerNode: ContextControllerSourceNode + var backgroundWallpaperNode: ChatMessageBubbleBackdrop? var backgroundNode: ChatMessageBackground? var selectionBackgroundNode: ASDisplayNode? - var currentParams: (size: CGSize, contentOrigin: CGPoint, presentationData: ChatPresentationData, graphics: PrincipalThemeEssentialGraphics, backgroundType: ChatMessageBackgroundType, Bool?, selectionInsets: UIEdgeInsets)? + private var currentParams: (size: CGSize, contentOrigin: CGPoint, presentationData: ChatPresentationData, graphics: PrincipalThemeEssentialGraphics, backgroundType: ChatMessageBackgroundType, mediaBox: MediaBox, messageSelection: Bool?, selectionInsets: UIEdgeInsets)? init(contentMessageStableId: UInt32) { self.contentMessageStableId = contentMessageStableId @@ -226,7 +227,31 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode self.containerNode = ContextControllerSourceNode() } - func willUpdateIsExtractedToContextPreview(isExtractedToContextPreview: Bool, transition: ContainedViewLayoutTransition) { + private var absoluteRect: (CGRect, CGSize)? + fileprivate func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) { + self.absoluteRect = (rect, containerSize) + guard let backgroundWallpaperNode = self.backgroundWallpaperNode else { + return + } + let mappedRect = CGRect(origin: CGPoint(x: rect.minX + backgroundWallpaperNode.frame.minX, y: rect.minY + backgroundWallpaperNode.frame.minY), size: rect.size) + backgroundWallpaperNode.update(rect: mappedRect, within: containerSize) + } + + fileprivate func applyAbsoluteOffset(value: CGFloat, animationCurve: ContainedViewLayoutTransitionCurve, duration: Double) { + guard let backgroundWallpaperNode = self.backgroundWallpaperNode else { + return + } + backgroundWallpaperNode.offset(value: value, animationCurve: animationCurve, duration: duration) + } + + fileprivate func applyAbsoluteOffsetSpring(value: CGFloat, duration: Double, damping: CGFloat) { + guard let backgroundWallpaperNode = self.backgroundWallpaperNode else { + return + } + backgroundWallpaperNode.offsetSpring(value: value, duration: duration, damping: damping) + } + + fileprivate func willUpdateIsExtractedToContextPreview(isExtractedToContextPreview: Bool, transition: ContainedViewLayoutTransition) { if isExtractedToContextPreview { var offset: CGFloat = 0.0 var inset: CGFloat = 0.0 @@ -242,34 +267,57 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode if let _ = self.backgroundNode { } else if let currentParams = self.currentParams { + let backgroundWallpaperNode = ChatMessageBubbleBackdrop() + backgroundWallpaperNode.alpha = 0.0 + let backgroundNode = ChatMessageBackground() backgroundNode.alpha = 0.0 - - backgroundNode.setType(type: type, highlighted: false, graphics: currentParams.graphics, maskMode: false, hasWallpaper: currentParams.presentationData.theme.wallpaper.hasWallpaper, transition: .immediate) + self.sourceNode.contentNode.insertSubnode(backgroundNode, at: 0) + self.sourceNode.contentNode.insertSubnode(backgroundWallpaperNode, at: 0) + + self.backgroundWallpaperNode = backgroundWallpaperNode self.backgroundNode = backgroundNode transition.updateAlpha(node: backgroundNode, alpha: 1.0) + transition.updateAlpha(node: backgroundWallpaperNode, alpha: 1.0) + + backgroundNode.setType(type: type, highlighted: false, graphics: currentParams.graphics, maskMode: true, hasWallpaper: currentParams.presentationData.theme.wallpaper.hasWallpaper, transition: .immediate) + backgroundWallpaperNode.setType(type: type, theme: currentParams.presentationData.theme, mediaBox: currentParams.mediaBox, essentialGraphics: currentParams.graphics, maskMode: true) } if let currentParams = self.currentParams { let backgroundFrame = CGRect(x: currentParams.contentOrigin.x + offset, y: 0.0, width: currentParams.size.width + inset, height: currentParams.size.height) self.backgroundNode?.updateLayout(size: backgroundFrame.size, transition: .immediate) self.backgroundNode?.frame = backgroundFrame + self.backgroundWallpaperNode?.frame = backgroundFrame + + if let (rect, containerSize) = self.absoluteRect { + let mappedRect = CGRect(origin: CGPoint(x: rect.minX + backgroundFrame.minX, y: rect.minY + backgroundFrame.minY), size: rect.size) + self.backgroundWallpaperNode?.update(rect: mappedRect, within: containerSize) + } + } + } else { + if let backgroundNode = self.backgroundNode { + self.backgroundNode = nil + transition.updateAlpha(node: backgroundNode, alpha: 0.0, completion: { [weak backgroundNode] _ in + backgroundNode?.removeFromSupernode() + }) + } + if let backgroundWallpaperNode = self.backgroundWallpaperNode { + self.backgroundWallpaperNode = nil + transition.updateAlpha(node: backgroundWallpaperNode, alpha: 0.0, completion: { [weak backgroundWallpaperNode] _ in + backgroundWallpaperNode?.removeFromSupernode() + }) } - } else if let backgroundNode = self.backgroundNode { - self.backgroundNode = nil - transition.updateAlpha(node: backgroundNode, alpha: 0.0, completion: { [weak backgroundNode] _ in - backgroundNode?.removeFromSupernode() - }) } } - func isExtractedToContextPreviewUpdated(_ isExtractedToContextPreview: Bool) { + fileprivate func isExtractedToContextPreviewUpdated(_ isExtractedToContextPreview: Bool) { } - func update(size: CGSize, contentOrigin: CGPoint, selectionInsets: UIEdgeInsets, index: Int, presentationData: ChatPresentationData, graphics: PrincipalThemeEssentialGraphics, backgroundType: ChatMessageBackgroundType, messageSelection: Bool?) { - self.currentParams = (size, contentOrigin, presentationData, graphics, backgroundType, messageSelection, selectionInsets) + fileprivate func update(size: CGSize, contentOrigin: CGPoint, selectionInsets: UIEdgeInsets, index: Int, presentationData: ChatPresentationData, graphics: PrincipalThemeEssentialGraphics, backgroundType: ChatMessageBackgroundType, mediaBox: MediaBox, messageSelection: Bool?) { + self.currentParams = (size, contentOrigin, presentationData, graphics, backgroundType, mediaBox, messageSelection, selectionInsets) let bounds = CGRect(origin: CGPoint(), size: size) var incoming: Bool = false @@ -2314,9 +2362,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode container?.isExtractedToContextPreviewUpdated(isExtractedToContextPreview) -// if !isExtractedToContextPreview, let (rect, size) = strongSelf.absoluteRect { -// strongSelf.updateAbsoluteRect(rect, within: size) -// } + if !isExtractedToContextPreview, let (rect, size) = strongSelf.absoluteRect { + container?.updateAbsoluteRect(relativeFrame.offsetBy(dx: rect.minX, dy: rect.minY), within: size) + } for contentNode in strongSelf.contentNodes { if contentNode.supernode === strongContextSourceNode.contentNode { @@ -2325,23 +2373,23 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode } } - contextSourceNode.updateAbsoluteRect = { [weak strongSelf] rect, size in - guard let strongSelf = strongSelf, strongSelf.mainContextSourceNode.isExtractedToContextPreview else { + contextSourceNode.updateAbsoluteRect = { [weak strongSelf, weak container, weak contextSourceNode] rect, size in + guard let strongSelf = strongSelf, let strongContextSourceNode = contextSourceNode, strongContextSourceNode.isExtractedToContextPreview else { return } -// strongSelf.updateAbsoluteRectInternal(rect, within: size) + container?.updateAbsoluteRect(relativeFrame.offsetBy(dx: rect.minX, dy: rect.minY), within: size) } - contextSourceNode.applyAbsoluteOffset = { [weak strongSelf] value, animationCurve, duration in - guard let strongSelf = strongSelf, strongSelf.mainContextSourceNode.isExtractedToContextPreview else { + contextSourceNode.applyAbsoluteOffset = { [weak strongSelf, weak container, weak contextSourceNode] value, animationCurve, duration in + guard let strongSelf = strongSelf, let strongContextSourceNode = contextSourceNode, strongContextSourceNode.isExtractedToContextPreview else { return } -// strongSelf.applyAbsoluteOffsetInternal(value: value, animationCurve: animationCurve, duration: duration) + container?.applyAbsoluteOffset(value: value, animationCurve: animationCurve, duration: duration) } - contextSourceNode.applyAbsoluteOffsetSpring = { [weak strongSelf] value, duration, damping in - guard let strongSelf = strongSelf, strongSelf.mainContextSourceNode.isExtractedToContextPreview else { + contextSourceNode.applyAbsoluteOffsetSpring = { [weak strongSelf, weak container, weak contextSourceNode] value, duration, damping in + guard let strongSelf = strongSelf, let strongContextSourceNode = contextSourceNode, strongContextSourceNode.isExtractedToContextPreview else { return } -// strongSelf.applyAbsoluteOffsetSpringInternal(value: value, duration: duration, damping: damping) + container?.applyAbsoluteOffsetSpring(value: value, duration: duration, damping: damping) } strongSelf.contentContainers.append(container) @@ -2371,7 +2419,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode selectionInsets.bottom = groupOverlap / 2.0 } - contentContainer?.update(size: relativeFrame.size, contentOrigin: contentOrigin, selectionInsets: selectionInsets, index: index, presentationData: item.presentationData, graphics: graphics, backgroundType: backgroundType, messageSelection: itemSelection) + contentContainer?.update(size: relativeFrame.size, contentOrigin: contentOrigin, selectionInsets: selectionInsets, index: index, presentationData: item.presentationData, graphics: graphics, backgroundType: backgroundType, mediaBox: item.context.account.postbox.mediaBox, messageSelection: itemSelection) index += 1 } diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift index bfe28fa2f5..1fbd931715 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveFileNode.swift @@ -24,7 +24,6 @@ private struct FetchControls { final class ChatMessageInteractiveFileNode: ASDisplayNode { private var selectionNode: FileMessageSelectionNode? - private var cutoutNode: ASDisplayNode? private let titleNode: TextNode private let descriptionNode: TextNode @@ -974,34 +973,13 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode { } } + let cutoutFrame = streamingCacheStatusFrame.insetBy(dx: -(1.0 + UIScreenPixel), dy: -(1.0 + UIScreenPixel)).offsetBy(dx: progressFrame.minX - 6.0, dy: progressFrame.minY) + + if streamingState == .none && self.selectionNode == nil { - if let cutoutNode = self.cutoutNode { - self.cutoutNode = nil - if animated { - cutoutNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false) { [weak cutoutNode] _ in - cutoutNode?.removeFromSupernode() - } - } else { - cutoutNode.removeFromSupernode() - } - } + self.statusNode?.cutout = nil } else if let statusNode = self.statusNode, (self.iconNode?.isHidden ?? true) { - if let _ = self.cutoutNode { - } else { - let cutoutNode = ASImageNode() - cutoutNode.displaysAsynchronously = false - cutoutNode.displayWithoutProcessing = true - cutoutNode.image = generateFilledCircleImage(diameter: 23.0, color: messageTheme.bubble.withWallpaper.fill) - - self.cutoutNode = cutoutNode - self.insertSubnode(cutoutNode, aboveSubnode: statusNode) - - cutoutNode.frame = streamingCacheStatusFrame.insetBy(dx: -(1.0 + UIScreenPixel), dy: -(1.0 + UIScreenPixel)) - - if animated { - cutoutNode.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2) - } - } + self.statusNode?.cutout = cutoutFrame } if let (expandedString, compactString, font) = downloadingStrings {