mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Merge commit '88d91ee7748714b9c1e2b3868f1b3acb942a8ed8'
This commit is contained in:
commit
618306b65e
@ -14496,3 +14496,5 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Chat.Todo.PremiumRequired" = "Only [Telegram Premium]() subscribers can mark tasks as done.";
|
||||
"Chat.Todo.CompletionLimited" = "%@ has restricted others from editing this to do list.";
|
||||
|
||||
"Forward.ErrorTodoDisabledInChannels" = "Sorry, to-do lists can’t be forwarded to channels.";
|
||||
|
@ -287,6 +287,8 @@ public class CheckLayer: CALayer {
|
||||
}
|
||||
}
|
||||
|
||||
public var animateScale = true
|
||||
|
||||
public var selected = false
|
||||
public func setSelected(_ selected: Bool, animated: Bool = false) {
|
||||
guard self.selected != selected else {
|
||||
@ -314,6 +316,7 @@ public class CheckLayer: CALayer {
|
||||
animation.duration = selected ? 0.21 : 0.15
|
||||
self.pop_add(animation, forKey: "progress")
|
||||
|
||||
if self.animateScale {
|
||||
if selected {
|
||||
self.animateScale(from: 1.0, to: 0.9, duration: 0.08, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
guard let self else {
|
||||
@ -335,6 +338,7 @@ public class CheckLayer: CALayer {
|
||||
self.animateScale(from: 0.9, to: 1.0, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.pop_removeAllAnimations()
|
||||
self.animatingOut = false
|
||||
|
@ -2294,14 +2294,24 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
public final class Source {
|
||||
public let id: AnyHashable
|
||||
public let title: String
|
||||
public let footer: String?
|
||||
public let source: ContextContentSource
|
||||
public let items: Signal<ContextController.Items, NoError>
|
||||
public let closeActionTitle: String?
|
||||
public let closeAction: (() -> Void)?
|
||||
|
||||
public init(id: AnyHashable, title: String, source: ContextContentSource, items: Signal<ContextController.Items, NoError>, closeActionTitle: String? = nil, closeAction: (() -> Void)? = nil) {
|
||||
public init(
|
||||
id: AnyHashable,
|
||||
title: String,
|
||||
footer: String? = nil,
|
||||
source: ContextContentSource,
|
||||
items: Signal<ContextController.Items, NoError>,
|
||||
closeActionTitle: String? = nil,
|
||||
closeAction: (() -> Void)? = nil
|
||||
) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.footer = footer
|
||||
self.source = source
|
||||
self.items = items
|
||||
self.closeActionTitle = closeActionTitle
|
||||
|
@ -125,6 +125,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
|
||||
var animateClippingFromContentAreaInScreenSpace: CGRect?
|
||||
var storedGlobalFrame: CGRect?
|
||||
var storedGlobalBoundsFrame: CGRect?
|
||||
|
||||
init(containingItem: ContextControllerTakeViewInfo.ContainingItem) {
|
||||
self.offsetContainerNode = ASDisplayNode()
|
||||
@ -772,6 +773,12 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
switch stateTransition {
|
||||
case .animateIn, .animateOut:
|
||||
contentNode.storedGlobalFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view)
|
||||
|
||||
var rect = convertFrame(contentNode.containingItem.view.bounds, from: contentNode.containingItem.view, to: self.view)
|
||||
if rect.origin.x < 0.0 {
|
||||
rect.origin.x += layout.size.width
|
||||
}
|
||||
contentNode.storedGlobalBoundsFrame = rect
|
||||
case .none:
|
||||
if contentNode.storedGlobalFrame == nil {
|
||||
contentNode.storedGlobalFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view)
|
||||
@ -803,13 +810,14 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
case .extracted:
|
||||
if let contentNode = itemContentNode {
|
||||
contentParentGlobalFrame = convertFrame(contentNode.containingItem.view.bounds, from: contentNode.containingItem.view, to: self.view)
|
||||
|
||||
if let frame = contentNode.storedGlobalBoundsFrame {
|
||||
contentParentGlobalFrame.origin.x = frame.minX
|
||||
}
|
||||
let contentRectGlobalFrame = CGRect(origin: CGPoint(x: contentNode.containingItem.contentRect.minX, y: (contentNode.storedGlobalFrame?.maxY ?? 0.0) - contentNode.containingItem.contentRect.height), size: contentNode.containingItem.contentRect.size)
|
||||
contentRect = CGRect(origin: CGPoint(x: contentRectGlobalFrame.minX, y: contentRectGlobalFrame.maxY - contentNode.containingItem.contentRect.size.height), size: contentNode.containingItem.contentRect.size)
|
||||
if case .animateOut = stateTransition {
|
||||
contentRect.origin.y = self.contentRectDebugNode.frame.maxY - contentRect.size.height
|
||||
}
|
||||
//contentRect.size.height = 200.0
|
||||
} else {
|
||||
return
|
||||
}
|
||||
@ -1454,6 +1462,9 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
||||
|
||||
if let contentNode = itemContentNode {
|
||||
currentContentScreenFrame = convertFrame(contentNode.containingItem.contentRect, from: contentNode.containingItem.view, to: self.view)
|
||||
if currentContentScreenFrame.origin.x < 0.0 {
|
||||
contentParentGlobalFrameOffsetX = layout.size.width
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import ReactionSelectionNode
|
||||
import ComponentFlow
|
||||
import TabSelectorComponent
|
||||
import PlainButtonComponent
|
||||
import MultilineTextComponent
|
||||
import ComponentDisplayAdapters
|
||||
import AccountContext
|
||||
|
||||
@ -17,6 +18,7 @@ final class ContextSourceContainer: ASDisplayNode {
|
||||
|
||||
let id: AnyHashable
|
||||
let title: String
|
||||
let footer: String?
|
||||
let context: AccountContext?
|
||||
let source: ContextContentSource
|
||||
let closeActionTitle: String?
|
||||
@ -44,6 +46,7 @@ final class ContextSourceContainer: ASDisplayNode {
|
||||
controller: ContextController,
|
||||
id: AnyHashable,
|
||||
title: String,
|
||||
footer: String?,
|
||||
context: AccountContext?,
|
||||
source: ContextContentSource,
|
||||
items: Signal<ContextController.Items, NoError>,
|
||||
@ -53,6 +56,7 @@ final class ContextSourceContainer: ASDisplayNode {
|
||||
self.controller = controller
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.footer = footer
|
||||
self.context = context
|
||||
self.source = source
|
||||
self.closeActionTitle = closeActionTitle
|
||||
@ -362,6 +366,7 @@ final class ContextSourceContainer: ASDisplayNode {
|
||||
var activeIndex: Int = 0
|
||||
|
||||
private var tabSelector: ComponentView<Empty>?
|
||||
private var footer: ComponentView<Empty>?
|
||||
private var closeButton: ComponentView<Empty>?
|
||||
|
||||
private var presentationData: PresentationData?
|
||||
@ -397,6 +402,7 @@ final class ContextSourceContainer: ASDisplayNode {
|
||||
controller: controller,
|
||||
id: source.id,
|
||||
title: source.title,
|
||||
footer: source.footer,
|
||||
context: context,
|
||||
source: source.source,
|
||||
items: source.items,
|
||||
@ -476,8 +482,14 @@ final class ContextSourceContainer: ASDisplayNode {
|
||||
func animateIn() {
|
||||
self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
|
||||
if let activeSource = self.activeSource {
|
||||
activeSource.animateIn()
|
||||
// if let activeSource = self.activeSource {
|
||||
// activeSource.animateIn()
|
||||
// }
|
||||
for source in self.sources {
|
||||
source.animateIn()
|
||||
}
|
||||
if let footerView = self.footer?.view {
|
||||
footerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
}
|
||||
if let tabSelectorView = self.tabSelector?.view {
|
||||
tabSelectorView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
@ -500,6 +512,9 @@ final class ContextSourceContainer: ASDisplayNode {
|
||||
}
|
||||
})
|
||||
|
||||
if let footerView = self.footer?.view {
|
||||
footerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, delay: delay, removeOnCompletion: false)
|
||||
}
|
||||
if let tabSelectorView = self.tabSelector?.view {
|
||||
tabSelectorView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, delay: delay, removeOnCompletion: false)
|
||||
}
|
||||
@ -507,6 +522,12 @@ final class ContextSourceContainer: ASDisplayNode {
|
||||
closeButtonView.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, delay: delay, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
for source in self.sources {
|
||||
if source !== self.activeSource {
|
||||
source.animateOut(result: result, completion: {})
|
||||
}
|
||||
}
|
||||
|
||||
if let activeSource = self.activeSource {
|
||||
activeSource.animateOut(result: result, completion: delayDismissal ? {} : completion)
|
||||
} else {
|
||||
@ -671,6 +692,49 @@ final class ContextSourceContainer: ASDisplayNode {
|
||||
)
|
||||
childLayout.intrinsicInsets.bottom += 30.0
|
||||
|
||||
if let footerText = self.activeSource?.footer {
|
||||
var footerTransition = transition
|
||||
let footer: ComponentView<Empty>
|
||||
if let current = self.footer {
|
||||
footer = current
|
||||
} else {
|
||||
footerTransition = .immediate
|
||||
footer = ComponentView()
|
||||
self.footer = footer
|
||||
}
|
||||
|
||||
let footerSize = footer.update(
|
||||
transition: ComponentTransition(footerTransition),
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: footerText, font: Font.regular(13.0), textColor: presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.4))),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.1
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: layout.size.width, height: 144.0)
|
||||
)
|
||||
|
||||
let spacing: CGFloat = 20.0
|
||||
childLayout.intrinsicInsets.bottom += footerSize.height + spacing
|
||||
|
||||
if let footerView = footer.view {
|
||||
if footerView.superview == nil {
|
||||
self.view.addSubview(footerView)
|
||||
|
||||
footerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
footerTransition.updateFrame(view: footerView, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - footerSize.width) * 0.5), y: layout.size.height - layout.intrinsicInsets.bottom - tabSelectorSize.height - footerSize.height - spacing), size: footerSize))
|
||||
}
|
||||
} else if let footer = self.footer {
|
||||
self.footer = nil
|
||||
footer.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
footer.view?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
if let tabSelectorView = tabSelector.view {
|
||||
if tabSelectorView.superview == nil {
|
||||
self.view.addSubview(tabSelectorView)
|
||||
@ -705,8 +769,9 @@ final class ContextSourceContainer: ASDisplayNode {
|
||||
} else {
|
||||
self.controller?.dismiss(result: .dismissWithoutContent, completion: nil)
|
||||
}
|
||||
})
|
||||
),
|
||||
},
|
||||
animateAlpha: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: layout.size.width, height: 44.0)
|
||||
)
|
||||
|
@ -2,18 +2,22 @@ import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import CheckNode
|
||||
|
||||
final class TodoChecksView: UIView, PhoneDemoDecorationView {
|
||||
private struct Particle {
|
||||
var id: Int64
|
||||
var trackIndex: Int
|
||||
var position: CGPoint
|
||||
var scale: CGFloat
|
||||
var alpha: CGFloat
|
||||
var direction: CGPoint
|
||||
var velocity: CGFloat
|
||||
var color: UIColor
|
||||
var rotation: CGFloat
|
||||
var currentTime: CGFloat
|
||||
var lifeTime: CGFloat
|
||||
var checkTime: CGFloat?
|
||||
var didSetup: Bool = false
|
||||
|
||||
init(
|
||||
trackIndex: Int,
|
||||
@ -22,19 +26,22 @@ final class TodoChecksView: UIView, PhoneDemoDecorationView {
|
||||
alpha: CGFloat,
|
||||
direction: CGPoint,
|
||||
velocity: CGFloat,
|
||||
color: UIColor,
|
||||
rotation: CGFloat,
|
||||
currentTime: CGFloat,
|
||||
lifeTime: CGFloat
|
||||
lifeTime: CGFloat,
|
||||
checkTime: CGFloat?
|
||||
) {
|
||||
self.id = Int64.random(in: 0 ..< .max)
|
||||
self.trackIndex = trackIndex
|
||||
self.position = position
|
||||
self.scale = scale
|
||||
self.alpha = alpha
|
||||
self.direction = direction
|
||||
self.velocity = velocity
|
||||
self.color = color
|
||||
self.rotation = rotation
|
||||
self.currentTime = currentTime
|
||||
self.lifeTime = lifeTime
|
||||
self.checkTime = checkTime
|
||||
}
|
||||
|
||||
mutating func update(deltaTime: CGFloat) {
|
||||
@ -44,11 +51,15 @@ final class TodoChecksView: UIView, PhoneDemoDecorationView {
|
||||
self.position = position
|
||||
self.currentTime += deltaTime
|
||||
}
|
||||
|
||||
mutating func setup() {
|
||||
self.didSetup = true
|
||||
}
|
||||
}
|
||||
|
||||
private final class ParticleSet {
|
||||
private let size: CGSize
|
||||
private(set) var particles: [Particle] = []
|
||||
var particles: [Particle] = []
|
||||
|
||||
init(size: CGSize, preAdvance: Bool) {
|
||||
self.size = size
|
||||
@ -82,59 +93,51 @@ final class TodoChecksView: UIView, PhoneDemoDecorationView {
|
||||
|
||||
for takeIndex in availableTrackIndices {
|
||||
let directionIndex = takeIndex
|
||||
var angle = (CGFloat(directionIndex % maxDirections) / CGFloat(maxDirections)) * CGFloat.pi * 2.0
|
||||
var lifeTimeMultiplier = 1.0
|
||||
|
||||
var isUpOrDownSemisphere = false
|
||||
if angle > CGFloat.pi / 7.0 && angle < CGFloat.pi - CGFloat.pi / 7.0 {
|
||||
isUpOrDownSemisphere = true
|
||||
} else if !"".isEmpty, angle > CGFloat.pi + CGFloat.pi / 7.0 && angle < 2.0 * CGFloat.pi - CGFloat.pi / 7.0 {
|
||||
isUpOrDownSemisphere = true
|
||||
}
|
||||
|
||||
if isUpOrDownSemisphere {
|
||||
if CGFloat.random(in: 0.0 ... 1.0) < 0.2 {
|
||||
lifeTimeMultiplier = 0.3
|
||||
let angle: CGFloat
|
||||
if directionIndex < 8 {
|
||||
angle = (CGFloat(directionIndex) / 5.0 - 0.5) * 2.0 * (CGFloat.pi / 4.0)
|
||||
} else {
|
||||
angle += CGFloat.random(in: 0.0 ... 1.0) > 0.5 ? CGFloat.pi / 1.6 : -CGFloat.pi / 1.6
|
||||
angle += CGFloat.random(in: -0.2 ... 0.2)
|
||||
lifeTimeMultiplier = 0.5
|
||||
angle = CGFloat.pi + (CGFloat(directionIndex - 6) / 5.0 - 0.5) * 2.0 * (CGFloat.pi / 4.0)
|
||||
}
|
||||
}
|
||||
// if self.large {
|
||||
// angle += CGFloat.random(in: -0.5 ... 0.5)
|
||||
// }
|
||||
|
||||
let lifeTimeMultiplier = 1.0
|
||||
|
||||
let scale = 1.0
|
||||
|
||||
let direction = CGPoint(x: cos(angle), y: sin(angle))
|
||||
let velocity = CGFloat.random(in: 15.0 ..< 20.0)
|
||||
let scale = 1.0
|
||||
let lifeTime = CGFloat.random(in: 2.0 ... 3.5)
|
||||
let velocity = CGFloat.random(in: 18.0 ..< 22.0)
|
||||
|
||||
var position = CGPoint(x: self.size.width / 2.0, y: self.size.height / 2.0)
|
||||
let lifeTime = CGFloat.random(in: 3.2 ... 4.2)
|
||||
|
||||
var position = CGPoint(x: self.size.width / 2.0, y: self.size.height / 2.0 + 40.0)
|
||||
var initialOffset: CGFloat = 0.5
|
||||
if preAdvance {
|
||||
initialOffset = CGFloat.random(in: 0.5 ... 1.0)
|
||||
initialOffset = CGFloat.random(in: 0.7 ... 0.7)
|
||||
} else {
|
||||
initialOffset = CGFloat.random(in: 0.60 ... 0.72)
|
||||
}
|
||||
position.x += direction.x * initialOffset * 250.0
|
||||
position.y += direction.y * initialOffset * 330.0
|
||||
|
||||
var checkTime: CGFloat?
|
||||
let p = CGFloat.random(in: 0.0 ... 1.0)
|
||||
if p < 0.5 {
|
||||
initialOffset = CGFloat.random(in: 0.65 ... 1.0)
|
||||
} else {
|
||||
initialOffset = 0.5
|
||||
if p < 0.2 {
|
||||
checkTime = 0.0
|
||||
} else if p < 0.6 {
|
||||
checkTime = 1.2 + CGFloat.random(in: 0.1 ... 0.6)
|
||||
}
|
||||
}
|
||||
position.x += direction.x * initialOffset * 225.0
|
||||
position.y += direction.y * initialOffset * 225.0
|
||||
|
||||
let particle = Particle(
|
||||
trackIndex: directionIndex,
|
||||
position: position,
|
||||
scale: scale,
|
||||
alpha: 1.0,
|
||||
alpha: 0.3,
|
||||
direction: direction,
|
||||
velocity: velocity,
|
||||
color: .white,
|
||||
rotation: CGFloat.random(in: -0.18 ... 0.2),
|
||||
currentTime: 0.0,
|
||||
lifeTime: lifeTime * lifeTimeMultiplier
|
||||
lifeTime: lifeTime * lifeTimeMultiplier,
|
||||
checkTime: checkTime
|
||||
)
|
||||
self.particles.append(particle)
|
||||
}
|
||||
@ -157,22 +160,16 @@ final class TodoChecksView: UIView, PhoneDemoDecorationView {
|
||||
private var displayLink: SharedDisplayLinkDriver.Link?
|
||||
|
||||
private var particleSet: ParticleSet?
|
||||
private let particleImage: UIImage
|
||||
private var particleLayers: [SimpleLayer] = []
|
||||
private var particleLayers: [CheckLayer] = []
|
||||
private var particleMap: [Int64: CheckLayer] = [:]
|
||||
|
||||
private var size: CGSize?
|
||||
private let large: Bool = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
// if large {
|
||||
// self.particleImage = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/PremiumIcon"), color: .white)!.withRenderingMode(.alwaysTemplate)
|
||||
// } else {
|
||||
self.particleImage = generateTintedImage(image: UIImage(bundleImageName: "Premium/Stars/Particle"), color: .white)!.withRenderingMode(.alwaysTemplate)
|
||||
// }
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.particleSet = ParticleSet(size: frame.size, preAdvance: true)
|
||||
self.particleSet = ParticleSet(size: frame.size, preAdvance: false)
|
||||
|
||||
self.displayLink = SharedDisplayLinkDriver.shared.add(framesPerSecond: .max, { [weak self] delta in
|
||||
self?.update(deltaTime: CGFloat(delta))
|
||||
@ -193,32 +190,54 @@ final class TodoChecksView: UIView, PhoneDemoDecorationView {
|
||||
}
|
||||
particleSet.update(deltaTime: deltaTime)
|
||||
|
||||
var validIds = Set<Int64>()
|
||||
for i in 0 ..< particleSet.particles.count {
|
||||
validIds.insert(particleSet.particles[i].id)
|
||||
}
|
||||
|
||||
for id in self.particleMap.keys {
|
||||
if !validIds.contains(id) {
|
||||
self.particleMap[id]?.isHidden = true
|
||||
self.particleMap.removeValue(forKey: id)
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0 ..< particleSet.particles.count {
|
||||
let particle = particleSet.particles[i]
|
||||
|
||||
let particleLayer: SimpleLayer
|
||||
if i < self.particleLayers.count {
|
||||
particleLayer = self.particleLayers[i]
|
||||
let particleLayer: CheckLayer
|
||||
if let assignedLayer = self.particleMap[particle.id] {
|
||||
particleLayer = assignedLayer
|
||||
} else {
|
||||
if i < self.particleLayers.count, let availableLayer = self.particleLayers.first(where: { $0.isHidden }) {
|
||||
particleLayer = availableLayer
|
||||
particleLayer.isHidden = false
|
||||
} else {
|
||||
particleLayer = SimpleLayer()
|
||||
particleLayer.contents = self.particleImage.cgImage
|
||||
particleLayer.bounds = CGRect(origin: CGPoint(), size: self.particleImage.size)
|
||||
particleLayer = CheckLayer()
|
||||
particleLayer.animateScale = false
|
||||
particleLayer.theme = CheckNodeTheme(backgroundColor: .white, strokeColor: .clear, borderColor: .white, overlayBorder: false, hasInset: false, hasShadow: false)
|
||||
particleLayer.bounds = CGRect(origin: CGPoint(), size: CGSize(width: 22.0, height: 22.0))
|
||||
self.particleLayers.append(particleLayer)
|
||||
self.layer.addSublayer(particleLayer)
|
||||
}
|
||||
self.particleMap[particle.id] = particleLayer
|
||||
}
|
||||
|
||||
particleLayer.layerTintColor = particle.color.cgColor
|
||||
if !particle.didSetup {
|
||||
particleLayer.setSelected(false, animated: false)
|
||||
particleSet.particles[i].setup()
|
||||
}
|
||||
|
||||
particleLayer.position = particle.position
|
||||
particleLayer.opacity = Float(particle.alpha)
|
||||
|
||||
let particleScale = min(1.0, particle.currentTime / 0.3) * min(1.0, (particle.lifeTime - particle.currentTime) / 0.2) * particle.scale
|
||||
particleLayer.transform = CATransform3DMakeScale(particleScale, particleScale, 1.0)
|
||||
}
|
||||
if particleSet.particles.count < self.particleLayers.count {
|
||||
for i in particleSet.particles.count ..< self.particleLayers.count {
|
||||
self.particleLayers[i].isHidden = true
|
||||
var transform = CATransform3DMakeScale(particleScale, particleScale, 1.0)
|
||||
transform = CATransform3DRotate(transform, particle.rotation, 0.0, 0.0, 1.0)
|
||||
particleLayer.transform = transform
|
||||
|
||||
if let checkTime = particle.checkTime, particle.currentTime >= checkTime, !particleLayer.selected {
|
||||
particleLayer.setSelected(true, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -373,6 +373,12 @@ private func generatePercentageAnimationImages(presentationData: ChatPresentatio
|
||||
}
|
||||
|
||||
private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
private var backgroundWallpaperNode: ChatMessageBubbleBackdrop?
|
||||
private var backgroundNode: ChatMessageBackground?
|
||||
private var snapshotView: UIView?
|
||||
|
||||
fileprivate let contextSourceNode: ContextExtractedContentContainingNode
|
||||
fileprivate let containerNode: ASDisplayNode
|
||||
fileprivate let highlightedBackgroundNode: ASDisplayNode
|
||||
private var avatarNode: AvatarNode?
|
||||
private(set) var radioNode: ChatMessageTaskOptionRadioNode?
|
||||
@ -381,13 +387,17 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
fileprivate var nameNode: TextNode?
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
let separatorNode: ASDisplayNode
|
||||
|
||||
var context: AccountContext?
|
||||
var message: Message?
|
||||
var option: TelegramMediaTodo.Item?
|
||||
|
||||
var pressed: (() -> Void)?
|
||||
var selectionUpdated: (() -> Void)?
|
||||
|
||||
var longTapped: (() -> Void)?
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
private var presentationData: ChatPresentationData?
|
||||
private var presentationContext: ChatPresentationContext?
|
||||
|
||||
weak var previousOptionNode: ChatMessageTodoItemNode?
|
||||
|
||||
@ -411,6 +421,9 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
override init() {
|
||||
self.contextSourceNode = ContextExtractedContentContainingNode()
|
||||
self.containerNode = ASDisplayNode()
|
||||
|
||||
self.highlightedBackgroundNode = ASDisplayNode()
|
||||
self.highlightedBackgroundNode.alpha = 0.0
|
||||
self.highlightedBackgroundNode.isUserInteractionEnabled = false
|
||||
@ -422,6 +435,9 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.highlightedBackgroundNode)
|
||||
|
||||
self.addSubnode(self.contextSourceNode)
|
||||
self.addSubnode(self.containerNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
@ -429,7 +445,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
if let theme = strongSelf.theme, theme.overallDarkAppearance, let contentNode = strongSelf.supernode as? ChatMessageTodoBubbleContentNode, let backdropNode = contentNode.bubbleBackgroundNode?.backdropNode {
|
||||
if let theme = strongSelf.presentationData?.theme.theme, theme.overallDarkAppearance, let contentNode = strongSelf.supernode as? ChatMessageTodoBubbleContentNode, let backdropNode = contentNode.bubbleBackgroundNode?.backdropNode {
|
||||
strongSelf.highlightedBackgroundNode.layer.compositingFilter = "overlayBlendMode"
|
||||
strongSelf.highlightedBackgroundNode.frame = strongSelf.view.convert(strongSelf.highlightedBackgroundNode.frame, to: backdropNode.view)
|
||||
backdropNode.addSubnode(strongSelf.highlightedBackgroundNode)
|
||||
@ -470,7 +486,115 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.contextSourceNode.willUpdateIsExtractedToContextPreview = { [weak self] isExtractedToContextPreview, transition in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if isExtractedToContextPreview {
|
||||
self.buttonNode.highligthedChanged(false)
|
||||
|
||||
var offset: CGFloat = 0.0
|
||||
var inset: CGFloat = 0.0
|
||||
var type: ChatMessageBackgroundType
|
||||
|
||||
var incoming = false
|
||||
if let context = self.context, let message = self.message {
|
||||
incoming = message.effectivelyIncoming(context.account.peerId)
|
||||
}
|
||||
|
||||
if incoming {
|
||||
type = .incoming(.Extracted)
|
||||
offset = -5.0
|
||||
inset = 5.0
|
||||
} else {
|
||||
type = .outgoing(.Extracted)
|
||||
inset = 5.0
|
||||
}
|
||||
|
||||
if let _ = self.backgroundNode {
|
||||
} else if let presentationData = self.presentationData, let presentationContext = self.presentationContext {
|
||||
let graphics = PresentationResourcesChat.principalGraphics(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper, bubbleCorners: presentationData.chatBubbleCorners)
|
||||
|
||||
let backgroundWallpaperNode = ChatMessageBubbleBackdrop()
|
||||
backgroundWallpaperNode.alpha = 0.0
|
||||
|
||||
let backgroundNode = ChatMessageBackground()
|
||||
backgroundNode.alpha = 0.0
|
||||
|
||||
self.contextSourceNode.contentNode.insertSubnode(backgroundNode, at: 0)
|
||||
self.contextSourceNode.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: graphics, maskMode: true, hasWallpaper: presentationData.theme.wallpaper.hasWallpaper, transition: .immediate, backgroundNode: presentationContext.backgroundNode)
|
||||
backgroundWallpaperNode.setType(type: type, theme: presentationData.theme, essentialGraphics: graphics, maskMode: true, backgroundNode: presentationContext.backgroundNode)
|
||||
}
|
||||
|
||||
let backgroundFrame = CGRect(x: offset, y: 0.0, width: self.bounds.width + inset, height: self.bounds.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)
|
||||
// }
|
||||
|
||||
if let snapshotView = self.containerNode.view.snapshotContentTree() {
|
||||
self.snapshotView = snapshotView
|
||||
self.contextSourceNode.contentNode.view.addSubview(snapshotView)
|
||||
}
|
||||
} else {
|
||||
if let backgroundNode = self.backgroundNode {
|
||||
self.backgroundNode = nil
|
||||
transition.updateAlpha(node: backgroundNode, alpha: 0.0, completion: { [weak backgroundNode] _ in
|
||||
self.snapshotView?.removeFromSuperview()
|
||||
self.snapshotView = nil
|
||||
|
||||
backgroundNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
if let backgroundWallpaperNode = self.backgroundWallpaperNode {
|
||||
self.backgroundWallpaperNode = nil
|
||||
transition.updateAlpha(node: backgroundWallpaperNode, alpha: 0.0, completion: { [weak backgroundWallpaperNode] _ in
|
||||
backgroundWallpaperNode?.removeFromSupernode()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fileprivate var absoluteRect: (CGRect, CGSize)?
|
||||
// fileprivate func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
// self.absoluteRect = (rect, containerSize)
|
||||
// guard let backgroundWallpaperNode = self.backgroundWallpaperNode else {
|
||||
// return
|
||||
// }
|
||||
// guard !self.sourceNode.isExtractedToContextPreview 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: CGPoint, 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)
|
||||
// }
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
guard !self.ignoreNextTap else {
|
||||
@ -485,11 +609,11 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
static func asyncLayout(_ maybeNode: ChatMessageTodoItemNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ todo: TelegramMediaTodo, _ option: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode))) {
|
||||
static func asyncLayout(_ maybeNode: ChatMessageTodoItemNode?) -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ presentationContext: ChatPresentationContext, _ message: Message, _ todo: TelegramMediaTodo, _ option: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode))) {
|
||||
let makeTitleLayout = TextNodeWithEntities.asyncLayout(maybeNode?.titleNode)
|
||||
let makeNameLayout = TextNode.asyncLayout(maybeNode?.nameNode)
|
||||
|
||||
return { context, presentationData, message, todo, option, completion, translation, constrainedWidth in
|
||||
return { context, presentationData, presentationContext, message, todo, option, completion, translation, constrainedWidth in
|
||||
var canMark = false
|
||||
if (todo.flags.contains(.othersCanComplete) || message.author?.id == context.account.peerId) {
|
||||
canMark = true
|
||||
@ -550,10 +674,13 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
node = ChatMessageTodoItemNode()
|
||||
}
|
||||
|
||||
node.option = option
|
||||
node.context = context
|
||||
node.presentationData = presentationData
|
||||
node.presentationContext = presentationContext
|
||||
|
||||
node.canMark = canMark
|
||||
node.isPremium = context.isPremium
|
||||
node.option = option
|
||||
node.theme = presentationData.theme.theme
|
||||
|
||||
node.highlightedBackgroundNode.backgroundColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.highlight : presentationData.theme.theme.chat.message.outgoing.polls.highlight
|
||||
|
||||
@ -597,7 +724,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
|
||||
if node.titleNode !== titleNode {
|
||||
node.titleNode = titleNode
|
||||
node.addSubnode(titleNode.textNode)
|
||||
node.containerNode.addSubnode(titleNode.textNode)
|
||||
titleNode.textNode.isUserInteractionEnabled = false
|
||||
|
||||
if let visibilityRect = node.visibilityRect {
|
||||
@ -622,7 +749,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
let nameNode = nameApply()
|
||||
if node.nameNode !== nameNode {
|
||||
node.nameNode = nameNode
|
||||
node.addSubnode(nameNode)
|
||||
node.containerNode.addSubnode(nameNode)
|
||||
nameNode.isUserInteractionEnabled = false
|
||||
|
||||
if animated {
|
||||
@ -647,7 +774,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
avatarNode = current
|
||||
} else {
|
||||
avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 12.0))
|
||||
node.insertSubnode(avatarNode, at: 0)
|
||||
node.containerNode.insertSubnode(avatarNode, at: 0)
|
||||
node.avatarNode = avatarNode
|
||||
if animated {
|
||||
avatarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
@ -658,7 +785,6 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
avatarNode.frame = CGRect(origin: CGPoint(x: 24.0, y: 12.0), size: avatarSize)
|
||||
if let peer = message.peers[completion.completedBy] {
|
||||
avatarNode.setPeer(context: context, theme: presentationData.theme.theme, peer: EnginePeer(peer), displayDimensions: avatarSize, cutoutRect: CGRect(origin: CGPoint(x: -12.0, y: -1.0), size: CGSize(width: 24.0, height: 24.0)))
|
||||
//avatarNode.setPeerV2(context: context, theme: presentationData.theme.theme, peer: EnginePeer(peer), displayDimensions: avatarSize)
|
||||
}
|
||||
} else if let avatarNode = node.avatarNode {
|
||||
node.avatarNode = nil
|
||||
@ -678,7 +804,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
radioNode = current
|
||||
} else {
|
||||
radioNode = ChatMessageTaskOptionRadioNode()
|
||||
node.addSubnode(radioNode)
|
||||
node.containerNode.addSubnode(radioNode)
|
||||
node.radioNode = radioNode
|
||||
if animated {
|
||||
radioNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
@ -707,7 +833,7 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
} else {
|
||||
iconNode = ASImageNode()
|
||||
iconNode.displaysAsynchronously = false
|
||||
node.addSubnode(iconNode)
|
||||
node.containerNode.addSubnode(iconNode)
|
||||
node.iconNode = iconNode
|
||||
if animated {
|
||||
iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
@ -742,6 +868,10 @@ private final class ChatMessageTodoItemNode: ASDisplayNode {
|
||||
node.separatorNode.backgroundColor = incoming ? presentationData.theme.theme.chat.message.incoming.polls.separator : presentationData.theme.theme.chat.message.outgoing.polls.separator
|
||||
node.separatorNode.frame = CGRect(origin: CGPoint(x: leftInset, y: contentHeight - UIScreenPixel), size: CGSize(width: width - leftInset, height: UIScreenPixel))
|
||||
|
||||
node.containerNode.frame = CGRect(origin: .zero, size: CGSize(width: width, height: contentHeight))
|
||||
node.contextSourceNode.frame = CGRect(origin: .zero, size: CGSize(width: width, height: contentHeight))
|
||||
node.contextSourceNode.contentRect = CGRect(origin: .zero, size: CGSize(width: width, height: contentHeight))
|
||||
|
||||
node.buttonNode.isAccessibilityElement = true
|
||||
|
||||
return node
|
||||
@ -829,7 +959,7 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
let makeViewResultsTextLayout = TextNode.asyncLayout(self.buttonViewResultsTextNode)
|
||||
let statusLayout = self.statusNode.asyncLayout()
|
||||
|
||||
var previousOptionNodeLayouts: [Int32: (_ contet: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ poll: TelegramMediaTodo, _ option: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode)))] = [:]
|
||||
var previousOptionNodeLayouts: [Int32: (_ contet: AccountContext, _ presentationData: ChatPresentationData, _ presentationContext: ChatPresentationContext, _ message: Message, _ poll: TelegramMediaTodo, _ option: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode)))] = [:]
|
||||
for optionNode in self.optionNodes {
|
||||
if let option = optionNode.option {
|
||||
previousOptionNodeLayouts[option.id] = ChatMessageTodoItemNode.asyncLayout(optionNode)
|
||||
@ -1034,7 +1164,7 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
for i in 0 ..< todo.items.count {
|
||||
let todoItem = todo.items[i]
|
||||
|
||||
let makeLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ message: Message, _ todo: TelegramMediaTodo, _ item: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode)))
|
||||
let makeLayout: (_ context: AccountContext, _ presentationData: ChatPresentationData, _ presentationContext: ChatPresentationContext, _ message: Message, _ todo: TelegramMediaTodo, _ item: TelegramMediaTodo.Item, _ completion: TelegramMediaTodo.Completion?, _ translation: TranslationMessageAttribute.Additional?, _ constrainedWidth: CGFloat) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (Bool, Bool, Bool) -> ChatMessageTodoItemNode)))
|
||||
if let previous = previousOptionNodeLayouts[todoItem.id] {
|
||||
makeLayout = previous
|
||||
} else {
|
||||
@ -1048,7 +1178,7 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
let itemCompletion = todo.completions.first(where: { $0.id == todoItem.id })
|
||||
|
||||
let result = makeLayout(item.context, item.presentationData, item.message, todo, todoItem, itemCompletion, translation, constrainedSize.width - layoutConstants.bubble.borderInset * 2.0)
|
||||
let result = makeLayout(item.context, item.presentationData, item.controllerInteraction.presentationContext, item.message, todo, todoItem, itemCompletion, translation, constrainedSize.width - layoutConstants.bubble.borderInset * 2.0)
|
||||
boundingSize.width = max(boundingSize.width, result.minimumWidth + layoutConstants.bubble.borderInset * 2.0)
|
||||
pollOptionsFinalizeLayouts.append(result.1)
|
||||
}
|
||||
@ -1146,10 +1276,10 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
item.controllerInteraction.displayTodoToggleUnavailable(item.message.id)
|
||||
}
|
||||
optionNode.longTapped = { [weak optionNode] in
|
||||
guard let strongSelf = self, let item = strongSelf.item, let todoItem, let optionNode, let contentNode = strongSelf.contextContentNodeForItem(itemNode: optionNode) else {
|
||||
guard let strongSelf = self, let item = strongSelf.item, let todoItem, let optionNode else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.todoItemLongTap(todoItem.id, ChatControllerInteraction.LongTapParams(message: item.message, contentNode: contentNode, messageNode: strongSelf, progress: nil))
|
||||
item.controllerInteraction.todoItemLongTap(todoItem.id, ChatControllerInteraction.LongTapParams(message: item.message, contentNode: optionNode.contextSourceNode, messageNode: strongSelf, progress: nil))
|
||||
}
|
||||
optionNode.frame = optionNodeFrame
|
||||
} else {
|
||||
@ -1403,42 +1533,4 @@ public class ChatMessageTodoBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func contextContentNodeForItem(itemNode: ChatMessageTodoItemNode) -> ContextExtractedContentContainingNode? {
|
||||
guard let item = self.item else {
|
||||
return nil
|
||||
}
|
||||
let containingNode = ContextExtractedContentContainingNode()
|
||||
|
||||
let incoming = item.content.effectivelyIncoming(item.context.account.peerId, associatedData: item.associatedData)
|
||||
|
||||
itemNode.highlightedBackgroundNode.alpha = 0.0
|
||||
guard let snapshotView = itemNode.view.snapshotContentTree() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let backgroundNode = ASDisplayNode()
|
||||
backgroundNode.backgroundColor = (incoming ? item.presentationData.theme.theme.chat.message.incoming.bubble.withoutWallpaper.fill : item.presentationData.theme.theme.chat.message.outgoing.bubble.withoutWallpaper.fill).first ?? .black
|
||||
backgroundNode.clipsToBounds = true
|
||||
backgroundNode.cornerRadius = 10.0
|
||||
|
||||
let insets = UIEdgeInsets.zero
|
||||
let backgroundSize = CGSize(width: snapshotView.frame.width + insets.left + insets.right, height: snapshotView.frame.height + insets.top + insets.bottom)
|
||||
backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: backgroundSize)
|
||||
snapshotView.frame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: snapshotView.frame.size)
|
||||
backgroundNode.view.addSubview(snapshotView)
|
||||
|
||||
let origin = CGPoint(x: 3.0, y: 1.0) //self.backgroundNode.frame.minX + 3.0, y: 1.0)
|
||||
|
||||
containingNode.frame = CGRect(origin: origin, size: CGSize(width: backgroundSize.width, height: backgroundSize.height + 20.0))
|
||||
containingNode.contentNode.frame = CGRect(origin: .zero, size: backgroundSize)
|
||||
containingNode.contentRect = CGRect(origin: .zero, size: backgroundSize)
|
||||
containingNode.contentNode.addSubnode(backgroundNode)
|
||||
|
||||
containingNode.contentNode.alpha = 0.0
|
||||
|
||||
self.addSubnode(containingNode)
|
||||
|
||||
return containingNode
|
||||
}
|
||||
}
|
||||
|
@ -190,7 +190,9 @@ extension ChatControllerImpl {
|
||||
ContextController.Source(
|
||||
id: AnyHashable(OptionsId.item),
|
||||
title: self.presentationData.strings.Chat_Todo_ContextMenu_SectionTask,
|
||||
source: .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)),
|
||||
footer: self.presentationData.strings.Chat_Todo_ContextMenu_SectionsInfo,
|
||||
//source: .extracted(ChatMessageLinkContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)),
|
||||
source: .extracted(ChatTodoItemContextExtractedContentSource(chatNode: self.chatDisplayNode, contentNode: contentNode)),
|
||||
items: .single(ContextController.Items(content: .list(items)))
|
||||
)
|
||||
)
|
||||
@ -199,7 +201,7 @@ extension ChatControllerImpl {
|
||||
ContextController.Source(
|
||||
id: AnyHashable(OptionsId.message),
|
||||
title: self.presentationData.strings.Chat_Todo_ContextMenu_SectionList,
|
||||
source: .extracted(ChatMessageContextExtractedContentSource(chatController: self, chatNode: self.chatDisplayNode, engine: self.context.engine, message: message, selectAll: false, keepDefaultContentTouches: false)),
|
||||
source: .extracted(ChatMessageContextExtractedContentSource(chatController: self, chatNode: self.chatDisplayNode, engine: self.context.engine, message: message, selectAll: false, snapshot: true)),
|
||||
items: .single(actions)
|
||||
)
|
||||
)
|
||||
@ -219,3 +221,31 @@ extension ChatControllerImpl {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
final class ChatTodoItemContextExtractedContentSource: ContextExtractedContentSource {
|
||||
let keepInPlace: Bool = false
|
||||
let ignoreContentTouches: Bool = false
|
||||
let blurBackground: Bool = true
|
||||
|
||||
private weak var chatNode: ChatControllerNode?
|
||||
private let contentNode: ContextExtractedContentContainingNode
|
||||
|
||||
init(chatNode: ChatControllerNode, contentNode: ContextExtractedContentContainingNode) {
|
||||
self.chatNode = chatNode
|
||||
self.contentNode = contentNode
|
||||
}
|
||||
|
||||
func takeView() -> ContextControllerTakeViewInfo? {
|
||||
guard let chatNode = self.chatNode else {
|
||||
return nil
|
||||
}
|
||||
return ContextControllerTakeViewInfo(containingItem: .node(self.contentNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
|
||||
}
|
||||
|
||||
func putBack() -> ContextControllerPutBackViewInfo? {
|
||||
guard let chatNode = self.chatNode else {
|
||||
return nil
|
||||
}
|
||||
return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ extension ChatControllerImpl {
|
||||
var filter: ChatListNodePeersFilter = [.onlyWriteable, .excludeDisabled, .doNotSearchMessages]
|
||||
var hasPublicPolls = false
|
||||
var hasPublicQuiz = false
|
||||
var hasTodo = false
|
||||
for message in messages {
|
||||
for media in message.media {
|
||||
if let poll = media as? TelegramMediaPoll, case .public = poll.publicity {
|
||||
@ -41,9 +42,10 @@ extension ChatControllerImpl {
|
||||
hasPublicQuiz = true
|
||||
}
|
||||
filter.insert(.excludeChannels)
|
||||
break
|
||||
}
|
||||
if let _ = media as? TelegramMediaPaidContent {
|
||||
} else if let _ = media as? TelegramMediaTodo {
|
||||
hasTodo = true
|
||||
filter.insert(.excludeChannels)
|
||||
} else if let _ = media as? TelegramMediaPaidContent {
|
||||
filter.insert(.excludeSecretChats)
|
||||
}
|
||||
}
|
||||
@ -63,6 +65,11 @@ extension ChatControllerImpl {
|
||||
controller.present(textAlertController(context: context, title: nil, text: hasPublicQuiz ? presentationData.strings.Forward_ErrorPublicQuizDisabledInChannels : presentationData.strings.Forward_ErrorPublicPollDisabledInChannels, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
return
|
||||
}
|
||||
} else if hasTodo {
|
||||
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||
controller.present(textAlertController(context: context, title: nil, text: presentationData.strings.Forward_ErrorTodoDisabledInChannels, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
return
|
||||
}
|
||||
}
|
||||
switch reason {
|
||||
case .generic:
|
||||
|
@ -62,6 +62,7 @@ extension ChatControllerImpl {
|
||||
var bannedSendFiles: (Int32, Bool)?
|
||||
|
||||
var canSendPolls = true
|
||||
var canSendTodos = true
|
||||
if let peer = self.presentationInterfaceState.renderedPeer?.peer {
|
||||
if let peer = peer as? TelegramUser {
|
||||
if peer.botInfo == nil && peer.id != self.context.account.peerId {
|
||||
@ -70,6 +71,9 @@ extension ChatControllerImpl {
|
||||
} else if peer is TelegramSecretChat {
|
||||
canSendPolls = false
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if case .broadcast = channel.info {
|
||||
canSendTodos = false
|
||||
}
|
||||
if let value = channel.hasBannedPermission(.banSendPhotos, ignoreDefault: canByPassRestrictions) {
|
||||
bannedSendPhotos = value
|
||||
}
|
||||
@ -116,7 +120,9 @@ extension ChatControllerImpl {
|
||||
availableButtons.insert(.poll, at: max(0, availableButtons.count - 1))
|
||||
}
|
||||
|
||||
if canSendTodos {
|
||||
availableButtons.insert(.todo, at: max(0, availableButtons.count - 1))
|
||||
}
|
||||
|
||||
let presentationData = self.presentationData
|
||||
|
||||
|
@ -169,6 +169,8 @@ private func canEditMessage(accountPeerId: PeerId, limitsConfiguration: EngineCo
|
||||
} else if let _ = media as? TelegramMediaGiveawayResults {
|
||||
hasUneditableAttributes = true
|
||||
break
|
||||
} else if let _ = media as? TelegramMediaTodo {
|
||||
unlimitedInterval = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,7 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
|
||||
private let engine: TelegramEngine
|
||||
private let message: Message
|
||||
private let selectAll: Bool
|
||||
private let snapshot: Bool
|
||||
|
||||
var shouldBeDismissed: Signal<Bool, NoError> {
|
||||
if self.message.adAttribute != nil {
|
||||
@ -60,7 +61,7 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
|
||||
|> distinctUntilChanged
|
||||
}
|
||||
|
||||
init(chatController: ChatControllerImpl, chatNode: ChatControllerNode, engine: TelegramEngine, message: Message, selectAll: Bool, centerVertically: Bool = false, keepDefaultContentTouches: Bool = false) {
|
||||
init(chatController: ChatControllerImpl, chatNode: ChatControllerNode, engine: TelegramEngine, message: Message, selectAll: Bool, centerVertically: Bool = false, keepDefaultContentTouches: Bool = false, snapshot: Bool = false) {
|
||||
self.chatController = chatController
|
||||
self.chatNode = chatNode
|
||||
self.engine = engine
|
||||
@ -68,8 +69,11 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
|
||||
self.selectAll = selectAll
|
||||
self.centerVertically = centerVertically
|
||||
self.keepDefaultContentTouches = keepDefaultContentTouches
|
||||
self.snapshot = snapshot
|
||||
}
|
||||
|
||||
private var snapshotView: UIView?
|
||||
|
||||
func takeView() -> ContextControllerTakeViewInfo? {
|
||||
guard let chatNode = self.chatNode else {
|
||||
return nil
|
||||
@ -85,6 +89,11 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
|
||||
}
|
||||
if item.content.contains(where: { $0.0.stableId == self.message.stableId }), let contentNode = itemNode.getMessageContextSourceNode(stableId: self.selectAll ? nil : self.message.stableId) {
|
||||
result = ContextControllerTakeViewInfo(containingItem: .node(contentNode), contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
|
||||
|
||||
if self.snapshot, let snapshotView = contentNode.contentNode.view.snapshotContentTree(unhide: false, keepPortals: true, keepTransform: true) {
|
||||
contentNode.view.superview?.addSubview(snapshotView)
|
||||
self.snapshotView = snapshotView
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
@ -107,6 +116,13 @@ final class ChatMessageContextExtractedContentSource: ContextExtractedContentSou
|
||||
result = ContextControllerPutBackViewInfo(contentAreaInScreenSpace: chatNode.convert(chatNode.frameForVisibleArea(), to: nil))
|
||||
}
|
||||
}
|
||||
|
||||
if let snapshotView = self.snapshotView {
|
||||
Queue.mainQueue().after(0.4) {
|
||||
snapshotView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user