2024-05-20 18:51:35 +04:00

526 lines
23 KiB
Swift

import Foundation
import UIKit
import Display
import ComponentFlow
import AccountContext
import TelegramCore
import TelegramPresentationData
import AnimationCache
import MultiAnimationRenderer
final class GroupHeaderLayer: UIView {
override static var layerClass: AnyClass {
return PassthroughLayer.self
}
private let actionPressed: () -> Void
private let performItemAction: (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void
private let textLayer: SimpleLayer
private let tintTextLayer: SimpleLayer
private var subtitleLayer: SimpleLayer?
private var tintSubtitleLayer: SimpleLayer?
private var lockIconLayer: SimpleLayer?
private var tintLockIconLayer: SimpleLayer?
private var badgeLayer: SimpleLayer?
private var tintBadgeLayer: SimpleLayer?
private(set) var clearIconLayer: SimpleLayer?
private var tintClearIconLayer: SimpleLayer?
private var separatorLayer: SimpleLayer?
private var tintSeparatorLayer: SimpleLayer?
private var actionButton: GroupHeaderActionButton?
private var groupEmbeddedView: GroupEmbeddedView?
private var theme: PresentationTheme?
private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)?
private var currentSubtitleLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)?
let tintContentLayer: SimpleLayer
init(actionPressed: @escaping () -> Void, performItemAction: @escaping (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void) {
self.actionPressed = actionPressed
self.performItemAction = performItemAction
self.textLayer = SimpleLayer()
self.tintTextLayer = SimpleLayer()
self.tintContentLayer = SimpleLayer()
super.init(frame: CGRect())
self.layer.addSublayer(self.textLayer)
self.tintContentLayer.addSublayer(self.tintTextLayer)
(self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContentLayer
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(
context: AccountContext,
theme: PresentationTheme,
forceNeedsVibrancy: Bool,
layoutType: EmojiPagerContentComponent.ItemLayoutType,
hasTopSeparator: Bool,
actionButtonTitle: String?,
actionButtonIsCompact: Bool,
title: String,
subtitle: String?,
badge: String?,
isPremiumLocked: Bool,
hasClear: Bool,
embeddedItems: [EmojiPagerContentComponent.Item]?,
isStickers: Bool,
constrainedSize: CGSize,
insets: UIEdgeInsets,
cache: AnimationCache,
renderer: MultiAnimationRenderer,
attemptSynchronousLoad: Bool
) -> (size: CGSize, centralContentWidth: CGFloat) {
var themeUpdated = false
if self.theme !== theme {
self.theme = theme
themeUpdated = true
}
let needsVibrancy = !theme.overallDarkAppearance || forceNeedsVibrancy
let textOffsetY: CGFloat
if hasTopSeparator {
textOffsetY = 9.0
} else {
textOffsetY = 0.0
}
let subtitleColor: UIColor
if theme.overallDarkAppearance && forceNeedsVibrancy {
subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.withMultipliedAlpha(0.2)
} else {
subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor
}
let color: UIColor
let needsTintText: Bool
if subtitle != nil {
color = theme.chat.inputPanel.primaryTextColor
needsTintText = false
} else {
color = subtitleColor
needsTintText = true
}
let titleHorizontalOffset: CGFloat
if isPremiumLocked {
titleHorizontalOffset = 10.0 + 2.0
} else {
titleHorizontalOffset = 0.0
}
var actionButtonSize: CGSize?
if let actionButtonTitle = actionButtonTitle {
let actionButton: GroupHeaderActionButton
if let current = self.actionButton {
actionButton = current
} else {
actionButton = GroupHeaderActionButton(pressed: self.actionPressed)
self.actionButton = actionButton
self.addSubview(actionButton)
self.tintContentLayer.addSublayer(actionButton.tintContainerLayer)
}
actionButtonSize = actionButton.update(theme: theme, title: actionButtonTitle, compact: actionButtonIsCompact)
} else {
if let actionButton = self.actionButton {
self.actionButton = nil
actionButton.removeFromSuperview()
}
}
var clearSize: CGSize = .zero
var clearWidth: CGFloat = 0.0
if hasClear {
var updateImage = themeUpdated
let clearIconLayer: SimpleLayer
if let current = self.clearIconLayer {
clearIconLayer = current
} else {
updateImage = true
clearIconLayer = SimpleLayer()
self.clearIconLayer = clearIconLayer
self.layer.addSublayer(clearIconLayer)
}
let tintClearIconLayer: SimpleLayer
if let current = self.tintClearIconLayer {
tintClearIconLayer = current
} else {
updateImage = true
tintClearIconLayer = SimpleLayer()
self.tintClearIconLayer = tintClearIconLayer
self.tintContentLayer.addSublayer(tintClearIconLayer)
}
tintClearIconLayer.isHidden = !needsVibrancy
clearSize = clearIconLayer.bounds.size
if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: subtitleColor) {
clearSize = image.size
clearIconLayer.contents = image.cgImage
}
if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: .white) {
tintClearIconLayer.contents = image.cgImage
}
tintClearIconLayer.frame = clearIconLayer.frame
clearWidth = 4.0 + clearSize.width
} else {
if let clearIconLayer = self.clearIconLayer {
self.clearIconLayer = nil
clearIconLayer.removeFromSuperlayer()
}
if let tintClearIconLayer = self.tintClearIconLayer {
self.tintClearIconLayer = nil
tintClearIconLayer.removeFromSuperlayer()
}
}
var textConstrainedWidth = constrainedSize.width - titleHorizontalOffset - 10.0
if let actionButtonSize = actionButtonSize {
if actionButtonIsCompact {
textConstrainedWidth -= actionButtonSize.width * 2.0 + 10.0
} else {
textConstrainedWidth -= actionButtonSize.width + 10.0
}
}
if clearWidth > 0.0 {
textConstrainedWidth -= clearWidth + 8.0
}
let textSize: CGSize
if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == color, currentTextLayout.constrainedWidth == textConstrainedWidth {
textSize = currentTextLayout.size
} else {
let font: UIFont
let stringValue: String
if subtitle == nil {
font = Font.medium(13.0)
stringValue = title.uppercased()
} else {
font = Font.semibold(16.0)
stringValue = title
}
let string = NSAttributedString(string: stringValue, font: font, textColor: color)
let whiteString = NSAttributedString(string: stringValue, font: font, textColor: .white)
let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 18.0), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil)
textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height))
self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
//string.draw(in: stringBounds)
string.draw(with: stringBounds, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil)
UIGraphicsPopContext()
})?.cgImage
self.tintTextLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
//whiteString.draw(in: stringBounds)
whiteString.draw(with: stringBounds, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil)
UIGraphicsPopContext()
})?.cgImage
self.tintTextLayer.isHidden = !needsVibrancy
self.currentTextLayout = (title, color, textConstrainedWidth, textSize)
}
var badgeSize: CGSize = .zero
if let badge {
func generateBadgeImage(color: UIColor) -> UIImage? {
let string = NSAttributedString(string: badge, font: Font.semibold(11.0), textColor: .white)
let stringBounds = string.boundingRect(with: CGSize(width: 120, height: 18.0), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil)
let badgeSize = CGSize(width: stringBounds.width + 8.0, height: 16.0)
return generateImage(badgeSize, opaque: false, scale: 0.0, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(color.cgColor)
context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: badgeSize), cornerRadius: badgeSize.height / 2.0).cgPath)
context.fillPath()
context.setBlendMode(.clear)
UIGraphicsPushContext(context)
string.draw(with: CGRect(origin: CGPoint(x: floorToScreenPixels((badgeSize.width - stringBounds.size.width) / 2.0), y: floorToScreenPixels((badgeSize.height - stringBounds.size.height) / 2.0)), size: stringBounds.size), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil)
UIGraphicsPopContext()
})
}
let badgeLayer: SimpleLayer
if let current = self.badgeLayer {
badgeLayer = current
} else {
badgeLayer = SimpleLayer()
self.badgeLayer = badgeLayer
self.layer.addSublayer(badgeLayer)
if let image = generateBadgeImage(color: color.withMultipliedAlpha(0.66)) {
badgeLayer.contents = image.cgImage
badgeLayer.bounds = CGRect(origin: .zero, size: image.size)
}
}
badgeSize = badgeLayer.bounds.size
let tintBadgeLayer: SimpleLayer
if let current = self.tintBadgeLayer {
tintBadgeLayer = current
} else {
tintBadgeLayer = SimpleLayer()
self.tintBadgeLayer = tintBadgeLayer
self.tintContentLayer.addSublayer(tintBadgeLayer)
if let image = generateBadgeImage(color: .white) {
tintBadgeLayer.contents = image.cgImage
}
}
} else {
if let badgeLayer = self.badgeLayer {
self.badgeLayer = nil
badgeLayer.removeFromSuperlayer()
}
if let tintBadgeLayer = self.tintBadgeLayer {
self.tintBadgeLayer = nil
tintBadgeLayer.removeFromSuperlayer()
}
}
let textFrame: CGRect
if subtitle == nil {
textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset + floor((constrainedSize.width - titleHorizontalOffset - (textSize.width + badgeSize.width)) / 2.0), y: textOffsetY), size: textSize)
} else {
textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset, y: textOffsetY), size: textSize)
}
self.textLayer.frame = textFrame
self.tintTextLayer.frame = textFrame
self.tintTextLayer.isHidden = !needsTintText
if let badgeLayer = self.badgeLayer, let tintBadgeLayer = self.tintBadgeLayer {
badgeLayer.frame = CGRect(origin: CGPoint(x: textFrame.maxX + 4.0, y: 0.0), size: badgeLayer.frame.size)
tintBadgeLayer.frame = badgeLayer.frame
}
if isPremiumLocked {
let lockIconLayer: SimpleLayer
if let current = self.lockIconLayer {
lockIconLayer = current
} else {
lockIconLayer = SimpleLayer()
self.lockIconLayer = lockIconLayer
self.layer.addSublayer(lockIconLayer)
}
if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme, color: color) {
let imageSize = image.size
lockIconLayer.contents = image.cgImage
lockIconLayer.frame = CGRect(origin: CGPoint(x: textFrame.minX - imageSize.width - 3.0, y: 2.0 + UIScreenPixel), size: imageSize)
} else {
lockIconLayer.contents = nil
}
let tintLockIconLayer: SimpleLayer
if let current = self.tintLockIconLayer {
tintLockIconLayer = current
} else {
tintLockIconLayer = SimpleLayer()
self.tintLockIconLayer = tintLockIconLayer
self.tintContentLayer.addSublayer(tintLockIconLayer)
}
if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme, color: .white) {
tintLockIconLayer.contents = image.cgImage
tintLockIconLayer.frame = lockIconLayer.frame
tintLockIconLayer.isHidden = !needsVibrancy
} else {
tintLockIconLayer.contents = nil
}
} else {
if let lockIconLayer = self.lockIconLayer {
self.lockIconLayer = nil
lockIconLayer.removeFromSuperlayer()
}
if let tintLockIconLayer = self.tintLockIconLayer {
self.tintLockIconLayer = nil
tintLockIconLayer.removeFromSuperlayer()
}
}
let subtitleSize: CGSize
if let subtitle = subtitle {
var updateSubtitleContents: UIImage?
var updateTintSubtitleContents: UIImage?
if let currentSubtitleLayout = self.currentSubtitleLayout, currentSubtitleLayout.string == subtitle, currentSubtitleLayout.color == subtitleColor, currentSubtitleLayout.constrainedWidth == textConstrainedWidth {
subtitleSize = currentSubtitleLayout.size
} else {
let string = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: subtitleColor)
let whiteString = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: .white)
let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
subtitleSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height))
updateSubtitleContents = generateImage(subtitleSize, opaque: false, scale: 0.0, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
string.draw(in: stringBounds)
UIGraphicsPopContext()
})
updateTintSubtitleContents = generateImage(subtitleSize, opaque: false, scale: 0.0, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
UIGraphicsPushContext(context)
whiteString.draw(in: stringBounds)
UIGraphicsPopContext()
})
self.currentSubtitleLayout = (subtitle, subtitleColor, textConstrainedWidth, subtitleSize)
}
let subtitleLayer: SimpleLayer
if let current = self.subtitleLayer {
subtitleLayer = current
} else {
subtitleLayer = SimpleLayer()
self.subtitleLayer = subtitleLayer
self.layer.addSublayer(subtitleLayer)
}
if let updateSubtitleContents = updateSubtitleContents {
subtitleLayer.contents = updateSubtitleContents.cgImage
}
let tintSubtitleLayer: SimpleLayer
if let current = self.tintSubtitleLayer {
tintSubtitleLayer = current
} else {
tintSubtitleLayer = SimpleLayer()
self.tintSubtitleLayer = tintSubtitleLayer
self.tintContentLayer.addSublayer(tintSubtitleLayer)
}
tintSubtitleLayer.isHidden = !needsVibrancy
if let updateTintSubtitleContents = updateTintSubtitleContents {
tintSubtitleLayer.contents = updateTintSubtitleContents.cgImage
}
let subtitleFrame = CGRect(origin: CGPoint(x: 0.0, y: textFrame.maxY + 1.0), size: subtitleSize)
subtitleLayer.frame = subtitleFrame
tintSubtitleLayer.frame = subtitleFrame
} else {
subtitleSize = CGSize()
if let subtitleLayer = self.subtitleLayer {
self.subtitleLayer = nil
subtitleLayer.removeFromSuperlayer()
}
if let tintSubtitleLayer = self.tintSubtitleLayer {
self.tintSubtitleLayer = nil
tintSubtitleLayer.removeFromSuperlayer()
}
}
self.clearIconLayer?.frame = CGRect(origin: CGPoint(x: constrainedSize.width - clearSize.width, y: floorToScreenPixels((textSize.height - clearSize.height) / 2.0)), size: clearSize)
var size: CGSize
size = CGSize(width: constrainedSize.width, height: constrainedSize.height)
if let embeddedItems = embeddedItems {
let groupEmbeddedView: GroupEmbeddedView
if let current = self.groupEmbeddedView {
groupEmbeddedView = current
} else {
groupEmbeddedView = GroupEmbeddedView(performItemAction: self.performItemAction)
self.groupEmbeddedView = groupEmbeddedView
self.addSubview(groupEmbeddedView)
}
let groupEmbeddedViewSize = CGSize(width: constrainedSize.width + insets.left + insets.right, height: 36.0)
groupEmbeddedView.frame = CGRect(origin: CGPoint(x: -insets.left, y: size.height - groupEmbeddedViewSize.height), size: groupEmbeddedViewSize)
groupEmbeddedView.update(
context: context,
theme: theme,
insets: insets,
size: groupEmbeddedViewSize,
items: embeddedItems,
isStickers: isStickers,
cache: cache,
renderer: renderer,
attemptSynchronousLoad: attemptSynchronousLoad
)
} else {
if let groupEmbeddedView = self.groupEmbeddedView {
self.groupEmbeddedView = nil
groupEmbeddedView.removeFromSuperview()
}
}
if let actionButtonSize = actionButtonSize, let actionButton = self.actionButton {
let actionButtonFrame = CGRect(origin: CGPoint(x: size.width - actionButtonSize.width, y: textFrame.minY + (actionButtonIsCompact ? 0.0 : 3.0)), size: actionButtonSize)
actionButton.bounds = CGRect(origin: CGPoint(), size: actionButtonFrame.size)
actionButton.center = actionButtonFrame.center
}
if hasTopSeparator {
let separatorLayer: SimpleLayer
if let current = self.separatorLayer {
separatorLayer = current
} else {
separatorLayer = SimpleLayer()
self.separatorLayer = separatorLayer
self.layer.addSublayer(separatorLayer)
}
separatorLayer.backgroundColor = subtitleColor.cgColor
separatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel))
let tintSeparatorLayer: SimpleLayer
if let current = self.tintSeparatorLayer {
tintSeparatorLayer = current
} else {
tintSeparatorLayer = SimpleLayer()
self.tintSeparatorLayer = tintSeparatorLayer
self.tintContentLayer.addSublayer(tintSeparatorLayer)
}
tintSeparatorLayer.backgroundColor = UIColor.white.cgColor
tintSeparatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel))
tintSeparatorLayer.isHidden = !needsVibrancy
} else {
if let separatorLayer = self.separatorLayer {
self.separatorLayer = separatorLayer
separatorLayer.removeFromSuperlayer()
}
if let tintSeparatorLayer = self.tintSeparatorLayer {
self.tintSeparatorLayer = tintSeparatorLayer
tintSeparatorLayer.removeFromSuperlayer()
}
}
return (size, titleHorizontalOffset + textSize.width + clearWidth)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return super.hitTest(point, with: event)
}
func tapGesture(point: CGPoint) -> Bool {
if let groupEmbeddedView = self.groupEmbeddedView {
return groupEmbeddedView.tapGesture(point: self.convert(point, to: groupEmbeddedView))
} else {
return false
}
}
}