Shared media improvements

This commit is contained in:
Ali 2021-10-22 01:41:39 +04:00
parent 205211d5ba
commit b5d1b377f2
9 changed files with 442 additions and 91 deletions

View File

@ -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) {

View File

@ -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
}
}
}
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

@ -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 {

View File

@ -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
@ -1720,6 +1721,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
self.subtitleNodeRawContainer = ASDisplayNode() self.subtitleNodeRawContainer = 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()
@ -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)
@ -2089,6 +2105,12 @@ final class PeerInfoHeaderNode: ASDisplayNode {
TitleNodeStateExpanded: MultiScaleTextState(attributedText: subtitleString, constrainedSize: CGSize(width: titleConstrainedSize.width - 82.0, height: titleConstrainedSize.height)) TitleNodeStateExpanded: MultiScaleTextState(attributedText: subtitleString, constrainedSize: CGSize(width: titleConstrainedSize.width - 82.0, height: titleConstrainedSize.height))
], 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)),
@ -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 {
@ -2150,17 +2173,53 @@ final class PeerInfoHeaderNode: ASDisplayNode {
let avatarMinScale: CGFloat = 0.7 let avatarMinScale: CGFloat = 0.7
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,
@ -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)

View File

@ -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?
@ -477,6 +478,11 @@ final class PeerInfoPaneContainerNode: ASDisplayNode, UIGestureRecognizerDelegat
return nil return nil
} }
} }
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))
}
} }
} }

View File

@ -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)
@ -2292,6 +2286,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 {
@ -6042,12 +6038,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
if !"".isEmpty { if !"".isEmpty {
canZoom = false canZoom = false
} }
/*if isZoomIn { return ContextMenuActionItem(text: isZoomIn ? "Zoom In" : "Zoom Out", textColor: canZoom ? .primary : .disabled, icon: { theme in
canZoom = pane?.availableZoomLevels().increment != nil
} else {
canZoom = pane?.availableZoomLevels().decrement != nil
}*/
return ContextMenuActionItem(text: isZoomIn ? "Zoom In" : "ZoomOut", 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
guard let pane = pane, let zoomLevel = isZoomIn ? pane.availableZoomLevels().increment : pane.availableZoomLevels().decrement else { guard let pane = pane, let zoomLevel = isZoomIn ? pane.availableZoomLevels().increment : pane.availableZoomLevels().decrement else {
@ -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