mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 01:10:09 +00:00
Shared media improvements
This commit is contained in:
parent
205211d5ba
commit
b5d1b377f2
@ -10,12 +10,12 @@ import TelegramPresentationData
|
|||||||
import ComponentFlow
|
import ComponentFlow
|
||||||
import PhotoResources
|
import PhotoResources
|
||||||
|
|
||||||
private final class MediaPreviewNode: ASDisplayNode {
|
private final class MediaPreviewView: UIView {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let message: EngineMessage
|
private let message: EngineMessage
|
||||||
private let media: EngineMedia
|
private let media: EngineMedia
|
||||||
|
|
||||||
private let imageNode: TransformImageNode
|
private let imageView: TransformImageView
|
||||||
|
|
||||||
private var requestedImage: Bool = false
|
private var requestedImage: Bool = false
|
||||||
private var disposable: Disposable?
|
private var disposable: Disposable?
|
||||||
@ -25,11 +25,15 @@ private final class MediaPreviewNode: ASDisplayNode {
|
|||||||
self.message = message
|
self.message = message
|
||||||
self.media = media
|
self.media = media
|
||||||
|
|
||||||
self.imageNode = TransformImageNode()
|
self.imageView = TransformImageView()
|
||||||
|
|
||||||
super.init()
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
self.addSubnode(self.imageNode)
|
self.addSubview(self.imageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -44,7 +48,7 @@ private final class MediaPreviewNode: ASDisplayNode {
|
|||||||
if !self.requestedImage {
|
if !self.requestedImage {
|
||||||
self.requestedImage = true
|
self.requestedImage = true
|
||||||
let signal = mediaGridMessagePhoto(account: self.context.account, photoReference: .message(message: MessageReference(self.message._asMessage()), media: image), fullRepresentationSize: CGSize(width: 36.0, height: 36.0), synchronousLoad: synchronousLoads)
|
let signal = mediaGridMessagePhoto(account: self.context.account, photoReference: .message(message: MessageReference(self.message._asMessage()), media: image), fullRepresentationSize: CGSize(width: 36.0, height: 36.0), synchronousLoad: synchronousLoads)
|
||||||
self.imageNode.setSignal(signal, attemptSynchronously: synchronousLoads)
|
self.imageView.setSignal(signal, attemptSynchronously: synchronousLoads)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if case let .file(file) = self.media {
|
} else if case let .file(file) = self.media {
|
||||||
@ -53,13 +57,13 @@ private final class MediaPreviewNode: ASDisplayNode {
|
|||||||
if !self.requestedImage {
|
if !self.requestedImage {
|
||||||
self.requestedImage = true
|
self.requestedImage = true
|
||||||
let signal = mediaGridMessageVideo(postbox: self.context.account.postbox, videoReference: .message(message: MessageReference(self.message._asMessage()), media: file), synchronousLoad: synchronousLoads, autoFetchFullSizeThumbnail: true, useMiniThumbnailIfAvailable: true)
|
let signal = mediaGridMessageVideo(postbox: self.context.account.postbox, videoReference: .message(message: MessageReference(self.message._asMessage()), media: file), synchronousLoad: synchronousLoads, autoFetchFullSizeThumbnail: true, useMiniThumbnailIfAvailable: true)
|
||||||
self.imageNode.setSignal(signal, attemptSynchronously: synchronousLoads)
|
self.imageView.setSignal(signal, attemptSynchronously: synchronousLoads)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let makeLayout = self.imageNode.asyncLayout()
|
let makeLayout = self.imageView.asyncLayout()
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(), size: size)
|
self.imageView.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: size.width / 2.0), imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
|
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: size.width / 2.0), imageSize: dimensions.aspectFilled(size), boundingSize: size, intrinsicInsets: UIEdgeInsets()))
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
@ -152,6 +156,13 @@ private final class ImageCache: Equatable {
|
|||||||
var color: UInt32
|
var color: UInt32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct Text: Hashable {
|
||||||
|
var fontSize: CGFloat
|
||||||
|
var isSemibold: Bool
|
||||||
|
var color: UInt32
|
||||||
|
var string: String
|
||||||
|
}
|
||||||
|
|
||||||
private var items: [AnyHashable: UIImage] = [:]
|
private var items: [AnyHashable: UIImage] = [:]
|
||||||
|
|
||||||
func filledCircle(diameter: CGFloat, color: UIColor) -> UIImage {
|
func filledCircle(diameter: CGFloat, color: UIColor) -> UIImage {
|
||||||
@ -169,6 +180,31 @@ private final class ImageCache: Equatable {
|
|||||||
self.items[key] = image
|
self.items[key] = image
|
||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func text(fontSize: CGFloat, isSemibold: Bool, color: UIColor, string: String) -> UIImage {
|
||||||
|
let key = AnyHashable(Text(fontSize: fontSize, isSemibold: isSemibold, color: color.argb, string: string))
|
||||||
|
if let image = self.items[key] {
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
|
||||||
|
let font: UIFont
|
||||||
|
if isSemibold {
|
||||||
|
font = Font.semibold(fontSize)
|
||||||
|
} else {
|
||||||
|
font = Font.regular(fontSize)
|
||||||
|
}
|
||||||
|
let attributedString = NSAttributedString(string: string, font: font, textColor: color)
|
||||||
|
let rect = attributedString.boundingRect(with: CGSize(width: 1000.0, height: 1000.0), options: .usesLineFragmentOrigin, context: nil)
|
||||||
|
let image = generateImage(CGSize(width: ceil(rect.width), height: ceil(rect.height)), rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
UIGraphicsPushContext(context)
|
||||||
|
attributedString.draw(in: rect)
|
||||||
|
UIGraphicsPopContext()
|
||||||
|
})!
|
||||||
|
self.items[key] = image
|
||||||
|
return image
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class DayComponent: Component {
|
private final class DayComponent: Component {
|
||||||
@ -223,28 +259,28 @@ private final class DayComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class View: UIView {
|
final class View: UIView {
|
||||||
private let buttonNode: HighlightableButtonNode
|
private let button: HighlightableButton
|
||||||
|
|
||||||
private let highlightNode: ASImageNode
|
private let highlightView: UIImageView
|
||||||
private let titleNode: ImmediateTextNode
|
private let titleView: UIImageView
|
||||||
private var mediaPreviewNode: MediaPreviewNode?
|
private var mediaPreviewView: MediaPreviewView?
|
||||||
|
|
||||||
private var action: (() -> Void)?
|
private var action: (() -> Void)?
|
||||||
private var currentMedia: DayMedia?
|
private var currentMedia: DayMedia?
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.buttonNode = HighlightableButtonNode()
|
self.button = HighlightableButton()
|
||||||
self.highlightNode = ASImageNode()
|
self.highlightView = UIImageView()
|
||||||
self.titleNode = ImmediateTextNode()
|
self.titleView = UIImageView()
|
||||||
|
|
||||||
super.init(frame: CGRect())
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
self.buttonNode.addSubnode(self.highlightNode)
|
self.button.addSubview(self.highlightView)
|
||||||
self.buttonNode.addSubnode(self.titleNode)
|
self.button.addSubview(self.titleView)
|
||||||
|
|
||||||
self.addSubnode(self.buttonNode)
|
self.addSubview(self.button)
|
||||||
|
|
||||||
self.buttonNode.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
|
self.button.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
@ -258,58 +294,64 @@ private final class DayComponent: Component {
|
|||||||
func update(component: DayComponent, availableSize: CGSize, environment: Environment<ImageCache>, transition: Transition) -> CGSize {
|
func update(component: DayComponent, availableSize: CGSize, environment: Environment<ImageCache>, transition: Transition) -> CGSize {
|
||||||
self.action = component.action
|
self.action = component.action
|
||||||
|
|
||||||
let shadowInset: CGFloat = 0.0
|
|
||||||
let diameter = min(availableSize.width, availableSize.height)
|
let diameter = min(availableSize.width, availableSize.height)
|
||||||
|
let contentFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - diameter) / 2.0), y: floor((availableSize.height - diameter) / 2.0)), size: CGSize(width: diameter, height: diameter))
|
||||||
|
|
||||||
let imageCache = environment[ImageCache.self]
|
let imageCache = environment[ImageCache.self]
|
||||||
if component.media != nil {
|
if component.media != nil {
|
||||||
self.highlightNode.image = imageCache.value.filledCircle(diameter: diameter, color: UIColor(white: 0.0, alpha: 0.2))
|
self.highlightView.image = imageCache.value.filledCircle(diameter: diameter, color: UIColor(white: 0.0, alpha: 0.2))
|
||||||
} else if component.isCurrent {
|
} else if component.isCurrent {
|
||||||
self.highlightNode.image = imageCache.value.filledCircle(diameter: diameter, color: component.theme.list.itemAccentColor)
|
self.highlightView.image = imageCache.value.filledCircle(diameter: diameter, color: component.theme.list.itemAccentColor)
|
||||||
} else {
|
} else {
|
||||||
self.highlightNode.image = nil
|
self.highlightView.image = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.currentMedia != component.media {
|
if self.currentMedia != component.media {
|
||||||
self.currentMedia = component.media
|
self.currentMedia = component.media
|
||||||
|
|
||||||
if let mediaPreviewNode = self.mediaPreviewNode {
|
if let mediaPreviewView = self.mediaPreviewView {
|
||||||
self.mediaPreviewNode = nil
|
self.mediaPreviewView = nil
|
||||||
mediaPreviewNode.removeFromSupernode()
|
mediaPreviewView.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let media = component.media {
|
if let media = component.media {
|
||||||
let mediaPreviewNode = MediaPreviewNode(context: component.context, message: media.message, media: media.media)
|
let mediaPreviewView = MediaPreviewView(context: component.context, message: media.message, media: media.media)
|
||||||
self.mediaPreviewNode = mediaPreviewNode
|
self.mediaPreviewView = mediaPreviewView
|
||||||
self.buttonNode.insertSubnode(mediaPreviewNode, belowSubnode: self.highlightNode)
|
self.button.insertSubview(mediaPreviewView, belowSubview: self.highlightView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleColor: UIColor
|
let titleColor: UIColor
|
||||||
let titleFont: UIFont
|
let titleFontSize: CGFloat
|
||||||
|
let titleFontIsSemibold: Bool
|
||||||
if component.isCurrent || component.media != nil {
|
if component.isCurrent || component.media != nil {
|
||||||
titleColor = component.theme.list.itemCheckColors.foregroundColor
|
titleColor = component.theme.list.itemCheckColors.foregroundColor
|
||||||
titleFont = Font.semibold(17.0)
|
titleFontSize = 17.0
|
||||||
|
titleFontIsSemibold = true
|
||||||
} else if component.isEnabled {
|
} else if component.isEnabled {
|
||||||
titleColor = component.theme.list.itemPrimaryTextColor
|
titleColor = component.theme.list.itemPrimaryTextColor
|
||||||
titleFont = Font.regular(17.0)
|
titleFontSize = 17.0
|
||||||
|
titleFontIsSemibold = false
|
||||||
} else {
|
} else {
|
||||||
titleColor = component.theme.list.itemDisabledTextColor
|
titleColor = component.theme.list.itemDisabledTextColor
|
||||||
titleFont = Font.regular(17.0)
|
titleFontSize = 17.0
|
||||||
|
titleFontIsSemibold = false
|
||||||
}
|
}
|
||||||
self.titleNode.attributedText = NSAttributedString(string: component.title, font: titleFont, textColor: titleColor)
|
|
||||||
let titleSize = self.titleNode.updateLayout(availableSize)
|
|
||||||
|
|
||||||
transition.setFrame(view: self.highlightNode.view, frame: CGRect(origin: CGPoint(x: -shadowInset, y: -shadowInset), size: CGSize(width: availableSize.width + shadowInset * 2.0, height: availableSize.height + shadowInset * 2.0)))
|
let titleImage = imageCache.value.text(fontSize: titleFontSize, isSemibold: titleFontIsSemibold, color: titleColor, string: component.title)
|
||||||
|
self.titleView.image = titleImage
|
||||||
|
let titleSize = titleImage.size
|
||||||
|
|
||||||
self.titleNode.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: floor((availableSize.height - titleSize.height) / 2.0)), size: titleSize)
|
transition.setFrame(view: self.highlightView, frame: contentFrame)
|
||||||
|
|
||||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: availableSize)
|
self.titleView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) / 2.0), y: floor((availableSize.height - titleSize.height) / 2.0)), size: titleSize)
|
||||||
self.buttonNode.isEnabled = component.isEnabled && component.media != nil
|
|
||||||
|
|
||||||
if let mediaPreviewNode = self.mediaPreviewNode {
|
self.button.frame = CGRect(origin: CGPoint(), size: availableSize)
|
||||||
mediaPreviewNode.frame = CGRect(origin: CGPoint(), size: availableSize)
|
self.button.isEnabled = component.isEnabled && component.media != nil
|
||||||
mediaPreviewNode.updateLayout(size: availableSize, synchronousLoads: false)
|
|
||||||
|
if let mediaPreviewView = self.mediaPreviewView {
|
||||||
|
mediaPreviewView.frame = contentFrame
|
||||||
|
mediaPreviewView.updateLayout(size: contentFrame.size, synchronousLoads: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return availableSize
|
return availableSize
|
||||||
@ -382,6 +424,7 @@ private final class MonthComponent: CombinedComponent {
|
|||||||
let weekdaySize: CGFloat = 46.0
|
let weekdaySize: CGFloat = 46.0
|
||||||
let weekdaySpacing: CGFloat = 6.0
|
let weekdaySpacing: CGFloat = 6.0
|
||||||
|
|
||||||
|
let usableWeekdayWidth = floor((context.availableSize.width - sideInset * 2.0 - weekdaySpacing * 6.0) / 7.0)
|
||||||
let weekdayWidth = floor((context.availableSize.width - sideInset * 2.0) / 7.0)
|
let weekdayWidth = floor((context.availableSize.width - sideInset * 2.0) / 7.0)
|
||||||
|
|
||||||
let title = title.update(
|
let title = title.update(
|
||||||
@ -440,7 +483,7 @@ private final class MonthComponent: CombinedComponent {
|
|||||||
environment: {
|
environment: {
|
||||||
context.environment[ImageCache.self]
|
context.environment[ImageCache.self]
|
||||||
},
|
},
|
||||||
availableSize: CGSize(width: weekdaySize, height: weekdaySize),
|
availableSize: CGSize(width: usableWeekdayWidth, height: weekdaySize),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -843,7 +886,7 @@ public final class CalendarMessageScreen: ViewController {
|
|||||||
|
|
||||||
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(dismissPressed)), animated: false)
|
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(dismissPressed)), animated: false)
|
||||||
//TODO:localize
|
//TODO:localize
|
||||||
self.navigationItem.setTitle("Jump to Date", animated: false)
|
self.navigationItem.setTitle("Calendar", animated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
required public init(coder aDecoder: NSCoder) {
|
required public init(coder aDecoder: NSCoder) {
|
||||||
|
|||||||
@ -206,3 +206,183 @@ open class TransformImageNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open class TransformImageView: UIView {
|
||||||
|
public var imageUpdated: ((UIImage?) -> Void)?
|
||||||
|
public var contentAnimations: TransformImageNodeContentAnimations = []
|
||||||
|
private var disposable = MetaDisposable()
|
||||||
|
|
||||||
|
private var currentTransform: ((TransformImageArguments) -> DrawingContext?)?
|
||||||
|
private var currentArguments: TransformImageArguments?
|
||||||
|
private var argumentsPromise = ValuePromise<TransformImageArguments>(ignoreRepeated: true)
|
||||||
|
|
||||||
|
private var overlayColor: UIColor?
|
||||||
|
private var overlayView: UIView?
|
||||||
|
|
||||||
|
override public init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||||
|
self.accessibilityIgnoresInvertColors = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
override open var frame: CGRect {
|
||||||
|
didSet {
|
||||||
|
if let overlayView = self.overlayView {
|
||||||
|
overlayView.frame = self.bounds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func reset() {
|
||||||
|
self.disposable.set(nil)
|
||||||
|
self.currentArguments = nil
|
||||||
|
self.currentTransform = nil
|
||||||
|
self.layer.contents = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setSignal(_ signal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>, attemptSynchronously: Bool = false, dispatchOnDisplayLink: Bool = true) {
|
||||||
|
let argumentsPromise = self.argumentsPromise
|
||||||
|
|
||||||
|
let data = combineLatest(signal, argumentsPromise.get())
|
||||||
|
|
||||||
|
let resultData: Signal<((TransformImageArguments) -> DrawingContext?, TransformImageArguments), NoError>
|
||||||
|
if attemptSynchronously {
|
||||||
|
resultData = data
|
||||||
|
} else {
|
||||||
|
resultData = data
|
||||||
|
|> deliverOn(Queue.concurrentDefaultQueue())
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = resultData
|
||||||
|
|> mapToThrottled { transform, arguments -> Signal<((TransformImageArguments) -> DrawingContext?, TransformImageArguments, UIImage?)?, NoError> in
|
||||||
|
return deferred {
|
||||||
|
if let context = transform(arguments) {
|
||||||
|
return .single((transform, arguments, context.generateImage()))
|
||||||
|
} else {
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.disposable.set((result |> deliverOnMainQueue).start(next: { [weak self] next in
|
||||||
|
let apply: () -> Void = {
|
||||||
|
if let strongSelf = self {
|
||||||
|
if strongSelf.layer.contents == nil {
|
||||||
|
if strongSelf.contentAnimations.contains(.firstUpdate) && !attemptSynchronously {
|
||||||
|
strongSelf.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||||
|
}
|
||||||
|
} else if strongSelf.contentAnimations.contains(.subsequentUpdates) {
|
||||||
|
let tempLayer = CALayer()
|
||||||
|
tempLayer.frame = strongSelf.bounds
|
||||||
|
tempLayer.contentsGravity = strongSelf.layer.contentsGravity
|
||||||
|
tempLayer.contents = strongSelf.layer.contents
|
||||||
|
strongSelf.layer.addSublayer(tempLayer)
|
||||||
|
tempLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak tempLayer] _ in
|
||||||
|
tempLayer?.removeFromSuperlayer()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var imageUpdate: UIImage?
|
||||||
|
if let (transform, arguments, image) = next {
|
||||||
|
strongSelf.currentTransform = transform
|
||||||
|
strongSelf.currentArguments = arguments
|
||||||
|
strongSelf.layer.contents = image?.cgImage
|
||||||
|
imageUpdate = image
|
||||||
|
}
|
||||||
|
if let _ = strongSelf.overlayColor {
|
||||||
|
strongSelf.applyOverlayColor(animated: false)
|
||||||
|
}
|
||||||
|
if let imageUpdated = strongSelf.imageUpdated {
|
||||||
|
imageUpdated(imageUpdate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dispatchOnDisplayLink && !attemptSynchronously {
|
||||||
|
displayLinkDispatcher.dispatch {
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func asyncLayout() -> (TransformImageArguments) -> (() -> Void) {
|
||||||
|
let currentTransform = self.currentTransform
|
||||||
|
let currentArguments = self.currentArguments
|
||||||
|
return { [weak self] arguments in
|
||||||
|
let updatedImage: UIImage?
|
||||||
|
if currentArguments != arguments {
|
||||||
|
updatedImage = currentTransform?(arguments)?.generateImage()
|
||||||
|
} else {
|
||||||
|
updatedImage = nil
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let image = updatedImage {
|
||||||
|
strongSelf.layer.contents = image.cgImage
|
||||||
|
strongSelf.currentArguments = arguments
|
||||||
|
if let _ = strongSelf.overlayColor {
|
||||||
|
strongSelf.applyOverlayColor(animated: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strongSelf.argumentsPromise.set(arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func setOverlayColor(_ color: UIColor?, animated: Bool) {
|
||||||
|
var updated = false
|
||||||
|
if let overlayColor = self.overlayColor, let color = color {
|
||||||
|
updated = !overlayColor.isEqual(color)
|
||||||
|
} else if (self.overlayColor != nil) != (color != nil) {
|
||||||
|
updated = true
|
||||||
|
}
|
||||||
|
if updated {
|
||||||
|
self.overlayColor = color
|
||||||
|
if let _ = self.overlayColor {
|
||||||
|
self.applyOverlayColor(animated: animated)
|
||||||
|
} else if let overlayView = self.overlayView {
|
||||||
|
self.overlayView = nil
|
||||||
|
if animated {
|
||||||
|
overlayView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak overlayView] _ in
|
||||||
|
overlayView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
overlayView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func applyOverlayColor(animated: Bool) {
|
||||||
|
if let overlayColor = self.overlayColor {
|
||||||
|
if let contents = self.layer.contents, CFGetTypeID(contents as CFTypeRef) == CGImage.typeID {
|
||||||
|
if let overlayView = self.overlayView {
|
||||||
|
(overlayView as! UIImageView).image = UIImage(cgImage: contents as! CGImage).withRenderingMode(.alwaysTemplate)
|
||||||
|
overlayView.tintColor = overlayColor
|
||||||
|
} else {
|
||||||
|
let overlayView = UIImageView()
|
||||||
|
overlayView.image = UIImage(cgImage: contents as! CGImage).withRenderingMode(.alwaysTemplate)
|
||||||
|
overlayView.tintColor = overlayColor
|
||||||
|
overlayView.frame = self.bounds
|
||||||
|
self.addSubview(overlayView)
|
||||||
|
self.overlayView = overlayView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,7 +29,7 @@ public protocol SparseItemGridDisplayItem: AnyObject {
|
|||||||
public protocol SparseItemGridBinding: AnyObject {
|
public protocol SparseItemGridBinding: AnyObject {
|
||||||
func createLayer() -> SparseItemGridLayer?
|
func createLayer() -> SparseItemGridLayer?
|
||||||
func createView() -> SparseItemGridView?
|
func createView() -> SparseItemGridView?
|
||||||
func bindLayers(items: [SparseItemGrid.Item], layers: [SparseItemGridDisplayItem])
|
func bindLayers(items: [SparseItemGrid.Item], layers: [SparseItemGridDisplayItem], synchronous: Bool)
|
||||||
func unbindLayer(layer: SparseItemGridLayer)
|
func unbindLayer(layer: SparseItemGridLayer)
|
||||||
func scrollerTextForTag(tag: Int32) -> String?
|
func scrollerTextForTag(tag: Int32) -> String?
|
||||||
func loadHole(anchor: SparseItemGrid.HoleAnchor, at location: SparseItemGrid.HoleLocation) -> Signal<Never, NoError>
|
func loadHole(anchor: SparseItemGrid.HoleAnchor, at location: SparseItemGrid.HoleLocation) -> Signal<Never, NoError>
|
||||||
@ -464,7 +464,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
self.layout = Layout(containerLayout: containerLayout, zoomLevel: self.zoomLevel)
|
self.layout = Layout(containerLayout: containerLayout, zoomLevel: self.zoomLevel)
|
||||||
self.items = items
|
self.items = items
|
||||||
|
|
||||||
self.updateVisibleItems(resetScrolling: true, restoreScrollPosition: restoreScrollPosition)
|
self.updateVisibleItems(resetScrolling: true, synchronous: false, restoreScrollPosition: restoreScrollPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -474,7 +474,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
|
|
||||||
@objc func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
@objc func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||||
if !self.ignoreScrolling {
|
if !self.ignoreScrolling {
|
||||||
self.updateVisibleItems(resetScrolling: false, restoreScrollPosition: nil)
|
self.updateVisibleItems(resetScrolling: false, synchronous: true, restoreScrollPosition: nil)
|
||||||
|
|
||||||
if let layout = self.layout, let items = self.items {
|
if let layout = self.layout, let items = self.items {
|
||||||
let offset = scrollView.contentOffset.y
|
let offset = scrollView.contentOffset.y
|
||||||
@ -660,7 +660,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateVisibleItems(resetScrolling: Bool, restoreScrollPosition: (y: CGFloat, index: Int)?) {
|
private func updateVisibleItems(resetScrolling: Bool, synchronous: Bool, restoreScrollPosition: (y: CGFloat, index: Int)?) {
|
||||||
guard let layout = self.layout, let items = self.items else {
|
guard let layout = self.layout, let items = self.items else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -753,7 +753,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !bindItems.isEmpty {
|
if !bindItems.isEmpty {
|
||||||
items.itemBinding.bindLayers(items: bindItems, layers: bindLayers)
|
items.itemBinding.bindLayers(items: bindItems, layers: bindLayers, synchronous: synchronous)
|
||||||
}
|
}
|
||||||
|
|
||||||
for item in updateLayers {
|
for item in updateLayers {
|
||||||
|
|||||||
@ -159,17 +159,28 @@ public final class TooltipComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class View: UIView {
|
public final class View: UIView {
|
||||||
private let backgroundNode: NavigationBackgroundNode
|
private let backgroundView: UIView
|
||||||
|
private let backgroundViewMask: UIImageView
|
||||||
private var icon: ComponentHostView<Empty>?
|
private var icon: ComponentHostView<Empty>?
|
||||||
private let content: ComponentHostView<Empty>
|
private let content: ComponentHostView<Empty>
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.backgroundNode = NavigationBackgroundNode(color: UIColor(white: 0.2, alpha: 0.7))
|
self.backgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||||
|
self.backgroundViewMask = UIImageView()
|
||||||
|
|
||||||
|
self.backgroundViewMask.image = generateImage(CGSize(width: 42.0, height: 42.0), rotatedContext: { size, context in
|
||||||
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
|
context.setFillColor(UIColor.black.cgColor)
|
||||||
|
let _ = try? drawSvgPath(context, path: "M0,18.0252 C0,14.1279 0,12.1792 0.5358,10.609 C1.5362,7.6772 3.8388,5.3746 6.7706,4.3742 C8.3409,3.8384 10.2895,3.8384 14.1868,3.8384 L16.7927,3.8384 C18.2591,3.8384 18.9923,3.8384 19.7211,3.8207 C25.1911,3.6877 30.6172,2.8072 35.8485,1.2035 C36.5454,0.9899 37.241,0.758 38.6321,0.2943 C39.1202,0.1316 39.3643,0.0503 39.5299,0.0245 C40.8682,-0.184 42.0224,0.9702 41.8139,2.3085 C41.7881,2.4741 41.7067,2.7181 41.544,3.2062 C41.0803,4.5974 40.8485,5.293 40.6348,5.99 C39.0312,11.2213 38.1507,16.6473 38.0177,22.1173 C38,22.846 38,23.5793 38,25.0457 L38,27.6516 C38,31.5489 38,33.4975 37.4642,35.0677 C36.4638,37.9995 34.1612,40.3022 31.2294,41.3026 C29.6591,41.8384 27.7105,41.8384 23.8132,41.8384 L16,41.8384 C10.3995,41.8384 7.5992,41.8384 5.4601,40.7484 C3.5785,39.7897 2.0487,38.2599 1.0899,36.3783 C0,34.2392 0,31.4389 0,25.8384 L0,18.0252 Z ")
|
||||||
|
})?.stretchableImage(withLeftCapWidth: 16, topCapHeight: 34)
|
||||||
|
|
||||||
self.content = ComponentHostView<Empty>()
|
self.content = ComponentHostView<Empty>()
|
||||||
|
|
||||||
super.init(frame: CGRect())
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
self.addSubnode(self.backgroundNode)
|
self.addSubview(self.backgroundView)
|
||||||
|
self.backgroundView.mask = self.backgroundViewMask
|
||||||
self.addSubview(self.content)
|
self.addSubview(self.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,8 +239,10 @@ public final class TooltipComponent: Component {
|
|||||||
contentRect.origin.y = component.arrowLocation.minY - contentRect.height
|
contentRect.origin.y = component.arrowLocation.minY - contentRect.height
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.setFrame(view: self.backgroundNode.view, frame: contentRect)
|
let maskedBackgroundFrame = CGRect(origin: CGPoint(x: contentRect.minX, y: contentRect.minY - 4.0), size: CGSize(width: contentRect.width + 4.0, height: contentRect.height + 4.0))
|
||||||
self.backgroundNode.update(size: contentRect.size, cornerRadius: 8.0, transition: .immediate)
|
|
||||||
|
self.backgroundView.frame = maskedBackgroundFrame
|
||||||
|
self.backgroundViewMask.frame = CGRect(origin: CGPoint(), size: maskedBackgroundFrame.size)
|
||||||
|
|
||||||
if let iconSize = iconSize, let icon = self.icon {
|
if let iconSize = iconSize, let icon = self.icon {
|
||||||
transition.setFrame(view: icon, frame: CGRect(origin: CGPoint(x: contentRect.minX + insets.left, y: contentRect.minY + insets.top + floor((contentRect.height - insets.top - insets.bottom - iconSize.height) / 2.0)), size: iconSize))
|
transition.setFrame(view: icon, frame: CGRect(origin: CGPoint(x: contentRect.minX + insets.left, y: contentRect.minY + insets.top + floor((contentRect.height - insets.top - insets.bottom - iconSize.height) / 2.0)), size: iconSize))
|
||||||
@ -378,7 +391,7 @@ private final class ShadowRoundedRectangle: Component {
|
|||||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
context.setFillColor(component.color.cgColor)
|
context.setFillColor(component.color.cgColor)
|
||||||
context.setShadow(offset: CGSize(width: 0.0, height: -2.0), blur: 5.0, color: UIColor(white: 0.0, alpha: 0.3).cgColor)
|
context.setShadow(offset: CGSize(width: 0.0, height: -1.0), blur: 4.0, color: UIColor(white: 0.0, alpha: 0.2).cgColor)
|
||||||
|
|
||||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset, y: shadowInset), size: CGSize(width: size.width - shadowInset * 2.0, height: size.height - shadowInset * 2.0)))
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: shadowInset, y: shadowInset), size: CGSize(width: size.width - shadowInset * 2.0, height: size.height - shadowInset * 2.0)))
|
||||||
})?.stretchableImage(withLeftCapWidth: Int(diameter + shadowInset * 2.0) / 2, topCapHeight: Int(diameter + shadowInset * 2.0) / 2)
|
})?.stretchableImage(withLeftCapWidth: Int(diameter + shadowInset * 2.0) / 2, topCapHeight: Int(diameter + shadowInset * 2.0) / 2)
|
||||||
@ -752,6 +765,11 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
|||||||
) {
|
) {
|
||||||
self.containerSize = containerSize
|
self.containerSize = containerSize
|
||||||
|
|
||||||
|
if self.dateIndicator.alpha.isZero {
|
||||||
|
let transition: ContainedViewLayoutTransition = .immediate
|
||||||
|
transition.updateSublayerTransformOffset(layer: self.dateIndicator.layer, offset: CGPoint())
|
||||||
|
}
|
||||||
|
|
||||||
if isScrolling {
|
if isScrolling {
|
||||||
self.updateActivityTimer(isScrolling: true)
|
self.updateActivityTimer(isScrolling: true)
|
||||||
}
|
}
|
||||||
@ -781,15 +799,15 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
|||||||
let topIndicatorInset: CGFloat = indicatorVerticalInset + containerInsets.top
|
let topIndicatorInset: CGFloat = indicatorVerticalInset + containerInsets.top
|
||||||
let bottomIndicatorInset: CGFloat = indicatorVerticalInset + containerInsets.bottom
|
let bottomIndicatorInset: CGFloat = indicatorVerticalInset + containerInsets.bottom
|
||||||
|
|
||||||
let scrollIndicatorHeight = max(35.0, ceil(scrollIndicatorHeightFraction * containerSize.height))
|
let scrollIndicatorHeight = max(44.0, ceil(scrollIndicatorHeightFraction * containerSize.height))
|
||||||
|
|
||||||
let indicatorPositionFraction = min(1.0, max(0.0, contentOffset / (contentHeight - containerSize.height)))
|
let indicatorPositionFraction = min(1.0, max(0.0, contentOffset / (contentHeight - containerSize.height)))
|
||||||
|
|
||||||
let indicatorTopPosition = topIndicatorInset
|
let indicatorTopPosition = topIndicatorInset
|
||||||
let indicatorBottomPosition = containerSize.height - bottomIndicatorInset - scrollIndicatorHeight
|
let indicatorBottomPosition = containerSize.height - bottomIndicatorInset - scrollIndicatorHeight
|
||||||
|
|
||||||
let dateIndicatorTopPosition = topIndicatorInset
|
let dateIndicatorTopPosition = topIndicatorInset + 4.0
|
||||||
let dateIndicatorBottomPosition = containerSize.height - bottomIndicatorInset - indicatorSize.height
|
let dateIndicatorBottomPosition = containerSize.height - bottomIndicatorInset - 4.0 - indicatorSize.height
|
||||||
|
|
||||||
self.indicatorPosition = indicatorTopPosition * (1.0 - indicatorPositionFraction) + indicatorBottomPosition * indicatorPositionFraction
|
self.indicatorPosition = indicatorTopPosition * (1.0 - indicatorPositionFraction) + indicatorBottomPosition * indicatorPositionFraction
|
||||||
self.scrollIndicatorHeight = scrollIndicatorHeight
|
self.scrollIndicatorHeight = scrollIndicatorHeight
|
||||||
@ -805,8 +823,9 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
|||||||
|
|
||||||
transition.updateFrame(view: self.dateIndicator, frame: CGRect(origin: CGPoint(x: containerSize.width - 12.0 - indicatorSize.width, y: dateIndicatorPosition), size: indicatorSize))
|
transition.updateFrame(view: self.dateIndicator, frame: CGRect(origin: CGPoint(x: containerSize.width - 12.0 - indicatorSize.width, y: dateIndicatorPosition), size: indicatorSize))
|
||||||
if isScrolling {
|
if isScrolling {
|
||||||
self.dateIndicator.alpha = 1.0
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
|
||||||
self.lineIndicator.alpha = 1.0
|
transition.updateAlpha(layer: self.dateIndicator.layer, alpha: 1.0)
|
||||||
|
transition.updateAlpha(layer: self.lineIndicator.layer, alpha: 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateLineTooltip(containerSize: containerSize)
|
self.updateLineTooltip(containerSize: containerSize)
|
||||||
@ -823,7 +842,7 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let lineIndicatorSize = CGSize(width: self.isDragging ? 6.0 : 3.0, height: scrollIndicatorHeight)
|
let lineIndicatorSize = CGSize(width: (self.isDragging || self.lineTooltip != nil) ? 6.0 : 3.0, height: scrollIndicatorHeight)
|
||||||
let mappedTransition: Transition
|
let mappedTransition: Transition
|
||||||
switch transition {
|
switch transition {
|
||||||
case .immediate:
|
case .immediate:
|
||||||
@ -889,6 +908,9 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
|||||||
|
|
||||||
lineTooltip.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
lineTooltip.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
|
||||||
|
let transition: ContainedViewLayoutTransition = .immediate
|
||||||
|
transition.updateSublayerTransformOffset(layer: self.dateIndicator.layer, offset: CGPoint(x: -3.0, y: 0.0))
|
||||||
|
|
||||||
displayTooltip.completed()
|
displayTooltip.completed()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -912,7 +934,7 @@ public final class SparseItemGridScrollingArea: ASDisplayNode {
|
|||||||
font: Font.regular(13.0),
|
font: Font.regular(13.0),
|
||||||
color: .white
|
color: .white
|
||||||
)),
|
)),
|
||||||
arrowLocation: self.lineIndicator.frame.insetBy(dx: -4.0, dy: -4.0)
|
arrowLocation: self.lineIndicator.frame.insetBy(dx: -3.0, dy: -8.0)
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: containerSize
|
containerSize: containerSize
|
||||||
|
|||||||
@ -922,9 +922,34 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
|||||||
let historyView = (strongSelf.opaqueTransactionState as? ChatHistoryTransactionOpaqueState)?.historyView
|
let historyView = (strongSelf.opaqueTransactionState as? ChatHistoryTransactionOpaqueState)?.historyView
|
||||||
let displayRange = strongSelf.displayedItemRange
|
let displayRange = strongSelf.displayedItemRange
|
||||||
if let filteredEntries = historyView?.filteredEntries, let visibleRange = displayRange.visibleRange {
|
if let filteredEntries = historyView?.filteredEntries, let visibleRange = displayRange.visibleRange {
|
||||||
let firstEntry = filteredEntries[filteredEntries.count - 1 - visibleRange.firstIndex]
|
var anchorIndex: MessageIndex?
|
||||||
|
loop: for index in visibleRange.firstIndex ..< filteredEntries.count {
|
||||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(firstEntry.index), anchorIndex: .message(firstEntry.index), count: historyMessageCount, highlight: false), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
switch filteredEntries[filteredEntries.count - 1 - index] {
|
||||||
|
case let .MessageEntry(message, _, _, _, _, _):
|
||||||
|
if message.adAttribute == nil {
|
||||||
|
anchorIndex = message.index
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
case let .MessageGroupEntry(_, messages, _):
|
||||||
|
for (message, _, _, _) in messages {
|
||||||
|
if message.adAttribute == nil {
|
||||||
|
anchorIndex = message.index
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if anchorIndex == nil, let historyView = historyView {
|
||||||
|
for entry in historyView.originalView.entries {
|
||||||
|
anchorIndex = entry.message.index
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let anchorIndex = anchorIndex {
|
||||||
|
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .Navigation(index: .message(anchorIndex), anchorIndex: .message(anchorIndex), count: historyMessageCount, highlight: false), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if let subject = subject, case let .message(messageId, highlight, _) = subject {
|
if let subject = subject, case let .message(messageId, highlight, _) = subject {
|
||||||
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: highlight), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
strongSelf.chatHistoryLocationValue = ChatHistoryLocationInput(content: .InitialSearch(location: .id(messageId), count: 60, highlight: highlight), id: (strongSelf.chatHistoryLocationValue?.id).flatMap({ $0 + 1 }) ?? 0)
|
||||||
|
|||||||
@ -909,7 +909,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
|
|||||||
return ItemView()
|
return ItemView()
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindLayers(items: [SparseItemGrid.Item], layers: [SparseItemGridDisplayItem]) {
|
func bindLayers(items: [SparseItemGrid.Item], layers: [SparseItemGridDisplayItem], synchronous: Bool) {
|
||||||
for i in 0 ..< items.count {
|
for i in 0 ..< items.count {
|
||||||
guard let item = items[i] as? VisualMediaItem else {
|
guard let item = items[i] as? VisualMediaItem else {
|
||||||
continue
|
continue
|
||||||
@ -964,16 +964,19 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
|
|||||||
if let image = result.image {
|
if let image = result.image {
|
||||||
layer.contents = image.cgImage
|
layer.contents = image.cgImage
|
||||||
layer.hasContents = true
|
layer.hasContents = true
|
||||||
|
if !synchronous {
|
||||||
|
layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let loadSignal = result.loadSignal {
|
if let loadSignal = result.loadSignal {
|
||||||
let shimmerColor = self.getShimmerColors().background
|
|
||||||
layer.disposable = (loadSignal
|
layer.disposable = (loadSignal
|
||||||
|> deliverOnMainQueue).start(next: { [weak layer] image in
|
|> deliverOnMainQueue).start(next: { [weak layer] image in
|
||||||
guard let layer = layer else {
|
guard let layer = layer else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let copyLayer = ItemLayer()
|
let copyLayer = ItemLayer()
|
||||||
copyLayer.backgroundColor = UIColor(rgb: shimmerColor).cgColor
|
copyLayer.contents = layer.contents
|
||||||
|
copyLayer.contentsRect = layer.contentsRect
|
||||||
copyLayer.frame = layer.bounds
|
copyLayer.frame = layer.bounds
|
||||||
layer.addSublayer(copyLayer)
|
layer.addSublayer(copyLayer)
|
||||||
copyLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak copyLayer] _ in
|
copyLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak copyLayer] _ in
|
||||||
@ -1560,7 +1563,17 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
)
|
)
|
||||||
|
|
||||||
if let (size, topInset, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams {
|
if let (size, topInset, sideInset, bottomInset, visibleHeight, isScrollingLockedAtTop, expandProgress, presentationData) = self.currentParams {
|
||||||
|
var gridSnapshot: UIView?
|
||||||
|
if reloadAtTop {
|
||||||
|
gridSnapshot = self.itemGrid.view.snapshotView(afterScreenUpdates: false)
|
||||||
|
}
|
||||||
self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: false, transition: .immediate)
|
self.update(size: size, topInset: topInset, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: isScrollingLockedAtTop, expandProgress: expandProgress, presentationData: presentationData, synchronous: false, transition: .immediate)
|
||||||
|
if let gridSnapshot = gridSnapshot {
|
||||||
|
self.view.addSubview(gridSnapshot)
|
||||||
|
gridSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak gridSnapshot] _ in
|
||||||
|
gridSnapshot?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.didSetReady {
|
if !self.didSetReady {
|
||||||
|
|||||||
@ -1666,6 +1666,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
let subtitleNodeContainer: ASDisplayNode
|
let subtitleNodeContainer: ASDisplayNode
|
||||||
let subtitleNodeRawContainer: ASDisplayNode
|
let subtitleNodeRawContainer: ASDisplayNode
|
||||||
let subtitleNode: MultiScaleTextNode
|
let subtitleNode: MultiScaleTextNode
|
||||||
|
let panelSubtitleNode: MultiScaleTextNode
|
||||||
let usernameNodeContainer: ASDisplayNode
|
let usernameNodeContainer: ASDisplayNode
|
||||||
let usernameNodeRawContainer: ASDisplayNode
|
let usernameNodeRawContainer: ASDisplayNode
|
||||||
let usernameNode: MultiScaleTextNode
|
let usernameNode: MultiScaleTextNode
|
||||||
@ -1721,6 +1722,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
self.subtitleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
|
self.subtitleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
|
||||||
self.subtitleNode.displaysAsynchronously = false
|
self.subtitleNode.displaysAsynchronously = false
|
||||||
|
|
||||||
|
self.panelSubtitleNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
|
||||||
|
self.panelSubtitleNode.displaysAsynchronously = false
|
||||||
|
|
||||||
self.usernameNodeContainer = ASDisplayNode()
|
self.usernameNodeContainer = ASDisplayNode()
|
||||||
self.usernameNodeRawContainer = ASDisplayNode()
|
self.usernameNodeRawContainer = ASDisplayNode()
|
||||||
self.usernameNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
|
self.usernameNode = MultiScaleTextNode(stateKeys: [TitleNodeStateRegular, TitleNodeStateExpanded])
|
||||||
@ -1770,6 +1774,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
self.titleNodeContainer.addSubnode(self.titleNode)
|
self.titleNodeContainer.addSubnode(self.titleNode)
|
||||||
self.regularContentNode.addSubnode(self.titleNodeContainer)
|
self.regularContentNode.addSubnode(self.titleNodeContainer)
|
||||||
self.subtitleNodeContainer.addSubnode(self.subtitleNode)
|
self.subtitleNodeContainer.addSubnode(self.subtitleNode)
|
||||||
|
self.subtitleNodeContainer.addSubnode(self.panelSubtitleNode)
|
||||||
self.regularContentNode.addSubnode(self.subtitleNodeContainer)
|
self.regularContentNode.addSubnode(self.subtitleNodeContainer)
|
||||||
self.regularContentNode.addSubnode(self.subtitleNodeRawContainer)
|
self.regularContentNode.addSubnode(self.subtitleNodeRawContainer)
|
||||||
self.usernameNodeContainer.addSubnode(self.usernameNode)
|
self.usernameNodeContainer.addSubnode(self.usernameNode)
|
||||||
@ -1899,7 +1904,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var initializedCredibilityIcon = false
|
var initializedCredibilityIcon = false
|
||||||
func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, notificationSettings: TelegramPeerNotificationSettings?, statusData: PeerInfoStatusData?, isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, transition: ContainedViewLayoutTransition, additive: Bool) -> CGFloat {
|
func update(width: CGFloat, containerHeight: CGFloat, containerInset: CGFloat, statusBarHeight: CGFloat, navigationHeight: CGFloat, isModalOverlay: Bool, isMediaOnly: Bool, contentOffset: CGFloat, paneContainerY: CGFloat, presentationData: PresentationData, peer: Peer?, cachedData: CachedPeerData?, notificationSettings: TelegramPeerNotificationSettings?, statusData: PeerInfoStatusData?, panelStatusData: PeerInfoStatusData?, isSecretChat: Bool, isContact: Bool, isSettings: Bool, state: PeerInfoState, transition: ContainedViewLayoutTransition, additive: Bool) -> CGFloat {
|
||||||
self.state = state
|
self.state = state
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
self.avatarListNode.listContainerNode.peer = peer
|
self.avatarListNode.listContainerNode.peer = peer
|
||||||
@ -2019,6 +2024,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
var isVerified = false
|
var isVerified = false
|
||||||
let titleString: NSAttributedString
|
let titleString: NSAttributedString
|
||||||
let subtitleString: NSAttributedString
|
let subtitleString: NSAttributedString
|
||||||
|
var panelSubtitleString: NSAttributedString?
|
||||||
let usernameString: NSAttributedString
|
let usernameString: NSAttributedString
|
||||||
if let peer = peer, peer.isVerified {
|
if let peer = peer, peer.isVerified {
|
||||||
isVerified = true
|
isVerified = true
|
||||||
@ -2063,6 +2069,16 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
}
|
}
|
||||||
subtitleString = NSAttributedString(string: statusData.text, font: Font.regular(15.0), textColor: subtitleColor)
|
subtitleString = NSAttributedString(string: statusData.text, font: Font.regular(15.0), textColor: subtitleColor)
|
||||||
usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
||||||
|
|
||||||
|
if let panelStatusData = panelStatusData {
|
||||||
|
let subtitleColor: UIColor
|
||||||
|
if panelStatusData.isActivity {
|
||||||
|
subtitleColor = presentationData.theme.list.itemAccentColor
|
||||||
|
} else {
|
||||||
|
subtitleColor = presentationData.theme.list.itemSecondaryTextColor
|
||||||
|
}
|
||||||
|
panelSubtitleString = NSAttributedString(string: panelStatusData.text, font: Font.regular(15.0), textColor: subtitleColor)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
subtitleString = NSAttributedString(string: " ", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
subtitleString = NSAttributedString(string: " ", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
||||||
usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
usernameString = NSAttributedString(string: "", font: Font.regular(15.0), textColor: presentationData.theme.list.itemSecondaryTextColor)
|
||||||
@ -2090,6 +2106,12 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
], mainState: TitleNodeStateRegular)
|
], mainState: TitleNodeStateRegular)
|
||||||
self.subtitleNode.accessibilityLabel = subtitleString.string
|
self.subtitleNode.accessibilityLabel = subtitleString.string
|
||||||
|
|
||||||
|
let panelSubtitleNodeLayout = self.panelSubtitleNode.updateLayout(states: [
|
||||||
|
TitleNodeStateRegular: MultiScaleTextState(attributedText: panelSubtitleString ?? subtitleString, constrainedSize: titleConstrainedSize),
|
||||||
|
TitleNodeStateExpanded: MultiScaleTextState(attributedText: panelSubtitleString ?? subtitleString, constrainedSize: CGSize(width: titleConstrainedSize.width - 82.0, height: titleConstrainedSize.height))
|
||||||
|
], mainState: TitleNodeStateRegular)
|
||||||
|
self.panelSubtitleNode.accessibilityLabel = (panelSubtitleString ?? subtitleString).string
|
||||||
|
|
||||||
let usernameNodeLayout = self.usernameNode.updateLayout(states: [
|
let usernameNodeLayout = self.usernameNode.updateLayout(states: [
|
||||||
TitleNodeStateRegular: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: titleConstrainedSize.width, height: titleConstrainedSize.height)),
|
TitleNodeStateRegular: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: titleConstrainedSize.width, height: titleConstrainedSize.height)),
|
||||||
TitleNodeStateExpanded: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: width - titleNodeLayout[TitleNodeStateExpanded]!.size.width - 8.0, height: titleConstrainedSize.height))
|
TitleNodeStateExpanded: MultiScaleTextState(attributedText: usernameString, constrainedSize: CGSize(width: width - titleNodeLayout[TitleNodeStateExpanded]!.size.width - 8.0, height: titleConstrainedSize.height))
|
||||||
@ -2102,6 +2124,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
let titleSize = titleNodeLayout[TitleNodeStateRegular]!.size
|
let titleSize = titleNodeLayout[TitleNodeStateRegular]!.size
|
||||||
let titleExpandedSize = titleNodeLayout[TitleNodeStateExpanded]!.size
|
let titleExpandedSize = titleNodeLayout[TitleNodeStateExpanded]!.size
|
||||||
let subtitleSize = subtitleNodeLayout[TitleNodeStateRegular]!.size
|
let subtitleSize = subtitleNodeLayout[TitleNodeStateRegular]!.size
|
||||||
|
let _ = panelSubtitleNodeLayout[TitleNodeStateRegular]!.size
|
||||||
let usernameSize = usernameNodeLayout[TitleNodeStateRegular]!.size
|
let usernameSize = usernameNodeLayout[TitleNodeStateRegular]!.size
|
||||||
|
|
||||||
if let image = self.titleCredibilityIconNode.image {
|
if let image = self.titleCredibilityIconNode.image {
|
||||||
@ -2151,17 +2174,53 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
|
|
||||||
let apparentTitleLockOffset = (1.0 - titleCollapseFraction) * 0.0 + titleCollapseFraction * titleMaxLockOffset
|
let apparentTitleLockOffset = (1.0 - titleCollapseFraction) * 0.0 + titleCollapseFraction * titleMaxLockOffset
|
||||||
|
|
||||||
|
let paneAreaExpansionDistance: CGFloat = 32.0
|
||||||
|
let effectiveAreaExpansionFraction: CGFloat
|
||||||
|
if state.isEditing {
|
||||||
|
effectiveAreaExpansionFraction = 0.0
|
||||||
|
} else if isSettings {
|
||||||
|
var paneAreaExpansionDelta = (self.frame.maxY - navigationHeight) - contentOffset
|
||||||
|
paneAreaExpansionDelta = max(0.0, min(paneAreaExpansionDelta, paneAreaExpansionDistance))
|
||||||
|
effectiveAreaExpansionFraction = 1.0 - paneAreaExpansionDelta / paneAreaExpansionDistance
|
||||||
|
} else {
|
||||||
|
var paneAreaExpansionDelta = (paneContainerY - navigationHeight) - contentOffset
|
||||||
|
paneAreaExpansionDelta = max(0.0, min(paneAreaExpansionDelta, paneAreaExpansionDistance))
|
||||||
|
effectiveAreaExpansionFraction = 1.0 - paneAreaExpansionDelta / paneAreaExpansionDistance
|
||||||
|
}
|
||||||
|
|
||||||
self.titleNode.update(stateFractions: [
|
self.titleNode.update(stateFractions: [
|
||||||
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
|
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
|
||||||
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
|
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
|
||||||
], transition: transition)
|
], transition: transition)
|
||||||
|
|
||||||
let subtitleAlpha: CGFloat = self.isSettings ? 1.0 - titleCollapseFraction : 1.0
|
let subtitleAlpha: CGFloat
|
||||||
|
var subtitleOffset: CGFloat = 0.0
|
||||||
|
let panelSubtitleAlpha: CGFloat
|
||||||
|
var panelSubtitleOffset: CGFloat = 0.0
|
||||||
|
if self.isSettings {
|
||||||
|
subtitleAlpha = 1.0 - titleCollapseFraction
|
||||||
|
panelSubtitleAlpha = 0.0
|
||||||
|
} else {
|
||||||
|
if (panelSubtitleString ?? subtitleString).string != subtitleString.string {
|
||||||
|
subtitleAlpha = 1.0 - effectiveAreaExpansionFraction
|
||||||
|
panelSubtitleAlpha = effectiveAreaExpansionFraction
|
||||||
|
subtitleOffset = -effectiveAreaExpansionFraction * 5.0
|
||||||
|
panelSubtitleOffset = (1.0 - effectiveAreaExpansionFraction) * 5.0
|
||||||
|
} else {
|
||||||
|
subtitleAlpha = 1.0
|
||||||
|
panelSubtitleAlpha = 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
self.subtitleNode.update(stateFractions: [
|
self.subtitleNode.update(stateFractions: [
|
||||||
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
|
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
|
||||||
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
|
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
|
||||||
], alpha: subtitleAlpha, transition: transition)
|
], alpha: subtitleAlpha, transition: transition)
|
||||||
|
|
||||||
|
self.panelSubtitleNode.update(stateFractions: [
|
||||||
|
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
|
||||||
|
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
|
||||||
|
], alpha: panelSubtitleAlpha, transition: transition)
|
||||||
|
|
||||||
self.usernameNode.update(stateFractions: [
|
self.usernameNode.update(stateFractions: [
|
||||||
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
|
TitleNodeStateRegular: self.isAvatarExpanded ? 0.0 : 1.0,
|
||||||
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
|
TitleNodeStateExpanded: self.isAvatarExpanded ? 1.0 : 0.0
|
||||||
@ -2315,7 +2374,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
let rawSubtitleFrame = CGRect(origin: CGPoint(x: subtitleCenter.x - subtitleFrame.size.width / 2.0, y: subtitleCenter.y - subtitleFrame.size.height / 2.0), size: subtitleFrame.size)
|
let rawSubtitleFrame = CGRect(origin: CGPoint(x: subtitleCenter.x - subtitleFrame.size.width / 2.0, y: subtitleCenter.y - subtitleFrame.size.height / 2.0), size: subtitleFrame.size)
|
||||||
self.subtitleNodeRawContainer.frame = rawSubtitleFrame
|
self.subtitleNodeRawContainer.frame = rawSubtitleFrame
|
||||||
transition.updateFrameAdditiveToCenter(node: self.subtitleNodeContainer, frame: CGRect(origin: rawSubtitleFrame.center, size: CGSize()))
|
transition.updateFrameAdditiveToCenter(node: self.subtitleNodeContainer, frame: CGRect(origin: rawSubtitleFrame.center, size: CGSize()))
|
||||||
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
|
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: subtitleOffset), size: CGSize()))
|
||||||
|
transition.updateFrame(node: self.panelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset), size: CGSize()))
|
||||||
transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
|
transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
|
||||||
transition.updateSublayerTransformScale(node: self.titleNodeContainer, scale: titleScale)
|
transition.updateSublayerTransformScale(node: self.titleNodeContainer, scale: titleScale)
|
||||||
transition.updateSublayerTransformScale(node: self.subtitleNodeContainer, scale: subtitleScale)
|
transition.updateSublayerTransformScale(node: self.subtitleNodeContainer, scale: subtitleScale)
|
||||||
@ -2353,7 +2413,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
|||||||
usernameCenter.x = rawTitleFrame.center.x + (usernameCenter.x - rawTitleFrame.center.x) * subtitleScale
|
usernameCenter.x = rawTitleFrame.center.x + (usernameCenter.x - rawTitleFrame.center.x) * subtitleScale
|
||||||
transition.updateFrameAdditiveToCenter(node: self.usernameNodeContainer, frame: CGRect(origin: usernameCenter, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset))
|
transition.updateFrameAdditiveToCenter(node: self.usernameNodeContainer, frame: CGRect(origin: usernameCenter, size: CGSize()).offsetBy(dx: 0.0, dy: titleOffset))
|
||||||
}
|
}
|
||||||
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
|
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: subtitleOffset), size: CGSize()))
|
||||||
|
transition.updateFrame(node: self.panelSubtitleNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelSubtitleOffset), size: CGSize()))
|
||||||
transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
|
transition.updateFrame(node: self.usernameNode, frame: CGRect(origin: CGPoint(), size: CGSize()))
|
||||||
transition.updateSublayerTransformScaleAdditive(node: self.titleNodeContainer, scale: titleScale)
|
transition.updateSublayerTransformScaleAdditive(node: self.titleNodeContainer, scale: titleScale)
|
||||||
transition.updateSublayerTransformScaleAdditive(node: self.subtitleNodeContainer, scale: subtitleScale)
|
transition.updateSublayerTransformScaleAdditive(node: self.subtitleNodeContainer, scale: subtitleScale)
|
||||||
|
|||||||
@ -454,6 +454,7 @@ private final class PeerInfoPendingPane {
|
|||||||
final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let peerId: PeerId
|
private let peerId: PeerId
|
||||||
|
private let isMediaOnly: Bool
|
||||||
|
|
||||||
weak var parentController: ViewController?
|
weak var parentController: ViewController?
|
||||||
|
|
||||||
@ -478,6 +479,11 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let currentPaneStatusPromise = Promise<PeerInfoStatusData?>(nil)
|
||||||
|
var currentPaneStatus: Signal<PeerInfoStatusData?, NoError> {
|
||||||
|
return self.currentPaneStatusPromise.get()
|
||||||
|
}
|
||||||
|
|
||||||
private var currentPanes: [PeerInfoPaneKey: PeerInfoPaneWrapper] = [:]
|
private var currentPanes: [PeerInfoPaneKey: PeerInfoPaneWrapper] = [:]
|
||||||
private var pendingPanes: [PeerInfoPaneKey: PeerInfoPendingPane] = [:]
|
private var pendingPanes: [PeerInfoPaneKey: PeerInfoPendingPane] = [:]
|
||||||
|
|
||||||
@ -498,10 +504,11 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
|||||||
private var currentAvailablePanes: [PeerInfoPaneKey]?
|
private var currentAvailablePanes: [PeerInfoPaneKey]?
|
||||||
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||||
|
|
||||||
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: PeerId) {
|
init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: PeerId, isMediaOnly: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.updatedPresentationData = updatedPresentationData
|
self.updatedPresentationData = updatedPresentationData
|
||||||
self.peerId = peerId
|
self.peerId = peerId
|
||||||
|
self.isMediaOnly = isMediaOnly
|
||||||
|
|
||||||
self.separatorNode = ASDisplayNode()
|
self.separatorNode = ASDisplayNode()
|
||||||
self.separatorNode.isLayerBacked = true
|
self.separatorNode.isLayerBacked = true
|
||||||
@ -539,6 +546,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
|||||||
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .animated(duration: 0.4, curve: .spring))
|
strongSelf.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .animated(duration: 0.4, curve: .spring))
|
||||||
|
|
||||||
strongSelf.currentPaneUpdated?(true)
|
strongSelf.currentPaneUpdated?(true)
|
||||||
|
|
||||||
|
strongSelf.currentPaneStatusPromise.set(strongSelf.currentPane?.node.status ?? .single(nil))
|
||||||
}
|
}
|
||||||
} else if strongSelf.pendingSwitchToPaneKey != key {
|
} else if strongSelf.pendingSwitchToPaneKey != key {
|
||||||
strongSelf.pendingSwitchToPaneKey = key
|
strongSelf.pendingSwitchToPaneKey = key
|
||||||
@ -642,6 +651,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
|||||||
self.transitionFraction = 0.0
|
self.transitionFraction = 0.0
|
||||||
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .animated(duration: 0.35, curve: .spring))
|
self.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, expansionFraction: expansionFraction, presentationData: presentationData, data: data, transition: .animated(duration: 0.35, curve: .spring))
|
||||||
self.currentPaneUpdated?(false)
|
self.currentPaneUpdated?(false)
|
||||||
|
|
||||||
|
self.currentPaneStatusPromise.set(self.currentPane?.node.status ?? .single(nil))
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
@ -683,6 +694,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
|||||||
self.currentAvailablePanes = availablePanes
|
self.currentAvailablePanes = availablePanes
|
||||||
|
|
||||||
let previousCurrentPaneKey = self.currentPaneKey
|
let previousCurrentPaneKey = self.currentPaneKey
|
||||||
|
var updateCurrentPaneStatus = false
|
||||||
|
|
||||||
if let currentPaneKey = self.currentPaneKey, !availablePanes.contains(currentPaneKey) {
|
if let currentPaneKey = self.currentPaneKey, !availablePanes.contains(currentPaneKey) {
|
||||||
var nextCandidatePaneKey: PeerInfoPaneKey?
|
var nextCandidatePaneKey: PeerInfoPaneKey?
|
||||||
@ -831,6 +843,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
|||||||
self.pendingSwitchToPaneKey = nil
|
self.pendingSwitchToPaneKey = nil
|
||||||
previousPaneKey = self.currentPaneKey
|
previousPaneKey = self.currentPaneKey
|
||||||
self.currentPaneKey = pendingSwitchToPaneKey
|
self.currentPaneKey = pendingSwitchToPaneKey
|
||||||
|
updateCurrentPaneStatus = true
|
||||||
updatedCurrentIndex = availablePanes.firstIndex(of: pendingSwitchToPaneKey)
|
updatedCurrentIndex = availablePanes.firstIndex(of: pendingSwitchToPaneKey)
|
||||||
if let previousPaneKey = previousPaneKey, let previousIndex = availablePanes.firstIndex(of: previousPaneKey), let updatedCurrentIndex = updatedCurrentIndex {
|
if let previousPaneKey = previousPaneKey, let previousIndex = availablePanes.firstIndex(of: previousPaneKey), let updatedCurrentIndex = updatedCurrentIndex {
|
||||||
if updatedCurrentIndex < previousIndex {
|
if updatedCurrentIndex < previousIndex {
|
||||||
@ -900,7 +913,7 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
|||||||
tabsOffset = currentPane.node.tabBarOffset
|
tabsOffset = currentPane.node.tabBarOffset
|
||||||
}
|
}
|
||||||
tabsOffset = max(0.0, min(tabsHeight, tabsOffset))
|
tabsOffset = max(0.0, min(tabsHeight, tabsOffset))
|
||||||
if isScrollingLockedAtTop {
|
if isScrollingLockedAtTop || self.isMediaOnly {
|
||||||
tabsOffset = 0.0
|
tabsOffset = 0.0
|
||||||
}
|
}
|
||||||
var tabsAlpha = 1.0 - tabsOffset / tabsHeight
|
var tabsAlpha = 1.0 - tabsOffset / tabsHeight
|
||||||
@ -954,5 +967,8 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
|
|||||||
if let previousCurrentPaneKey = previousCurrentPaneKey, self.currentPaneKey != previousCurrentPaneKey {
|
if let previousCurrentPaneKey = previousCurrentPaneKey, self.currentPaneKey != previousCurrentPaneKey {
|
||||||
self.currentPaneUpdated?(true)
|
self.currentPaneUpdated?(true)
|
||||||
}
|
}
|
||||||
|
if updateCurrentPaneStatus {
|
||||||
|
self.currentPaneStatusPromise.set(self.currentPane?.node.status ?? .single(nil))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1618,7 +1618,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
self.scrollNode.canCancelAllTouchesInViews = true
|
self.scrollNode.canCancelAllTouchesInViews = true
|
||||||
|
|
||||||
self.headerNode = PeerInfoHeaderNode(context: context, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, isSettings: isSettings)
|
self.headerNode = PeerInfoHeaderNode(context: context, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, isSettings: isSettings)
|
||||||
self.paneContainerNode = PeerInfoPaneContainerNode(context: context, updatedPresentationData: controller.updatedPresentationData, peerId: peerId)
|
self.paneContainerNode = PeerInfoPaneContainerNode(context: context, updatedPresentationData: controller.updatedPresentationData, peerId: peerId, isMediaOnly: self.isMediaOnly)
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
@ -2268,12 +2268,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let pane = strongSelf.paneContainerNode.currentPane?.node {
|
|
||||||
strongSelf.customStatusPromise.set(pane.status)
|
|
||||||
} else {
|
|
||||||
strongSelf.customStatusPromise.set(.single(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
if let (layout, navigationHeight) = strongSelf.validLayout {
|
if let (layout, navigationHeight) = strongSelf.validLayout {
|
||||||
if strongSelf.headerNode.isAvatarExpanded {
|
if strongSelf.headerNode.isAvatarExpanded {
|
||||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
|
let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
|
||||||
@ -2293,6 +2287,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.customStatusPromise.set(self.paneContainerNode.currentPaneStatus)
|
||||||
|
|
||||||
self.paneContainerNode.requestExpandTabs = { [weak self] in
|
self.paneContainerNode.requestExpandTabs = { [weak self] in
|
||||||
guard let strongSelf = self, let (_, navigationHeight) = strongSelf.validLayout else {
|
guard let strongSelf = self, let (_, navigationHeight) = strongSelf.validLayout else {
|
||||||
return false
|
return false
|
||||||
@ -6042,11 +6038,6 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
if !"".isEmpty {
|
if !"".isEmpty {
|
||||||
canZoom = false
|
canZoom = false
|
||||||
}
|
}
|
||||||
/*if isZoomIn {
|
|
||||||
canZoom = pane?.availableZoomLevels().increment != nil
|
|
||||||
} else {
|
|
||||||
canZoom = pane?.availableZoomLevels().decrement != nil
|
|
||||||
}*/
|
|
||||||
return ContextMenuActionItem(text: isZoomIn ? "Zoom In" : "Zoom Out", textColor: canZoom ? .primary : .disabled, icon: { theme in
|
return ContextMenuActionItem(text: isZoomIn ? "Zoom In" : "Zoom Out", textColor: canZoom ? .primary : .disabled, icon: { theme in
|
||||||
return generateTintedImage(image: UIImage(bundleImageName: isZoomIn ? "Chat/Context Menu/ZoomIn" : "Chat/Context Menu/ZoomOut"), color: canZoom ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withMultipliedAlpha(0.4))
|
return generateTintedImage(image: UIImage(bundleImageName: isZoomIn ? "Chat/Context Menu/ZoomIn" : "Chat/Context Menu/ZoomOut"), color: canZoom ? theme.contextMenu.primaryColor : theme.contextMenu.primaryColor.withMultipliedAlpha(0.4))
|
||||||
}, action: canZoom ? { action in
|
}, action: canZoom ? { action in
|
||||||
@ -6202,7 +6193,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
|
|
||||||
var contentHeight: CGFloat = 0.0
|
var contentHeight: CGFloat = 0.0
|
||||||
|
|
||||||
let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: layout.safeInsets.left, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, notificationSettings: self.data?.notificationSettings, statusData: self.customStatusData ?? self.data?.status, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, transition: transition, additive: additive)
|
let headerHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: layout.safeInsets.left, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : self.scrollNode.view.contentOffset.y, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, notificationSettings: self.data?.notificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, transition: transition, additive: additive)
|
||||||
let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: headerHeight))
|
let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: contentHeight), size: CGSize(width: layout.size.width, height: headerHeight))
|
||||||
if additive {
|
if additive {
|
||||||
transition.updateFrameAdditive(node: self.headerNode, frame: headerFrame)
|
transition.updateFrameAdditive(node: self.headerNode, frame: headerFrame)
|
||||||
@ -6451,7 +6442,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
|
|
||||||
if let (layout, navigationHeight) = self.validLayout {
|
if let (layout, navigationHeight) = self.validLayout {
|
||||||
if !additive {
|
if !additive {
|
||||||
let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: layout.safeInsets.left, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, notificationSettings: self.data?.notificationSettings, statusData: self.customStatusData ?? self.data?.status, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, transition: transition, additive: additive)
|
let _ = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: layout.safeInsets.left, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: navigationHeight, isModalOverlay: layout.isModalOverlay, isMediaOnly: self.isMediaOnly, contentOffset: self.isMediaOnly ? 212.0 : offsetY, paneContainerY: self.paneContainerNode.frame.minY, presentationData: self.presentationData, peer: self.data?.peer, cachedData: self.data?.cachedData, notificationSettings: self.data?.notificationSettings, statusData: self.data?.status, panelStatusData: self.customStatusData, isSecretChat: self.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.data?.isContact ?? false, isSettings: self.isSettings, state: self.state, transition: transition, additive: additive)
|
||||||
}
|
}
|
||||||
|
|
||||||
let paneAreaExpansionDistance: CGFloat = 32.0
|
let paneAreaExpansionDistance: CGFloat = 32.0
|
||||||
@ -7323,7 +7314,7 @@ private final class PeerInfoNavigationTransitionNode: ASDisplayNode, CustomNavig
|
|||||||
self.headerNode.navigationTransition = PeerInfoHeaderNavigationTransition(sourceNavigationBar: bottomNavigationBar, sourceTitleView: previousTitleView, sourceTitleFrame: previousTitleFrame, sourceSubtitleFrame: previousStatusFrame, fraction: fraction)
|
self.headerNode.navigationTransition = PeerInfoHeaderNavigationTransition(sourceNavigationBar: bottomNavigationBar, sourceTitleView: previousTitleView, sourceTitleFrame: previousTitleFrame, sourceSubtitleFrame: previousStatusFrame, fraction: fraction)
|
||||||
var topHeight = topNavigationBar.backgroundNode.bounds.height
|
var topHeight = topNavigationBar.backgroundNode.bounds.height
|
||||||
if let (layout, _) = self.screenNode.validLayout {
|
if let (layout, _) = self.screenNode.validLayout {
|
||||||
topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: layout.safeInsets.left, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, notificationSettings: self.screenNode.data?.notificationSettings, statusData: self.screenNode.data?.status, isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, transition: transition, additive: false)
|
topHeight = self.headerNode.update(width: layout.size.width, containerHeight: layout.size.height, containerInset: layout.safeInsets.left, statusBarHeight: layout.statusBarHeight ?? 0.0, navigationHeight: topNavigationBar.bounds.height, isModalOverlay: layout.isModalOverlay, isMediaOnly: false, contentOffset: 0.0, paneContainerY: 0.0, presentationData: self.presentationData, peer: self.screenNode.data?.peer, cachedData: self.screenNode.data?.cachedData, notificationSettings: self.screenNode.data?.notificationSettings, statusData: self.screenNode.data?.status, panelStatusData: nil, isSecretChat: self.screenNode.peerId.namespace == Namespaces.Peer.SecretChat, isContact: self.screenNode.data?.isContact ?? false, isSettings: self.screenNode.isSettings, state: self.screenNode.state, transition: transition, additive: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
let titleScale = (fraction * previousTitleNode.bounds.height + (1.0 - fraction) * self.headerNode.titleNodeRawContainer.bounds.height) / previousTitleNode.bounds.height
|
let titleScale = (fraction * previousTitleNode.bounds.height + (1.0 - fraction) * self.headerNode.titleNodeRawContainer.bounds.height) / previousTitleNode.bounds.height
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user