Glass updates

This commit is contained in:
Isaac 2026-01-11 21:01:45 +04:00
parent 1425fd8973
commit 0f5a019f04
23 changed files with 1000 additions and 592 deletions

View file

@ -22,9 +22,7 @@ private let avatarFont = avatarPlaceholderFont(size: 16.0)
public final class ReactionListContextMenuContent: ContextControllerItemsContent {
private final class BackButtonNode: HighlightTrackingButtonNode {
let highlightBackgroundNode: ASDisplayNode
let titleLabelNode: ImmediateTextNode
let separatorNode: ASDisplayNode
let iconNode: ASImageNode
var action: (() -> Void)?
@ -32,10 +30,6 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
private var theme: PresentationTheme?
init() {
self.highlightBackgroundNode = ASDisplayNode()
self.highlightBackgroundNode.isAccessibilityElement = false
self.highlightBackgroundNode.alpha = 0.0
self.titleLabelNode = ImmediateTextNode()
self.titleLabelNode.isAccessibilityElement = false
self.titleLabelNode.maximumNumberOfLines = 1
@ -44,31 +38,13 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
self.iconNode = ASImageNode()
self.iconNode.isAccessibilityElement = false
self.separatorNode = ASDisplayNode()
self.separatorNode.isAccessibilityElement = false
super.init()
self.addSubnode(self.separatorNode)
self.addSubnode(self.highlightBackgroundNode)
self.addSubnode(self.titleLabelNode)
self.addSubnode(self.iconNode)
self.isAccessibilityElement = true
self.highligthedChanged = { [weak self] highlighted in
guard let strongSelf = self else {
return
}
if highlighted {
strongSelf.highlightBackgroundNode.alpha = 1.0
} else {
let previousAlpha = strongSelf.highlightBackgroundNode.alpha
strongSelf.highlightBackgroundNode.alpha = 0.0
strongSelf.highlightBackgroundNode.layer.animateAlpha(from: previousAlpha, to: 0.0, duration: 0.2)
}
}
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
}
@ -78,8 +54,8 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
func update(size: CGSize, presentationData: PresentationData, isLast: Bool) {
let standardIconWidth: CGFloat = 32.0
let sideInset: CGFloat = 16.0
let iconSideInset: CGFloat = 12.0
let sideInset: CGFloat = 18.0
let iconSideInset: CGFloat = 23.0
if self.theme !== presentationData.theme {
self.theme = presentationData.theme
@ -88,24 +64,16 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
self.accessibilityLabel = presentationData.strings.Common_Back
}
self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
self.separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
self.highlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
let titleFontSize = presentationData.listsFontSize.baseDisplaySize * 17.0 / 17.0
self.titleLabelNode.attributedText = NSAttributedString(string: presentationData.strings.Common_Back, font: Font.regular(titleFontSize), textColor: presentationData.theme.contextMenu.primaryColor)
let titleSize = self.titleLabelNode.updateLayout(CGSize(width: size.width - sideInset - standardIconWidth, height: 100.0))
self.titleLabelNode.frame = CGRect(origin: CGPoint(x: sideInset + 36.0, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
self.titleLabelNode.frame = CGRect(origin: CGPoint(x: sideInset + 42.0, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
if let iconImage = self.iconNode.image {
let iconFrame = CGRect(origin: CGPoint(x: iconSideInset, y: floor((size.height - iconImage.size.height) / 2.0)), size: iconImage.size)
self.iconNode.frame = iconFrame
}
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel))
self.separatorNode.isHidden = isLast
}
}
@ -405,13 +373,11 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
let availableReactions: AvailableReactions?
let animationCache: AnimationCache
let animationRenderer: MultiAnimationRenderer
let highlightBackgroundNode: ASDisplayNode
let avatarNode: AvatarNode
let titleLabelNode: ImmediateTextNode
let textLabelNode: ImmediateTextNode
let readIconView: UIImageView
var credibilityIconView: ComponentView<Empty>?
let separatorNode: ASDisplayNode
private var reactionLayer: InlineStickerItemLayer?
private var iconFrame: CGRect?
@ -434,10 +400,6 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
self.avatarNode = AvatarNode(font: avatarFont)
self.avatarNode.isAccessibilityElement = false
self.highlightBackgroundNode = ASDisplayNode()
self.highlightBackgroundNode.isAccessibilityElement = false
self.highlightBackgroundNode.alpha = 0.0
self.titleLabelNode = ImmediateTextNode()
self.titleLabelNode.isAccessibilityElement = false
self.titleLabelNode.maximumNumberOfLines = 1
@ -450,33 +412,15 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
self.readIconView = UIImageView(image: readIconImage)
self.separatorNode = ASDisplayNode()
self.separatorNode.isAccessibilityElement = false
super.init()
self.isAccessibilityElement = true
self.addSubnode(self.separatorNode)
self.addSubnode(self.highlightBackgroundNode)
self.addSubnode(self.avatarNode)
self.addSubnode(self.titleLabelNode)
self.addSubnode(self.textLabelNode)
self.view.addSubview(self.readIconView)
self.highligthedChanged = { [weak self] highlighted in
guard let strongSelf = self else {
return
}
if highlighted {
strongSelf.highlightBackgroundNode.alpha = 1.0
} else {
let previousAlpha = strongSelf.highlightBackgroundNode.alpha
strongSelf.highlightBackgroundNode.alpha = 0.0
strongSelf.highlightBackgroundNode.layer.animateAlpha(from: previousAlpha, to: 0.0, duration: 0.2)
}
}
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
}
@ -553,9 +497,9 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
}
func update(size: CGSize, presentationData: PresentationData, item: EngineMessageReactionListContext.Item, isLast: Bool, syncronousLoad: Bool) {
let avatarInset: CGFloat = 12.0
let avatarSpacing: CGFloat = 8.0
let avatarSize: CGFloat = 28.0
let avatarInset: CGFloat = 20.0
let avatarSpacing: CGFloat = 10.0
let avatarSize: CGFloat = 30.0
let sideInset: CGFloat = 16.0
let reaction: MessageReaction.Reaction? = item.reaction
@ -671,11 +615,6 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
additionalTitleInset += 3.0 + credibilityIconSize.width
}
self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
self.separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
self.highlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
self.avatarNode.frame = CGRect(origin: CGPoint(x: avatarInset, y: floor((size.height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))
self.avatarNode.setPeer(context: self.context, theme: presentationData.theme, peer: item.peer, synchronousLoad: true)
@ -765,9 +704,6 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
}
reactionLayer.frame = iconFrame
}
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: CGSize(width: size.width, height: UIScreenPixel))
self.separatorNode.isHidden = isLast
}
}
@ -961,6 +897,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
let heightFraction: CGFloat = presentationData.listsFontSize.baseDisplaySize / 17.0
let topInset: CGFloat = 5.0
let itemHeight: CGFloat = (self.displayReadTimestamps ? 56.0 : 44.0) * heightFraction
let visibleBounds = self.scrollNode.bounds.insetBy(dx: 0.0, dy: -180.0)
@ -972,7 +909,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
if minVisibleIndex <= maxVisibleIndex {
for index in minVisibleIndex ... maxVisibleIndex {
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: CGFloat(index) * itemHeight), size: CGSize(width: size.width, height: itemHeight))
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset + CGFloat(index) * itemHeight), size: CGSize(width: size.width, height: itemHeight))
if let item = self.state.item(at: index) {
validIds.insert(index)
@ -1059,12 +996,13 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
func update(presentationData: PresentationData, constrainedSize: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> (height: CGFloat, apparentHeight: CGFloat) {
let heightFraction: CGFloat = presentationData.listsFontSize.baseDisplaySize / 17.0
let itemHeight: CGFloat = (self.displayReadTimestamps ? 56.0 : 44.0) * heightFraction
let topInset: CGFloat = 5.0
if self.presentationData?.theme !== presentationData.theme {
let sideInset: CGFloat = 40.0
let avatarInset: CGFloat = 12.0
let avatarSpacing: CGFloat = 8.0
let avatarSize: CGFloat = 28.0
let avatarInset: CGFloat = 20.0
let avatarSpacing: CGFloat = 10.0
let avatarSize: CGFloat = 30.0
let lineHeight: CGFloat = 8.0
let shimmeringForegroundColor: UIColor
@ -1096,7 +1034,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
}
self.presentationData = presentationData
let size = CGSize(width: constrainedSize.width, height: CGFloat(self.state.totalCount) * itemHeight)
let size = CGSize(width: constrainedSize.width, height: topInset + CGFloat(self.state.totalCount) * itemHeight + topInset)
let containerSize = CGSize(width: size.width, height: min(constrainedSize.height, size.height))
self.currentSize = containerSize
@ -1311,6 +1249,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
let constrainedSize = CGSize(width: min(self.displayReadTimestamps ? 280.0 : 260.0, constrainedWidth), height: maxHeight)
var topContentHeight: CGFloat = 0.0
topContentHeight += 6.0
if let backButtonNode = self.backButtonNode {
let backButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: topContentHeight), size: CGSize(width: constrainedSize.width, height: 44.0))
backButtonNode.update(size: backButtonFrame.size, presentationData: self.presentationData, isLast: self.tabListNode == nil)
@ -1325,10 +1264,10 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
topContentHeight += tabListFrame.height
}
if let separatorNode = self.separatorNode {
let separatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topContentHeight), size: CGSize(width: constrainedSize.width, height: 7.0))
separatorNode.backgroundColor = self.presentationData.theme.contextMenu.sectionSeparatorColor
let separatorFrame = CGRect(origin: CGPoint(x: 18.0, y: topContentHeight + 4.0), size: CGSize(width: max(0.0, constrainedSize.width - 18.0 * 2.0), height: 1.0))
separatorNode.backgroundColor = self.presentationData.theme.contextMenu.itemSeparatorColor
transition.updateFrame(node: separatorNode, frame: separatorFrame)
topContentHeight += separatorFrame.height
topContentHeight += 5.0
}
var tabLayouts: [Int: (height: CGFloat, apparentHeight: CGFloat)] = [:]

View file

@ -33,6 +33,7 @@ swift_library(
"//submodules/Components/MultilineTextComponent",
"//submodules/UIKitRuntimeUtils",
"//submodules/TelegramUI/Components/EmojiStatusComponent",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
],
visibility = [
"//visibility:public",

View file

@ -94,32 +94,12 @@ private final class InnerActionsContainerNode: ASDisplayNode {
itemNodes.append(.action(ContextActionNode(presentationData: presentationData, action: action, getController: getController, actionSelected: actionSelected, requestLayout: requestLayout, requestUpdateAction: { id, action in
requestUpdateAction?(id, action)
})))
if i != items.count - 1 {
switch items[i + 1] {
case .action, .custom:
let separatorNode = ASDisplayNode()
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
itemNodes.append(.itemSeparator(separatorNode))
default:
break
}
}
case let .custom(item, _):
let itemNode = item.node(presentationData: presentationData, getController: getController, actionSelected: actionSelected)
itemNodes.append(.custom(itemNode))
if i != items.count - 1 && itemNode.needsSeparator {
switch items[i + 1] {
case .action, .custom:
let separatorNode = ASDisplayNode()
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
itemNodes.append(.itemSeparator(separatorNode))
default:
break
}
}
case .separator:
let separatorNode = ASDisplayNode()
separatorNode.backgroundColor = presentationData.theme.contextMenu.sectionSeparatorColor
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
itemNodes.append(.separator(separatorNode))
}
}
@ -291,7 +271,7 @@ private final class InnerActionsContainerNode: ASDisplayNode {
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: maxWidth, height: UIScreenPixel)))
verticalOffset += UIScreenPixel
case let .separator(separatorNode):
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: maxWidth, height: separatorHeight)))
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset + floorToScreenPixels((separatorHeight - UIScreenPixel) * 0.5)), size: CGSize(width: maxWidth, height: UIScreenPixel)))
verticalOffset += separatorHeight
}
}
@ -815,8 +795,8 @@ final class ContextActionsContainerNode: ASDisplayNode {
super.init()
self.addSubnode(self.shadowNode)
self.additionalShadowNode.flatMap(self.addSubnode)
//self.addSubnode(self.shadowNode)
//self.additionalShadowNode.flatMap(self.addSubnode)
self.additionalActionsNode.flatMap(self.scrollNode.addSubnode)
self.scrollNode.addSubnode(self.actionsNode)
self.addSubnode(self.scrollNode)

View file

@ -14,6 +14,8 @@ import AnimationCache
import MultiAnimationRenderer
import AnimationUI
import ComponentFlow
import ComponentDisplayAdapters
import GlassBackgroundComponent
import LottieComponent
import TextNodeWithEntities
@ -102,7 +104,6 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
private let requestUpdateAction: (AnyHashable, ContextMenuActionItem) -> Void
private var item: ContextMenuActionItem
private let highlightBackgroundNode: ASDisplayNode
private let titleLabelNode: ImmediateTextNodeWithEntities
private let subtitleNode: ImmediateTextNode
private let iconNode: ASImageNode
@ -130,11 +131,6 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
self.requestUpdateAction = requestUpdateAction
self.item = item
self.highlightBackgroundNode = ASDisplayNode()
self.highlightBackgroundNode.isAccessibilityElement = false
self.highlightBackgroundNode.isUserInteractionEnabled = false
self.highlightBackgroundNode.alpha = 0.0
self.titleLabelNode = ImmediateTextNodeWithEntities()
self.titleLabelNode.isAccessibilityElement = false
self.titleLabelNode.displaysAsynchronously = false
@ -158,7 +154,6 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
self.accessibilityLabel = item.text
self.accessibilityTraits = [.button]
self.addSubnode(self.highlightBackgroundNode)
self.addSubnode(self.titleLabelNode)
self.addSubnode(self.subtitleNode)
self.addSubnode(self.iconNode)
@ -171,10 +166,8 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
return
}
if highlighted {
strongSelf.highlightBackgroundNode.alpha = 1.0
strongSelf.startTimer()
} else {
strongSelf.highlightBackgroundNode.alpha = 0.0
strongSelf.invalidateTimer()
}
}
@ -256,7 +249,6 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
}
public func updateIsHighlighted(isHighlighted: Bool) {
self.highlightBackgroundNode.alpha = isHighlighted ? 1.0 : 0.0
}
public func performAction() {
@ -279,14 +271,12 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
}
public func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void) {
let sideInset: CGFloat = 16.0
let sideInset: CGFloat = 18.0
let verticalInset: CGFloat = 11.0
let titleSubtitleSpacing: CGFloat = 1.0
let iconSideInset: CGFloat = 12.0
let iconSideInset: CGFloat = 18.0 + 6.0
let standardIconWidth: CGFloat = 32.0
let iconSpacing: CGFloat = 8.0
self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
var forcedHeight: CGFloat?
var titleVerticalOffset: CGFloat?
@ -610,30 +600,24 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
if let titleVerticalOffset {
titleFrame = titleFrame.offsetBy(dx: 0.0, dy: titleVerticalOffset)
}
var subtitleFrame = CGRect(origin: CGPoint(x: sideInset, y: titleFrame.maxY + titleSubtitleSpacing), size: subtitleSize)
var subtitleFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleSubtitleSpacing), size: subtitleSize)
if self.item.additionalLeftIcon != nil {
titleFrame = titleFrame.offsetBy(dx: 26.0, dy: 0.0)
subtitleFrame = subtitleFrame.offsetBy(dx: 26.0, dy: 0.0)
} else if self.item.iconPosition == .left {
titleFrame = titleFrame.offsetBy(dx: 36.0, dy: 0.0)
subtitleFrame = subtitleFrame.offsetBy(dx: 36.0, dy: 0.0)
} else if iconSize != nil {
titleFrame.origin.x = iconSideInset + 36.0
subtitleFrame.origin.x = titleFrame.minX
}
transition.updateFrame(node: self.highlightBackgroundNode, frame: CGRect(origin: CGPoint(), size: size), beginWithCurrentState: true)
transition.updateFrameAdditive(node: self.titleLabelNode, frame: titleFrame)
transition.updateFrameAdditive(node: self.subtitleNode, frame: subtitleFrame)
if let badgeIconNode = self.badgeIconNode {
if let iconSize = badgeIconNode.image?.size {
transition.updateFrame(node: badgeIconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 8.0, y: titleFrame.minY + floor((titleFrame.height - iconSize.height) * 0.5)), size: iconSize))
}
if let badgeIconNode = self.badgeIconNode, let iconSize = badgeIconNode.image?.size {
transition.updateFrame(node: badgeIconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 8.0, y: titleFrame.minY + floor((titleFrame.height - iconSize.height) * 0.5)), size: iconSize))
}
if let iconSize = iconSize {
let iconWidth = max(standardIconWidth, iconSize.width)
if let iconSize {
let iconFrame = CGRect(
origin: CGPoint(
x: self.item.iconPosition == .left ? iconSideInset : size.width - iconSideInset - iconWidth + floor((iconWidth - iconSize.width) / 2.0),
x: iconSideInset,
y: floor((size.height - iconSize.height) / 2.0)
),
size: iconSize
@ -660,16 +644,13 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
}
if let additionalIconSize {
var iconFrame = CGRect(
let iconFrame = CGRect(
origin: CGPoint(
x: 10.0,
y: floor((size.height - additionalIconSize.height) / 2.0)
),
size: additionalIconSize
)
if self.item.iconPosition == .left {
iconFrame.origin.x = size.width - additionalIconSize.width - 10.0
}
transition.updateFrame(node: self.additionalIconNode, frame: iconFrame, beginWithCurrentState: true)
}
})
@ -677,6 +658,8 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
}
private final class ContextControllerActionsListSeparatorItemNode: ASDisplayNode, ContextControllerActionsListItemNode {
private let separatorView: UIImageView
func canBeHighlighted() -> Bool {
return false
}
@ -688,12 +671,23 @@ private final class ContextControllerActionsListSeparatorItemNode: ASDisplayNode
}
override init() {
self.separatorView = UIImageView()
self.separatorView.image = generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.white.cgColor)
context.fill(CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - 1.0) * 0.5)), size: CGSize(width: size.width, height: 1.0)))
})?.withRenderingMode(.alwaysTemplate)
super.init()
self.view.addSubview(self.separatorView)
}
func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void) {
return (minSize: CGSize(width: 0.0, height: 7.0), apply: { _, _ in
self.backgroundColor = presentationData.theme.contextMenu.sectionSeparatorColor
return (minSize: CGSize(width: 0.0, height: 10.0), apply: { size, transition in
let sideInset: CGFloat = 18.0
self.separatorView.tintColor = presentationData.theme.contextMenu.itemSeparatorColor
transition.updateFrame(view: self.separatorView, frame: CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((size.height - 1.0) * 0.5)), size: CGSize(width: max(0.0, size.width - sideInset * 2.0), height: 1.0)))
})
}
}
@ -777,13 +771,27 @@ private final class ContextControllerActionsListCustomItemNode: ASDisplayNode, C
public final class ContextControllerActionsListStackItem: ContextControllerActionsStackItem {
final class Node: ASDisplayNode, ContextControllerActionsStackItemNode {
private final class Params {
let presentationData: PresentationData
let constrainedSize: CGSize
let standardMinWidth: CGFloat
let standardMaxWidth: CGFloat
let additionalBottomInset: CGFloat
init(presentationData: PresentationData, constrainedSize: CGSize, standardMinWidth: CGFloat, standardMaxWidth: CGFloat, additionalBottomInset: CGFloat) {
self.presentationData = presentationData
self.constrainedSize = constrainedSize
self.standardMinWidth = standardMinWidth
self.standardMaxWidth = standardMaxWidth
self.additionalBottomInset = additionalBottomInset
}
}
private final class Item {
let node: ContextControllerActionsListItemNode
let separatorNode: ASDisplayNode?
init(node: ContextControllerActionsListItemNode, separatorNode: ASDisplayNode?) {
init(node: ContextControllerActionsListItemNode) {
self.node = node
self.separatorNode = separatorNode
}
}
@ -795,8 +803,11 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
private var itemNodes: [Item]
private var hapticFeedback: HapticFeedback?
private let highlightedItemBackgroundView: UIView
private var highlightedItemNode: Item?
private var params: Params?
private var invalidatedItemNodes: Bool = false
var wantsFullWidth: Bool {
@ -829,13 +840,11 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
requestUpdateAction?(id, action)
},
item: actionItem
),
separatorNode: ASDisplayNode()
)
)
case .separator:
return Item(
node: ContextControllerActionsListSeparatorItemNode(),
separatorNode: nil
node: ContextControllerActionsListSeparatorItemNode()
)
case let .custom(customItem, _):
return Item(
@ -843,19 +852,18 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
getController: getController,
item: customItem,
requestDismiss: requestDismiss
),
separatorNode: ASDisplayNode()
)
)
}
}
self.highlightedItemBackgroundView = UIView()
self.highlightedItemBackgroundView.alpha = 0.0
super.init()
for item in self.itemNodes {
if let separatorNode = item.separatorNode {
self.addSubnode(separatorNode)
}
}
self.view.addSubview(self.highlightedItemBackgroundView)
for item in self.itemNodes {
self.addSubnode(item.node)
}
@ -889,7 +897,6 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
if currentAction.id == id {
let previousNode = self.itemNodes[i]
previousNode.node.removeFromSupernode()
previousNode.separatorNode?.removeFromSupernode()
let addedNode = Item(
node: ContextControllerActionsListActionItemNode(
@ -903,13 +910,9 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
self.requestUpdateAction(id: id, action: action)
},
item: action
),
separatorNode: ASDisplayNode()
)
)
self.itemNodes[i] = addedNode
if let separatorNode = addedNode.separatorNode {
self.insertSubnode(separatorNode, at: 0)
}
self.addSubnode(addedNode.node)
self.requestUpdate(.immediate)
@ -922,6 +925,19 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
}
}
private func update(transition: ContainedViewLayoutTransition) {
if let params = self.params {
let _ = self.update(
presentationData: params.presentationData,
constrainedSize: params.constrainedSize,
standardMinWidth: params.standardMinWidth,
standardMaxWidth: params.standardMaxWidth,
additionalBottomInset: params.additionalBottomInset,
transition: transition
)
}
}
func update(
presentationData: PresentationData,
constrainedSize: CGSize,
@ -930,11 +946,21 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
additionalBottomInset: CGFloat,
transition: ContainedViewLayoutTransition
) -> (size: CGSize, apparentHeight: CGFloat) {
self.params = Params(
presentationData: presentationData,
constrainedSize: constrainedSize,
standardMinWidth: standardMinWidth,
standardMaxWidth: standardMaxWidth,
additionalBottomInset: additionalBottomInset
)
var highlightedItemFrame: CGRect?
let verticalInset: CGFloat = 10.0
var itemNodeLayouts: [(minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void)] = []
var combinedSize = CGSize()
var combinedSize = CGSize(width: 0.0, height: verticalInset)
for item in self.itemNodes {
item.separatorNode?.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
let itemNodeLayout = item.node.update(
presentationData: presentationData,
constrainedSize: CGSize(width: standardMaxWidth, height: constrainedSize.height)
@ -943,10 +969,11 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
combinedSize.width = max(combinedSize.width, itemNodeLayout.minSize.width)
combinedSize.height += itemNodeLayout.minSize.height
}
combinedSize.height += verticalInset
self.invalidatedItemNodes = false
combinedSize.width = max(combinedSize.width, standardMinWidth)
var nextItemOrigin = CGPoint()
var nextItemOrigin = CGPoint(x: 0.0, y: verticalInset)
for i in 0 ..< self.itemNodes.count {
let item = self.itemNodes[i]
let itemNodeLayout = itemNodeLayouts[i]
@ -960,34 +987,38 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
let itemFrame = CGRect(origin: nextItemOrigin, size: itemSize)
itemTransition.updateFrame(node: item.node, frame: itemFrame, beginWithCurrentState: true)
if let separatorNode = item.separatorNode {
itemTransition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: itemFrame.minX, y: itemFrame.maxY), size: CGSize(width: itemFrame.width, height: UIScreenPixel)), beginWithCurrentState: true)
var separatorHidden = false
if i != self.itemNodes.count - 1 {
switch self.items[i + 1] {
case .separator:
separatorHidden = true
case .action:
separatorHidden = false
case .custom:
separatorHidden = false
}
} else {
separatorHidden = true
}
if let itemContainerNode = item.node as? ContextControllerActionsListCustomItemNode, let itemNode = itemContainerNode.itemNode {
if !itemNode.needsSeparator {
separatorHidden = true
}
}
separatorNode.isHidden = separatorHidden
}
itemNodeLayout.apply(itemSize, itemTransition)
nextItemOrigin.y += itemSize.height
if self.highlightedItemNode === item {
highlightedItemFrame = itemFrame
}
}
if let highlightedItemFrame {
self.highlightedItemBackgroundView.backgroundColor = presentationData.theme.overallDarkAppearance ? UIColor.white : UIColor.black
self.highlightedItemBackgroundView.setMonochromaticEffect(tintColor: self.highlightedItemBackgroundView.backgroundColor)
var highlightTransition = ComponentTransition(transition)
var animateIn = false
if self.highlightedItemBackgroundView.alpha == 0.0 {
if self.highlightedItemBackgroundView.layer.animation(forKey: "opacity") == nil {
highlightTransition = .immediate
}
animateIn = true
}
let highlightFrame = CGRect(origin: CGPoint(x: 10.0, y: highlightedItemFrame.minY), size: CGSize(width: combinedSize.width - 10.0 * 2.0, height: highlightedItemFrame.height))
highlightTransition.setFrame(view: self.highlightedItemBackgroundView, frame: highlightFrame)
highlightTransition.setCornerRadius(layer: self.highlightedItemBackgroundView.layer, cornerRadius: min(20.0, highlightFrame.height * 0.5))
if animateIn {
var alphaTransition = transition
if transition.isAnimated {
alphaTransition = .animated(duration: 0.2, curve: .easeInOut)
}
ComponentTransition(alphaTransition).setAlpha(view: self.highlightedItemBackgroundView, alpha: 0.1)
}
} else if self.highlightedItemBackgroundView.alpha != 0.0 {
ComponentTransition(transition).setAlpha(view: self.highlightedItemBackgroundView, alpha: 0.0)
}
return (combinedSize, combinedSize.height)
@ -1012,6 +1043,8 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
self.hapticFeedback = HapticFeedback()
}
self.hapticFeedback?.tap()
self.update(transition: .animated(duration: 0.16, curve: .easeInOut))
}
}
@ -1022,6 +1055,8 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
if performAction {
highlightedItemNode.node.performAction()
}
self.update(transition: .animated(duration: 0.2, curve: .easeInOut))
}
}
@ -1228,6 +1263,70 @@ func makeContextControllerActionsStackItem(items: ContextController.Items) -> [C
}
}
private final class ItemSelectionRecognizer: UIGestureRecognizer {
var shouldBegin: (() -> Bool)?
private var initialLocation: CGPoint?
private var currentLocation: CGPoint?
public override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
self.delaysTouchesBegan = false
self.delaysTouchesEnded = false
}
public override func reset() {
super.reset()
self.initialLocation = nil
}
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
if let shouldBegin = self.shouldBegin, !shouldBegin() {
self.state = .failed
return
}
if self.initialLocation == nil {
self.initialLocation = touches.first?.location(in: self.view)
}
self.currentLocation = self.initialLocation
self.state = .began
}
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
self.state = .ended
}
public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesCancelled(touches, with: event)
self.state = .cancelled
}
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
self.currentLocation = touches.first?.location(in: self.view)
self.state = .changed
}
public func translation(in: UIView?) -> CGPoint {
if let initialLocation = self.initialLocation, let currentLocation = self.currentLocation {
return CGPoint(x: currentLocation.x - initialLocation.x, y: currentLocation.y - initialLocation.y)
}
return CGPoint()
}
}
public final class ContextControllerActionsStackNode: ASDisplayNode {
public enum Presentation {
case modal
@ -1236,8 +1335,9 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
}
final class NavigationContainer: ASDisplayNode, ASGestureRecognizerDelegate {
let backgroundNode: NavigationBackgroundNode
let parentShadowNode: ASImageNode
let backgroundContainer: GlassBackgroundContainerView
let backgroundView: GlassBackgroundView
let contentContainer: UIView
var requestUpdate: ((ContainedViewLayoutTransition) -> Void)?
var requestPop: (() -> Void)?
@ -1252,16 +1352,18 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
}
override init() {
self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: false)
self.parentShadowNode = ASImageNode()
self.parentShadowNode.image = UIImage(bundleImageName: "Components/Context Menu/Shadow")?.stretchableImage(withLeftCapWidth: 60, topCapHeight: 48)
self.backgroundContainer = GlassBackgroundContainerView()
self.backgroundView = GlassBackgroundView()
self.backgroundContainer.contentView.addSubview(self.backgroundView)
self.contentContainer = UIView()
self.contentContainer.clipsToBounds = true
self.contentContainer.layer.cornerRadius = 30.0
self.backgroundView.contentView.addSubview(self.contentContainer)
super.init()
self.addSubnode(self.backgroundNode)
self.clipsToBounds = true
self.cornerRadius = 14.0
self.view.addSubview(self.backgroundContainer)
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
guard let strongSelf = self else {
@ -1316,18 +1418,32 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
}
func update(presentationData: PresentationData, presentation: Presentation, size: CGSize, transition: ContainedViewLayoutTransition) {
switch presentation {
case .modal:
self.backgroundNode.updateColor(color: presentationData.theme.contextMenu.backgroundColor, enableBlur: false, forceKeepBlur: false, transition: transition)
self.parentShadowNode.isHidden = true
case .inline:
self.backgroundNode.updateColor(color: presentationData.theme.contextMenu.backgroundColor, enableBlur: true, forceKeepBlur: true, transition: transition)
self.parentShadowNode.isHidden = false
case .additional:
self.backgroundNode.updateColor(color: presentationData.theme.contextMenu.backgroundColor.withMultipliedAlpha(0.5), enableBlur: true, forceKeepBlur: true, transition: transition)
self.parentShadowNode.isHidden = false
let transition = ComponentTransition(transition)
transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: size))
var backgroundContainerSize = size
if !transition.animation.isImmediate && (size.width < self.backgroundContainer.bounds.width || size.height < self.backgroundContainer.bounds.height) {
backgroundContainerSize = CGSize(
width: max(self.backgroundContainer.bounds.width, size.width),
height: max(self.backgroundContainer.bounds.height, size.height)
)
}
self.backgroundNode.update(size: size, transition: transition)
if self.backgroundContainer.bounds.size != size {
self.backgroundContainer.update(size: backgroundContainerSize, isDark: presentationData.theme.overallDarkAppearance, transition: transition)
let isDark = presentationData.theme.overallDarkAppearance
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size), completion: { [weak self] completed in
guard let self, completed else {
return
}
self.backgroundContainer.update(size: self.backgroundContainer.bounds.size, isDark: isDark, transition: .immediate)
})
}
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
self.backgroundView.update(size: size, cornerRadius: 30.0, isDark: presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: false, transition: transition)
}
}
@ -1510,7 +1626,7 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
private var itemContainers: [ItemContainer] = []
private var dismissingItemContainers: [(container: ItemContainer, isPopped: Bool)] = []
private var selectionPanGesture: UIPanGestureRecognizer?
private var selectionPanGesture: ItemSelectionRecognizer?
public var topReactionItems: ContextControllerReactionItems? {
return self.itemContainers.last?.reactionItems
@ -1543,7 +1659,6 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
super.init()
self.addSubnode(self.navigationContainer.parentShadowNode)
self.addSubnode(self.navigationContainer)
self.navigationContainer.requestUpdate = { [weak self] transition in
@ -1560,7 +1675,18 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
strongSelf.pop()
}
let selectionPanGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
let selectionPanGesture = ItemSelectionRecognizer(target: self, action: #selector(self.panGesture(_:)))
selectionPanGesture.shouldBegin = { [weak self] in
guard let self, let topItemContainer = self.itemContainers.last else {
return false
}
if topItemContainer.item is ContextControllerActionsCustomStackItem {
return false
}
return true
}
self.selectionPanGesture = selectionPanGesture
self.view.addGestureRecognizer(selectionPanGesture)
selectionPanGesture.isEnabled = false
@ -1568,7 +1694,7 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .changed:
case .began, .changed:
let location = recognizer.location(in: self.view)
self.highlightGestureMoved(location: location)
case .ended:
@ -1661,7 +1787,7 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
positionLock: positionLock
)
self.itemContainers.append(itemContainer)
self.navigationContainer.addSubnode(itemContainer)
self.navigationContainer.contentContainer.addSubview(itemContainer.view)
self.navigationContainer.isNavigationEnabled = self.itemContainers.count > 1
let transition: ContainedViewLayoutTransition
@ -1826,10 +1952,9 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
}
let previousNavigationContainerFrame = self.navigationContainer.frame
transition.updateFrame(node: self.navigationContainer, frame: navigationContainerFrame, beginWithCurrentState: true)
self.navigationContainer.update(presentationData: presentationData, presentation: presentation, size: navigationContainerFrame.size, transition: transition)
let navigationContainerShadowFrame = navigationContainerFrame.insetBy(dx: -30.0, dy: -30.0)
transition.updateFrame(node: self.navigationContainer.parentShadowNode, frame: navigationContainerShadowFrame, beginWithCurrentState: true)
if !navigationContainerFrame.isEmpty {
self.navigationContainer.update(presentationData: presentationData, presentation: presentation, size: navigationContainerFrame.size, transition: transition)
}
for i in 0 ..< self.itemContainers.count {
let xOffset: CGFloat

View file

@ -396,7 +396,7 @@ public enum GradientImageDirection {
case mirroredDiagonal
}
public func generateGradientImage(size: CGSize, scale: CGFloat = 0.0, colors: [UIColor], locations: [CGFloat], direction: GradientImageDirection = .vertical) -> UIImage? {
public func generateGradientImage(size: CGSize, scale: CGFloat = 0.0, colors: [UIColor], locations: [CGFloat], direction: GradientImageDirection = .vertical, isInverted: Bool = false) -> UIImage? {
guard colors.count == locations.count else {
return nil
}
@ -408,7 +408,11 @@ public func generateGradientImage(size: CGSize, scale: CGFloat = 0.0, colors: [U
var locations = locations
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: direction == .horizontal ? CGPoint(x: size.width, y: 0.0) : CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
if isInverted {
context.drawLinearGradient(gradient, start: direction == .horizontal ? CGPoint(x: size.width, y: 0.0) : CGPoint(x: 0.0, y: size.height), end: CGPoint(x: 0.0, y: 0.0), options: CGGradientDrawingOptions())
} else {
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: direction == .horizontal ? CGPoint(x: size.width, y: 0.0) : CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
}
}
let image = UIGraphicsGetImageFromCurrentImageContext()!

View file

@ -116,3 +116,9 @@ open class SimpleTransformLayer: CATransformLayer {
fatalError("init(coder:) has not been implemented")
}
}
public final class SimpleLayerDelegate: NSObject, CALayerDelegate {
public func action(for layer: CALayer, forKey event: String) -> CAAction? {
return nullAction
}
}

View file

@ -881,6 +881,10 @@ public extension CALayer {
return makeBlurFilter()
}
static func variableBlur() -> NSObject? {
return makeVariableBlurFilter()
}
static func luminanceToAlpha() -> NSObject? {
return makeLuminanceToAlphaFilter()
}

View file

@ -22,6 +22,25 @@ public extension TelegramWallpaper {
}
}
var singleColor: UIColor? {
switch self {
case .image:
return nil
case .emoticon:
return nil
case let .file(file):
if self.isPattern, file.settings.colors.count == 1 {
return UIColor(rgb: file.settings.colors[0])
} else {
return nil
}
case let .color(color):
return UIColor(rgb: color)
default:
return nil
}
}
var isColorOrGradient: Bool {
switch self {
case .color, .gradient:

View file

@ -327,14 +327,14 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
}
displayAuthorInfo = incoming && peerId.isGroupOrChannel && effectiveAuthor != nil
if let channel = content.firstMessage.peers[content.firstMessage.id.peerId] as? TelegramChannel, channel.isForumOrMonoForum {
if let chatPeer = content.firstMessage.peers[content.firstMessage.id.peerId], chatPeer.isForumOrMonoForum {
if case .replyThread = chatLocation {
if channel.isMonoForum && chatLocation.threadId != context.account.peerId.toInt64() {
if chatPeer.isMonoForum && chatLocation.threadId != context.account.peerId.toInt64() {
displayAuthorInfo = false
}
} else {
if channel.isMonoForum {
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
if chatPeer.isMonoForum {
if let chatPeer = chatPeer as? TelegramChannel, let linkedMonoforumId = chatPeer.linkedMonoforumId, let mainChannel = content.firstMessage.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) {
headerSeparableThreadId = content.firstMessage.threadId
if let threadId = content.firstMessage.threadId, let peer = content.firstMessage.peers[EnginePeer.Id(threadId)] {

View file

@ -392,19 +392,18 @@ public final class ChatListNavigationBar: Component {
}
}
let edgeEffectHeight: CGFloat = currentLayout.size.height + 20.0
var edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: currentLayout.size.width, height: edgeEffectHeight))
var edgeEffectHeight: CGFloat = currentLayout.size.height + 14.0
if component.search != nil {
if component.activeSearch != nil {
edgeEffectFrame.origin.y -= 16.0
} else {
edgeEffectFrame.origin.y -= embeddedSearchBarExpansionHeight
edgeEffectHeight -= embeddedSearchBarExpansionHeight
}
} else if component.activeSearch != nil {
edgeEffectFrame.origin.y -= 16.0
}
edgeEffectHeight = max(0.0, edgeEffectHeight)
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: currentLayout.size.width, height: edgeEffectHeight))
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: component.theme.list.plainBackgroundColor, blur: true, alpha: 0.55, rect: edgeEffectFrame, edge: .top, edgeSize: 40.0, transition: transition)
self.edgeEffectView.update(content: component.theme.list.plainBackgroundColor, blur: true, alpha: 0.85, rect: edgeEffectFrame, edge: .top, edgeSize: min(54.0, edgeEffectHeight), transition: transition)
let headerTransition = transition

View file

@ -19,14 +19,16 @@ public final class ChatNavigationBarTitleView: UIView, NavigationBarTitleView {
private final class ContentData {
let context: AccountContext
let theme: PresentationTheme
let wallpaper: TelegramWallpaper
let strings: PresentationStrings
let dateTimeFormat: PresentationDateTimeFormat
let nameDisplayOrder: PresentationPersonNameOrder
let content: ChatTitleContent
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, content: ChatTitleContent) {
init(context: AccountContext, theme: PresentationTheme, wallpaper: TelegramWallpaper, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, content: ChatTitleContent) {
self.context = context
self.theme = theme
self.wallpaper = wallpaper
self.strings = strings
self.dateTimeFormat = dateTimeFormat
self.nameDisplayOrder = nameDisplayOrder
@ -72,6 +74,7 @@ public final class ChatNavigationBarTitleView: UIView, NavigationBarTitleView {
public func update(
context: AccountContext,
theme: PresentationTheme,
wallpaper: TelegramWallpaper,
strings: PresentationStrings,
dateTimeFormat: PresentationDateTimeFormat,
nameDisplayOrder: PresentationPersonNameOrder,
@ -81,6 +84,7 @@ public final class ChatNavigationBarTitleView: UIView, NavigationBarTitleView {
self.contentData = ContentData(
context: context,
theme: theme,
wallpaper: wallpaper,
strings: strings,
dateTimeFormat: dateTimeFormat,
nameDisplayOrder: nameDisplayOrder,
@ -111,6 +115,18 @@ public final class ChatNavigationBarTitleView: UIView, NavigationBarTitleView {
let transition = ComponentTransition(transition)
if let contentData = self.contentData {
let displayBackground: Bool
if let singleColor = contentData.wallpaper.singleColor {
let brightness = singleColor.brightness
if brightness <= 0.2 || brightness >= 0.8 {
displayBackground = false
} else {
displayBackground = true
}
} else {
displayBackground = true
}
let titleSize = self.title.update(
transition: transition,
component: AnyComponent(ChatTitleComponent(
@ -119,7 +135,7 @@ public final class ChatNavigationBarTitleView: UIView, NavigationBarTitleView {
strings: contentData.strings,
dateTimeFormat: contentData.dateTimeFormat,
nameDisplayOrder: contentData.nameDisplayOrder,
displayBackground: true,
displayBackground: displayBackground,
content: contentData.content,
activities: self.activities,
networkState: self.networkState,

View file

@ -13,6 +13,7 @@ swift_library(
"//submodules/Display",
"//submodules/ComponentFlow",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
],
visibility = [
"//visibility:public",

View file

@ -3,6 +3,7 @@ import UIKit
import Display
import ComponentFlow
import ComponentDisplayAdapters
import GlassBackgroundComponent
public class EdgeEffectView: UIView {
public enum Edge {
@ -31,52 +32,19 @@ public class EdgeEffectView: UIView {
public func update(content: UIColor, blur: Bool = false, alpha: CGFloat = 0.75, rect: CGRect, edge: Edge, edgeSize: CGFloat, transition: ComponentTransition) {
#if DEBUG && false
let content: UIColor = .blue
let blur: Bool = !"".isEmpty
self.backgroundColor = .blue
//let blur: Bool = !"".isEmpty
#endif
transition.setBackgroundColor(view: self.contentView, color: content)
switch edge {
case .top:
self.contentMaskView.transform = CGAffineTransformMakeScale(1.0, -1.0)
case .bottom:
self.contentMaskView.transform = .identity
}
transition.setAlpha(view: self.contentView, alpha: alpha)
let bounds = CGRect(origin: CGPoint(), size: rect.size)
transition.setFrame(view: self.contentView, frame: bounds)
transition.setFrame(view: self.contentMaskView, frame: bounds)
if self.contentMaskView.image?.size.height != edgeSize {
let baseGradientAlpha: CGFloat = alpha
let numSteps = 8
let firstStep = 1
let firstLocation = 0.0
let colors: [UIColor] = (0 ..< numSteps).map { i in
if i < firstStep {
return UIColor(white: 1.0, alpha: 1.0)
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
let value: CGFloat = bezierPoint(0.42, 0.0, 0.58, 1.0, step)
return UIColor(white: 1.0, alpha: baseGradientAlpha * value)
}
}
let locations: [CGFloat] = (0 ..< numSteps).map { i in
if i < firstStep {
return 0.0
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
return (firstLocation + (1.0 - firstLocation) * step)
}
}
if edgeSize > 0.0 {
self.contentMaskView.image = generateGradientImage(
size: CGSize(width: 8.0, height: edgeSize),
colors: colors,
locations: locations
)?.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(edgeSize))
self.contentMaskView.image = EdgeEffectView.generateEdgeGradient(baseHeight: edgeSize, isInverted: edge == .bottom)
} else {
self.contentMaskView.image = nil
}
@ -87,46 +55,417 @@ public class EdgeEffectView: UIView {
if let current = self.blurView {
blurView = current
} else {
let gradientMaskLayer = SimpleGradientLayer()
let baseGradientAlpha: CGFloat = 1.0
let numSteps = 8
let firstStep = 1
let firstLocation = 0.8
gradientMaskLayer.colors = (0 ..< numSteps).map { i in
if i < firstStep {
return UIColor(white: 1.0, alpha: 1.0).cgColor
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step)
return UIColor(white: 1.0, alpha: baseGradientAlpha * value).cgColor
}
}
gradientMaskLayer.locations = (0 ..< numSteps).map { i -> NSNumber in
if i < firstStep {
return 0.0 as NSNumber
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
return (firstLocation + (1.0 - firstLocation) * step) as NSNumber
}
}
blurView = VariableBlurView(gradientMask: self.contentMaskView.image ?? UIImage(), maxBlurRadius: 8.0)
blurView.layer.mask = gradientMaskLayer
blurView = VariableBlurView(maxBlurRadius: 1.0)
self.insertSubview(blurView, at: 0)
self.blurView = blurView
}
blurView.update(size: bounds.size, transition: transition.containedViewLayoutTransition)
blurView.update(
size: bounds.size,
constantHeight: edgeSize,
isInverted: edge == .bottom,
gradient: EdgeEffectView.generateEdgeGradientData(baseHeight: edgeSize),
transition: transition.containedViewLayoutTransition
)
transition.setFrame(view: blurView, frame: bounds)
if let maskLayer = blurView.layer.mask {
transition.setFrame(layer: maskLayer, frame: bounds)
maskLayer.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
}
blurView.transform = self.contentMaskView.transform
} else if let blurView = self.blurView {
self.blurView = nil
blurView.removeFromSuperview()
}
}
public static func generateEdgeGradientData(baseHeight: CGFloat) -> VariableBlurView.Gradient {
let gradientColors: [CGFloat] = [
0.8470588235294118,
0.8431372549019608,
0.8392156862745098,
0.8352941176470589,
0.8313725490196078,
0.8274509803921568,
0.8235294117647058,
0.8196078431372549,
0.8156862745098039,
0.8117647058823529,
0.807843137254902,
0.803921568627451,
0.8,
0.7960784313725491,
0.792156862745098,
0.788235294117647,
0.7843137254901961,
0.7803921568627451,
0.7764705882352941,
0.7725490196078432,
0.7686274509803921,
0.7647058823529411,
0.7607843137254902,
0.7568627450980392,
0.7529411764705882,
0.7490196078431373,
0.7450980392156863,
0.7411764705882353,
0.7372549019607844,
0.7333333333333334,
0.7294117647058824,
0.7254901960784313,
0.7215686274509804,
0.7176470588235294,
0.7137254901960784,
0.7098039215686274,
0.7019607843137254,
0.6941176470588235,
0.6862745098039216,
0.6784313725490196,
0.6705882352941177,
0.6588235294117647,
0.6509803921568628,
0.6431372549019607,
0.6313725490196078,
0.6235294117647059,
0.615686274509804,
0.603921568627451,
0.596078431372549,
0.5882352941176471,
0.5764705882352941,
0.5647058823529412,
0.5529411764705883,
0.5411764705882354,
0.5294117647058824,
0.5176470588235293,
0.5058823529411764,
0.49411764705882355,
0.4862745098039216,
0.4745098039215686,
0.4627450980392157,
0.4549019607843138,
0.44313725490196076,
0.43137254901960786,
0.41960784313725485,
0.4117647058823529,
0.4,
0.388235294117647,
0.3764705882352941,
0.3647058823529412,
0.3529411764705882,
0.3411764705882353,
0.3294117647058824,
0.3176470588235294,
0.3058823529411765,
0.2941176470588235,
0.2823529411764706,
0.2705882352941177,
0.2588235294117647,
0.2431372549019608,
0.2313725490196078,
0.21568627450980393,
0.19999999999999996,
0.18039215686274512,
0.16078431372549018,
0.14117647058823535,
0.11764705882352944,
0.09019607843137256,
0.04705882352941182,
0.0,
]
let gradientColorNorm = gradientColors.max()!
let gradientLocations: [CGFloat] = [
0.0,
0.020905923344947737,
0.059233449477351915,
0.08710801393728224,
0.10801393728222997,
0.12195121951219512,
0.13240418118466898,
0.14285714285714285,
0.15331010452961671,
0.1602787456445993,
0.17073170731707318,
0.18118466898954705,
0.1916376306620209,
0.20209059233449478,
0.20905923344947736,
0.21254355400696864,
0.21951219512195122,
0.2264808362369338,
0.23344947735191637,
0.23693379790940766,
0.24390243902439024,
0.24738675958188153,
0.25435540069686413,
0.2578397212543554,
0.2613240418118467,
0.2682926829268293,
0.27177700348432055,
0.27526132404181186,
0.28222996515679444,
0.2857142857142857,
0.289198606271777,
0.2926829268292683,
0.2961672473867596,
0.29965156794425085,
0.30313588850174217,
0.30662020905923343,
0.313588850174216,
0.3205574912891986,
0.32752613240418116,
0.3344947735191638,
0.34146341463414637,
0.34843205574912894,
0.3554006968641115,
0.3623693379790941,
0.3693379790940767,
0.37630662020905925,
0.3797909407665505,
0.3867595818815331,
0.39372822299651566,
0.397212543554007,
0.40418118466898956,
0.41114982578397213,
0.4181184668989547,
0.4250871080139373,
0.43205574912891986,
0.43902439024390244,
0.445993031358885,
0.4529616724738676,
0.4564459930313589,
0.4634146341463415,
0.47038327526132406,
0.4738675958188153,
0.4808362369337979,
0.4878048780487805,
0.49477351916376305,
0.49825783972125437,
0.5052264808362369,
0.5121951219512195,
0.519163763066202,
0.5261324041811847,
0.5331010452961672,
0.5400696864111498,
0.5470383275261324,
0.554006968641115,
0.5609756097560976,
0.5679442508710801,
0.5749128919860628,
0.5818815331010453,
0.5888501742160279,
0.5993031358885017,
0.6062717770034843,
0.6167247386759582,
0.627177700348432,
0.6411149825783972,
0.6585365853658537,
0.6759581881533101,
0.6968641114982579,
0.7282229965156795,
0.7909407665505227,
1.0,
]
return VariableBlurView.Gradient(
height: baseHeight,
alpha: gradientColors.map { $0 / gradientColorNorm },
positions: gradientLocations
)
}
public static func generateEdgeGradient(baseHeight: CGFloat, isInverted: Bool) -> UIImage {
let gradientColors: [CGFloat] = [
0.8470588235294118,
0.8431372549019608,
0.8392156862745098,
0.8352941176470589,
0.8313725490196078,
0.8274509803921568,
0.8235294117647058,
0.8196078431372549,
0.8156862745098039,
0.8117647058823529,
0.807843137254902,
0.803921568627451,
0.8,
0.7960784313725491,
0.792156862745098,
0.788235294117647,
0.7843137254901961,
0.7803921568627451,
0.7764705882352941,
0.7725490196078432,
0.7686274509803921,
0.7647058823529411,
0.7607843137254902,
0.7568627450980392,
0.7529411764705882,
0.7490196078431373,
0.7450980392156863,
0.7411764705882353,
0.7372549019607844,
0.7333333333333334,
0.7294117647058824,
0.7254901960784313,
0.7215686274509804,
0.7176470588235294,
0.7137254901960784,
0.7098039215686274,
0.7019607843137254,
0.6941176470588235,
0.6862745098039216,
0.6784313725490196,
0.6705882352941177,
0.6588235294117647,
0.6509803921568628,
0.6431372549019607,
0.6313725490196078,
0.6235294117647059,
0.615686274509804,
0.603921568627451,
0.596078431372549,
0.5882352941176471,
0.5764705882352941,
0.5647058823529412,
0.5529411764705883,
0.5411764705882354,
0.5294117647058824,
0.5176470588235293,
0.5058823529411764,
0.49411764705882355,
0.4862745098039216,
0.4745098039215686,
0.4627450980392157,
0.4549019607843138,
0.44313725490196076,
0.43137254901960786,
0.41960784313725485,
0.4117647058823529,
0.4,
0.388235294117647,
0.3764705882352941,
0.3647058823529412,
0.3529411764705882,
0.3411764705882353,
0.3294117647058824,
0.3176470588235294,
0.3058823529411765,
0.2941176470588235,
0.2823529411764706,
0.2705882352941177,
0.2588235294117647,
0.2431372549019608,
0.2313725490196078,
0.21568627450980393,
0.19999999999999996,
0.18039215686274512,
0.16078431372549018,
0.14117647058823535,
0.11764705882352944,
0.09019607843137256,
0.04705882352941182,
0.0,
]
let gradientColorNorm = gradientColors.max()!
let gradientLocations: [CGFloat] = [
0.0,
0.020905923344947737,
0.059233449477351915,
0.08710801393728224,
0.10801393728222997,
0.12195121951219512,
0.13240418118466898,
0.14285714285714285,
0.15331010452961671,
0.1602787456445993,
0.17073170731707318,
0.18118466898954705,
0.1916376306620209,
0.20209059233449478,
0.20905923344947736,
0.21254355400696864,
0.21951219512195122,
0.2264808362369338,
0.23344947735191637,
0.23693379790940766,
0.24390243902439024,
0.24738675958188153,
0.25435540069686413,
0.2578397212543554,
0.2613240418118467,
0.2682926829268293,
0.27177700348432055,
0.27526132404181186,
0.28222996515679444,
0.2857142857142857,
0.289198606271777,
0.2926829268292683,
0.2961672473867596,
0.29965156794425085,
0.30313588850174217,
0.30662020905923343,
0.313588850174216,
0.3205574912891986,
0.32752613240418116,
0.3344947735191638,
0.34146341463414637,
0.34843205574912894,
0.3554006968641115,
0.3623693379790941,
0.3693379790940767,
0.37630662020905925,
0.3797909407665505,
0.3867595818815331,
0.39372822299651566,
0.397212543554007,
0.40418118466898956,
0.41114982578397213,
0.4181184668989547,
0.4250871080139373,
0.43205574912891986,
0.43902439024390244,
0.445993031358885,
0.4529616724738676,
0.4564459930313589,
0.4634146341463415,
0.47038327526132406,
0.4738675958188153,
0.4808362369337979,
0.4878048780487805,
0.49477351916376305,
0.49825783972125437,
0.5052264808362369,
0.5121951219512195,
0.519163763066202,
0.5261324041811847,
0.5331010452961672,
0.5400696864111498,
0.5470383275261324,
0.554006968641115,
0.5609756097560976,
0.5679442508710801,
0.5749128919860628,
0.5818815331010453,
0.5888501742160279,
0.5993031358885017,
0.6062717770034843,
0.6167247386759582,
0.627177700348432,
0.6411149825783972,
0.6585365853658537,
0.6759581881533101,
0.6968641114982579,
0.7282229965156795,
0.7909407665505227,
1.0,
]
return generateGradientImage(
size: CGSize(width: 1.0, height: baseHeight),
colors: gradientColors.map { UIColor(white: 0.0, alpha: $0 / gradientColorNorm) },
locations: gradientLocations,
isInverted: isInverted
)!.resizableImage(withCapInsets: UIEdgeInsets(top: isInverted ? baseHeight : 0.0, left: 0.0, bottom: isInverted ? 0.0 : baseHeight, right: 0.0), resizingMode: .stretch)
}
}
public final class EdgeEffectComponent: Component {
@ -192,28 +531,107 @@ public final class EdgeEffectComponent: Component {
}
}
public final class VariableBlurView: UIVisualEffectView {
public let maxBlurRadius: CGFloat
public var gradientMask: UIImage {
didSet {
if self.gradientMask !== oldValue {
self.resetEffect()
}
public final class VariableBlurView: UIView {
private struct Params: Equatable {
let size: CGSize
let constantHeight: CGFloat
let isInverted: Bool
let gradient: Gradient
init(size: CGSize, constantHeight: CGFloat, isInverted: Bool, gradient: Gradient) {
self.size = size
self.constantHeight = constantHeight
self.isInverted = isInverted
self.gradient = gradient
}
}
public init(gradientMask: UIImage, maxBlurRadius: CGFloat = 20.0) {
self.gradientMask = gradientMask
public final class Gradient: Equatable {
public let height: CGFloat
public let alpha: [CGFloat]
public let positions: [CGFloat]
public init(height: CGFloat, alpha: [CGFloat], positions: [CGFloat]) {
self.height = height
self.alpha = alpha
self.positions = positions
}
public static func ==(lhs: Gradient, rhs: Gradient) -> Bool {
if lhs === rhs {
return true
}
if lhs.height != rhs.height {
return false
}
if lhs.alpha != rhs.alpha {
return false
}
if lhs.positions != rhs.positions {
return false
}
return true
}
}
public let maxBlurRadius: CGFloat
private let effectLayerDelegate: SimpleLayerDelegate
private var mainEffectLayer: CALayer?
private var additionalEffectLayer: CALayer?
private var params: Params?
private var gradientImage: UIImage?
private var isAnimating: Bool = false
private let imageSubview: UIImageView
public init(maxBlurRadius: CGFloat = 20.0) {
self.maxBlurRadius = maxBlurRadius
super.init(effect: UIBlurEffect(style: .regular))
self.effectLayerDelegate = SimpleLayerDelegate()
self.mainEffectLayer = createBackdropLayer()
if #available(iOS 26.0, *) {
if let mainEffectLayer = self.mainEffectLayer, let variableBlur = CALayer.variableBlur() {
variableBlur.setValue(self.maxBlurRadius, forKey: "inputRadius")
variableBlur.setValue("mask_source", forKey: "inputSourceSublayerName")
variableBlur.setValue(true, forKey: "inputNormalizeEdges")
mainEffectLayer.filters = [variableBlur]
}
} else {
self.additionalEffectLayer = createBackdropLayer()
}
self.imageSubview = UIImageView()
self.imageSubview.layer.name = "mask_source"
super.init(frame: CGRect())
let groupName = "group-\(UInt32.random(in: 0 ... UInt32.max))"
self.resetEffect()
if self.subviews.indices.contains(1) {
let tintOverlayView = subviews[1]
tintOverlayView.alpha = 0
if let mainEffectLayer = self.mainEffectLayer {
mainEffectLayer.delegate = self.effectLayerDelegate
mainEffectLayer.setValue(0.5, forKey: "scale")
mainEffectLayer.setValue(groupName, forKey: "groupName")
self.layer.addSublayer(mainEffectLayer)
if #available(iOS 26.0, *) {
mainEffectLayer.addSublayer(self.imageSubview.layer)
}
}
if let additionalEffectLayer = self.additionalEffectLayer {
self.layer.addSublayer(additionalEffectLayer)
additionalEffectLayer.delegate = self.effectLayerDelegate
additionalEffectLayer.setValue(0.5, forKey: "scale")
if let blurFilter = CALayer.blur() {
blurFilter.setValue(true, forKey: "inputNormalizeEdges")
blurFilter.setValue((maxBlurRadius * 2.0) as NSNumber, forKey: "inputRadius")
additionalEffectLayer.filters = [blurFilter]
}
}
}
@ -221,68 +639,88 @@ public final class VariableBlurView: UIVisualEffectView {
fatalError("init(coder:) has not been implemented")
}
override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
if self.traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
self.resetEffect()
}
}
private func resetEffect() {
let filterClassStringEncoded = "Q0FGaWx0ZXI="
let filterClassString: String = {
if
let data = Data(base64Encoded: filterClassStringEncoded),
let string = String(data: data, encoding: .utf8)
{
return string
}
return ""
}()
let filterWithTypeStringEncoded = "ZmlsdGVyV2l0aFR5cGU6"
let filterWithTypeString: String = {
if
let data = Data(base64Encoded: filterWithTypeStringEncoded),
let string = String(data: data, encoding: .utf8)
{
return string
}
return ""
}()
let filterWithTypeSelector = Selector(filterWithTypeString)
guard let filterClass = NSClassFromString(filterClassString) as AnyObject as? NSObjectProtocol else {
private func updateLegacyEffect() {
guard let params = self.params else {
return
}
guard filterClass.responds(to: filterWithTypeSelector) else {
guard let mainEffectLayer = self.mainEffectLayer else {
return
}
let variableBlur = filterClass.perform(filterWithTypeSelector, with: "variableBlur").takeUnretainedValue()
guard let variableBlur = variableBlur as? NSObject else {
guard let variableBlur = CALayer.variableBlur() else {
return
}
guard let gradientImageRef = self.gradientMask.cgImage else {
guard let gradientImage = self.gradientImage else {
return
}
variableBlur.setValue(self.maxBlurRadius, forKey: "inputRadius")
variableBlur.setValue(gradientImageRef, forKey: "inputMaskImage")
variableBlur.setValue(true, forKey: "inputNormalizeEdges")
let backdropLayer = self.subviews.first?.layer
backdropLayer?.filters = [variableBlur]
backdropLayer?.setValue(UIScreenScale, forKey: "scale")
let image: UIImage? = generateImage(CGSize(width: 1.0, height: min(800.0, params.size.height)), rotatedContext: { size, context in
UIGraphicsPushContext(context)
defer {
UIGraphicsPopContext()
}
context.clear(CGRect(origin: CGPoint(), size: size))
let mainEffectFrame: CGRect
let additionalEffectFrame: CGRect
if params.isInverted {
mainEffectFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: params.constantHeight))
additionalEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: params.constantHeight), size: CGSize(width: size.width, height: max(0.0, size.height - params.constantHeight)))
} else {
mainEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - params.constantHeight), size: CGSize(width: size.width, height: params.constantHeight))
additionalEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: max(0.0, size.height - params.constantHeight)))
}
context.setFillColor(UIColor(white: 0.0, alpha: 1.0).cgColor)
context.fill(additionalEffectFrame)
gradientImage.draw(in: mainEffectFrame, blendMode: .normal, alpha: 1.0)
})
if let cgImage = image?.cgImage {
variableBlur.setValue(cgImage, forKey: "inputMaskImage")
}
mainEffectLayer.filters = [variableBlur]
}
public func update(size: CGSize, transition: ContainedViewLayoutTransition) {
for layer in self.layer.sublayers ?? [] {
transition.updateFrame(layer: layer, frame: CGRect(origin: CGPoint(), size: size))
public func update(size: CGSize, constantHeight: CGFloat, isInverted: Bool, gradient: Gradient, transition: ContainedViewLayoutTransition) {
let params = Params(size: size, constantHeight: constantHeight, isInverted: isInverted, gradient: gradient)
if params == self.params {
return
}
let isGradientUpdated = gradient != self.params?.gradient
let isHeightUpdated = gradient.height != self.params?.gradient.height || size.height != self.params?.size.height
if isGradientUpdated {
self.gradientImage = EdgeEffectView.generateEdgeGradient(baseHeight: max(1.0, params.gradient.height), isInverted: params.isInverted)
}
self.params = params
guard let mainEffectLayer = self.mainEffectLayer else {
return
}
let transition = ComponentTransition(transition)
if #available(iOS 26.0, *) {
if isGradientUpdated {
self.imageSubview.image = self.gradientImage
}
transition.setFrame(layer: mainEffectLayer, frame: CGRect(origin: CGPoint(), size: size))
transition.setFrame(view: self.imageSubview, frame: CGRect(origin: CGPoint(), size: size))
} else {
if isHeightUpdated || isGradientUpdated {
self.updateLegacyEffect()
transition.setFrame(layer: mainEffectLayer, frame: CGRect(origin: CGPoint(), size: size))
} else {
transition.setFrame(layer: mainEffectLayer, frame: CGRect(origin: CGPoint(), size: size))
}
}
}
}

View file

@ -55,6 +55,11 @@ private func invokeBackdropLayerInitMethod(object: NSObject) -> NSObject? {
}
}
public func createBackdropLayer() -> CALayer? {
return invokeBackdropLayerCreateMethod().flatMap(invokeBackdropLayerInitMethod) as? CALayer
}
private var cachedBackdropLayerSetScaleMethod: (@convention(c) (NSObject, Selector, Double) -> Void, Selector)?
private func invokeBackdropLayerSetScaleMethod(object: NSObject, scale: Double) {
if let cachedBackdropLayerSetScaleMethod {
@ -93,7 +98,7 @@ final class LegacyGlassView: UIView {
override init(frame: CGRect) {
self.backdropLayerDelegate = BackdropLayerDelegate()
self.backdropLayer = invokeBackdropLayerCreateMethod().flatMap(invokeBackdropLayerInitMethod) as? CALayer
self.backdropLayer = createBackdropLayer()
super.init(frame: frame)

View file

@ -396,6 +396,7 @@ extension ChatControllerImpl {
self.chatTitleView?.update(
context: self.context,
theme: self.presentationData.theme,
wallpaper: self.presentationInterfaceState.chatWallpaper,
strings: self.presentationData.strings,
dateTimeFormat: self.presentationData.dateTimeFormat,
nameDisplayOrder: self.presentationData.nameDisplayOrder,
@ -3835,11 +3836,32 @@ extension ChatControllerImpl {
}
let chatController = strongSelf.context.sharedContext.makeChatController(context: strongSelf.context, chatLocation: chatLocation, subject: .pinnedMessages(id: pinnedMessage.message.id), botStart: nil, mode: .standard(.previewing), params: nil)
chatController.customNavigationController = strongSelf.navigationController as? NavigationController
var dismissPreviewing: ((Bool) -> (() -> Void))?
chatController.dismissPreviewing = { animateIn in
return dismissPreviewing?(animateIn) ?? {}
}
chatController.canReadHistory.set(false)
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
let contextController = ContextController(presentationData: strongSelf.presentationData, source: .controller(ChatContextControllerContentSourceImpl(controller: chatController, sourceNode: node, passthroughTouches: true)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
dismissPreviewing = { [weak self, weak contextController] animateIn in
if let self, let contextController {
if animateIn {
contextController.statusBar.statusBarStyle = .Ignore
contextController.animateDismissalIfNeeded()
self.present(contextController, in: .window(.root))
return {
contextController.dismissNow()
}
} else {
contextController.dismiss()
}
}
return {}
}
strongSelf.presentInGlobalOverlay(contextController)
}, joinGroupCall: { [weak self] activeCall in
guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {

View file

@ -52,6 +52,7 @@ extension ChatControllerImpl {
self.chatTitleView?.update(
context: self.context,
theme: self.presentationData.theme,
wallpaper: self.presentationInterfaceState.chatWallpaper,
strings: self.presentationData.strings,
dateTimeFormat: self.presentationData.dateTimeFormat,
nameDisplayOrder: self.presentationData.nameDisplayOrder,
@ -103,6 +104,7 @@ extension ChatControllerImpl {
self.chatTitleView?.update(
context: self.context,
theme: self.presentationData.theme,
wallpaper: self.presentationInterfaceState.chatWallpaper,
strings: self.presentationData.strings,
dateTimeFormat: self.presentationData.dateTimeFormat,
nameDisplayOrder: self.presentationData.nameDisplayOrder,

View file

@ -1925,7 +1925,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
updateExtraNavigationBarBackgroundHeight(0.0, 0.0, nil, transition)
var sidePanelTopInset: CGFloat = insets.top + 4.0
var sidePanelTopInset: CGFloat = insets.top - 4.0
let contentBounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width - wrappingInsets.left - wrappingInsets.right, height: layout.size.height - wrappingInsets.top - wrappingInsets.bottom)
@ -2235,6 +2235,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
let edgeEffectAlpha: CGFloat
if case .image = self.chatPresentationInterfaceState.chatWallpaper {
edgeEffectAlpha = 0.7
} else {
edgeEffectAlpha = self.chatPresentationInterfaceState.chatWallpaper.singleColor != nil ? 0.85 : 0.75
}
var bottomBackgroundEdgeEffectNode: WallpaperEdgeEffectNode?
if let current = self.bottomBackgroundEdgeEffectNode {
bottomBackgroundEdgeEffectNode = current
@ -2247,12 +2254,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
if let bottomBackgroundEdgeEffectNode {
var blurFrame = inputBackgroundFrame
blurFrame.origin.y -= 18.0
blurFrame.origin.y -= 20.0
blurFrame.size.height = max(100.0, layout.size.height - blurFrame.origin.y)
transition.updateFrame(node: bottomBackgroundEdgeEffectNode, frame: blurFrame)
bottomBackgroundEdgeEffectNode.update(
rect: blurFrame,
edge: WallpaperEdgeEffectEdge(edge: .bottom, size: 100.0),
edge: WallpaperEdgeEffectEdge(edge: .bottom, size: min(60.0, blurFrame.height)),
alpha: edgeEffectAlpha,
blur: false,
containerSize: wallpaperBounds.size,
transition: transition
@ -2269,7 +2277,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
var contentBottomInset: CGFloat = inputPanelsHeight + 11.0 + inputPanelsInset
var contentBottomInset: CGFloat = inputPanelsHeight + inputPanelsInset
if previewing {
} else {
contentBottomInset += 11.0
}
if let scrollContainerNode = self.scrollContainerNode {
transition.updateFrame(node: scrollContainerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
@ -2301,7 +2313,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
if let containerNode = self.containerNode {
contentBottomInset += 8.0
let containerNodeFrame = CGRect(origin: CGPoint(x: wrappingInsets.left, y: wrappingInsets.top), size: CGSize(width: contentBounds.size.width, height: contentBounds.size.height - containerInsets.bottom - inputPanelsHeight - 8.0))
transition.updateFrame(node: containerNode, frame: containerNodeFrame)
@ -2450,13 +2461,14 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
if let topBackgroundEdgeEffectNode {
var blurFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(100.0, listInsets.bottom + 10.0)))
blurFrame.origin.y = listInsets.bottom + 10.0 - blurFrame.height
var blurFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(100.0, listInsets.bottom + 24.0)))
blurFrame.origin.y = listInsets.bottom + 24.0 - blurFrame.height
transition.updateFrame(node: topBackgroundEdgeEffectNode, frame: blurFrame)
topBackgroundEdgeEffectNode.update(
rect: blurFrame,
edge: WallpaperEdgeEffectEdge(edge: .top, size: 100.0),
blur: false,
edge: WallpaperEdgeEffectEdge(edge: .top, size: 70.0),
alpha: edgeEffectAlpha,
blur: true,
containerSize: wallpaperBounds.size,
transition: transition
)
@ -2518,6 +2530,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
transition.updateFrame(node: self.inputPanelBackgroundNode, frame: apparentInputBackgroundFrame, beginWithCurrentState: true)
if let headerPanelsComponentView = self.headerPanelsView?.view, let headerPanelsSize {
sidePanelTopInset += 8.0
let headerPanelsFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: sidePanelTopInset), size: headerPanelsSize)
var headerPanelsTransition = ComponentTransition(transition)
if headerPanelsComponentView.superview == nil {

View file

@ -2022,13 +2022,6 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
return false
})
if isEdited {
if !actions.isEmpty {
actions.insert(.separator, at: 0)
}
actions.insert(.custom(ChatReadReportContextItem(context: context, message: message, hasReadReports: false, isEdit: true, stats: MessageReadStats(reactionCount: 0, peers: [], readTimestamps: [:]), action: nil), false), at: 0)
}
if canViewAuthor {
actions.insert(.custom(ChatMessageAuthorContextItem(context: context, message: message, action: { c, f, peer in
@ -2037,6 +2030,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
})
}), false), at: 0)
}
if let peer = message.peers[message.id.peerId], (canViewStats || reactionCount != 0) {
var hasReadReports = false
if let channel = peer as? TelegramChannel {
@ -2151,6 +2145,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
}
}
if isEdited {
if !actions.isEmpty {
actions.insert(.separator, at: 0)
}
actions.insert(.custom(ChatReadReportContextItem(context: context, message: message, hasReadReports: false, isEdit: true, stats: MessageReadStats(reactionCount: 0, peers: [], readTimestamps: [:]), action: nil), false), at: 0)
}
if !actions.isEmpty, case .separator = actions[0] {
actions.removeFirst()
}
@ -2606,7 +2607,6 @@ private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenu
private let actionSelected: (ContextMenuActionResult) -> Void
private let backgroundNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let textNode: ImmediateTextNode
private let statusNode: ImmediateTextNode
private let iconNode: ASImageNode
@ -2633,10 +2633,6 @@ private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenu
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isAccessibilityElement = false
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isAccessibilityElement = false
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
self.highlightedBackgroundNode.alpha = 0.0
self.textNode = ImmediateTextNode()
self.textNode.isAccessibilityElement = false
@ -2666,24 +2662,12 @@ private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenu
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.highlightedBackgroundNode)
self.addSubnode(self.textNode)
self.addSubnode(self.statusNode)
self.addSubnode(self.iconNode)
self.addSubnode(self.textIconNode)
self.addSubnode(self.buttonNode)
self.buttonNode.highligthedChanged = { [weak self] highligted in
guard let strongSelf = self else {
return
}
if highligted {
strongSelf.highlightedBackgroundNode.alpha = 1.0
} else {
strongSelf.highlightedBackgroundNode.alpha = 0.0
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
}
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
}
@ -2694,14 +2678,8 @@ private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenu
override func didLoad() {
super.didLoad()
self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: { [weak self] in
if let strongSelf = self {
strongSelf.highlightedBackgroundNode.alpha = 0.75
}
}, willExit: { [weak self] in
if let strongSelf = self {
strongSelf.highlightedBackgroundNode.alpha = 0.0
}
self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: {
}, willExit: {
})
let timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] in
@ -2720,13 +2698,13 @@ private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenu
let subtextFont = Font.regular(self.presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
self.statusNode.attributedText = NSAttributedString(string: stringForRemainingTime(Int32(max(0.0, self.item.timestamp - Date().timeIntervalSince1970)), strings: presentationData.strings), font: subtextFont, textColor: presentationData.theme.contextMenu.destructiveColor)
let sideInset: CGFloat = 16.0
let sideInset: CGFloat = 18.0
let statusSize = self.statusNode.updateLayout(CGSize(width: size.width - sideInset - 32.0 + 4.0, height: .greatestFiniteMagnitude))
transition.updateFrameAdditive(node: self.statusNode, frame: CGRect(origin: CGPoint(x: self.statusNode.frame.minX, y: self.statusNode.frame.minY), size: statusSize))
}
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
let sideInset: CGFloat = 16.0
let sideInset: CGFloat = 18.0
let iconSideInset: CGFloat = 12.0
let verticalInset: CGFloat = 12.0
@ -2758,14 +2736,12 @@ private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenu
}
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
})
}
func updateTheme(presentationData: PresentationData) {
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
let subtextFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 13.0 / 17.0)
@ -2788,11 +2764,6 @@ private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenu
}
func setIsHighlighted(_ value: Bool) {
if value {
self.highlightedBackgroundNode.alpha = 1.0
} else {
self.highlightedBackgroundNode.alpha = 0.0
}
}
func canBeHighlighted() -> Bool {
@ -2831,17 +2802,10 @@ private final class ChatMessageAuthorContextItemNode: ASDisplayNode, ContextMenu
private let actionSelected: (ContextMenuActionResult) -> Void
private let backgroundNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let placeholderCalculationTextNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let shimmerNode: ShimmerEffectNode
/*private let avatarsNode: AnimatedAvatarSetNode
private let avatarsContext: AnimatedAvatarSetContext
private let placeholderAvatarsNode: AnimatedAvatarSetNode
private let placeholderAvatarsContext: AnimatedAvatarSetContext*/
private let buttonNode: HighlightTrackingButtonNode
private var pointerInteraction: PointerInteraction?
@ -2860,10 +2824,6 @@ private final class ChatMessageAuthorContextItemNode: ASDisplayNode, ContextMenu
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isAccessibilityElement = false
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isAccessibilityElement = false
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
self.highlightedBackgroundNode.alpha = 0.0
self.placeholderCalculationTextNode = ImmediateTextNode()
self.placeholderCalculationTextNode.attributedText = NSAttributedString(string: presentationData.strings.Conversation_ContextMenuSeen(11), font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
@ -2884,33 +2844,13 @@ private final class ChatMessageAuthorContextItemNode: ASDisplayNode, ContextMenu
self.buttonNode.isAccessibilityElement = true
self.buttonNode.accessibilityLabel = presentationData.strings.VoiceChat_StopRecording
/*self.avatarsNode = AnimatedAvatarSetNode()
self.avatarsContext = AnimatedAvatarSetContext()
self.placeholderAvatarsNode = AnimatedAvatarSetNode()
self.placeholderAvatarsContext = AnimatedAvatarSetContext()*/
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.highlightedBackgroundNode)
self.addSubnode(self.shimmerNode)
self.addSubnode(self.textNode)
/*self.addSubnode(self.avatarsNode)
self.addSubnode(self.placeholderAvatarsNode)*/
self.addSubnode(self.buttonNode)
self.buttonNode.highligthedChanged = { [weak self] highligted in
guard let strongSelf = self else {
return
}
if highligted {
strongSelf.highlightedBackgroundNode.alpha = 1.0
} else {
strongSelf.highlightedBackgroundNode.alpha = 0.0
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
}
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.buttonNode.isUserInteractionEnabled = false
@ -2933,14 +2873,8 @@ private final class ChatMessageAuthorContextItemNode: ASDisplayNode, ContextMenu
override func didLoad() {
super.didLoad()
self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: { [weak self] in
if let strongSelf = self {
strongSelf.highlightedBackgroundNode.alpha = 0.75
}
}, willExit: { [weak self] in
if let strongSelf = self {
strongSelf.highlightedBackgroundNode.alpha = 0.0
}
self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: {
}, willExit: {
})
}
@ -2960,7 +2894,7 @@ private final class ChatMessageAuthorContextItemNode: ASDisplayNode, ContextMenu
}
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
let sideInset: CGFloat = 14.0
let sideInset: CGFloat = 18.0
let verticalInset: CGFloat
let rightTextInset: CGFloat
//let avatarsWidth: CGFloat = 32.0
@ -3026,27 +2960,7 @@ private final class ChatMessageAuthorContextItemNode: ASDisplayNode, ContextMenu
self.shimmerNode.updateAbsoluteRect(self.shimmerNode.frame, within: size)
transition.updateAlpha(node: self.shimmerNode, alpha: self.peer == nil ? 1.0 : 0.0)
/*let avatarsContent: AnimatedAvatarSetContext.Content
let placeholderAvatarsContent: AnimatedAvatarSetContext.Content
var avatarsPeers: [EnginePeer] = []
if let peer = self.peer {
avatarsPeers = [peer]
}
avatarsContent = self.avatarsContext.update(peers: avatarsPeers, animated: false)
placeholderAvatarsContent = self.avatarsContext.updatePlaceholder(color: shimmeringForegroundColor, count: 1, animated: false)
let avatarsSize = self.avatarsNode.update(context: self.item.context, content: avatarsContent, itemSize: CGSize(width: 24.0, height: 24.0), customSpacing: 10.0, animated: false, synchronousLoad: true)
self.avatarsNode.frame = CGRect(origin: CGPoint(x: sideInset, y: floor((size.height - avatarsSize.height) / 2.0)), size: avatarsSize)
transition.updateAlpha(node: self.avatarsNode, alpha: self.peer == nil ? 0.0 : 1.0)
let placeholderAvatarsSize = self.placeholderAvatarsNode.update(context: self.item.context, content: placeholderAvatarsContent, itemSize: CGSize(width: 24.0, height: 24.0), customSpacing: 10.0, animated: false, synchronousLoad: true)
self.placeholderAvatarsNode.frame = CGRect(origin: CGPoint(x: self.avatarsNode.frame.minX, y: floor((size.height - placeholderAvatarsSize.height) / 2.0)), size: placeholderAvatarsSize)
transition.updateAlpha(node: self.placeholderAvatarsNode, alpha: self.peer == nil ? 1.0 : 0.0)*/
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
})
}
@ -3055,7 +2969,6 @@ private final class ChatMessageAuthorContextItemNode: ASDisplayNode, ContextMenu
self.presentationData = presentationData
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
@ -3103,11 +3016,6 @@ private final class ChatMessageAuthorContextItemNode: ASDisplayNode, ContextMenu
}
func setIsHighlighted(_ value: Bool) {
if value {
self.highlightedBackgroundNode.alpha = 1.0
} else {
self.highlightedBackgroundNode.alpha = 0.0
}
}
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
@ -3144,7 +3052,6 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
private let actionSelected: (ContextMenuActionResult) -> Void
private let backgroundNode: ASDisplayNode
private let highlightedBackgroundNode: ASDisplayNode
private let placeholderCalculationTextNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private var badgeBackground: UIImageView?
@ -3181,10 +3088,6 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isAccessibilityElement = false
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
self.highlightedBackgroundNode = ASDisplayNode()
self.highlightedBackgroundNode.isAccessibilityElement = false
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
self.highlightedBackgroundNode.alpha = 0.0
self.placeholderCalculationTextNode = ImmediateTextNode()
self.placeholderCalculationTextNode.attributedText = NSAttributedString(string: presentationData.strings.Conversation_ContextMenuSeen(11), font: textFont, textColor: presentationData.theme.contextMenu.primaryColor)
@ -3225,7 +3128,6 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
super.init()
self.addSubnode(self.backgroundNode)
self.addSubnode(self.highlightedBackgroundNode)
self.addSubnode(self.shimmerNode)
self.addSubnode(self.textNode)
self.addSubnode(self.iconNode)
@ -3233,17 +3135,6 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
self.addSubnode(self.placeholderAvatarsNode)
self.addSubnode(self.buttonNode)
self.buttonNode.highligthedChanged = { [weak self] highligted in
guard let strongSelf = self else {
return
}
if highligted {
strongSelf.highlightedBackgroundNode.alpha = 1.0
} else {
strongSelf.highlightedBackgroundNode.alpha = 0.0
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
}
}
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
var reactionCount = 0
@ -3338,14 +3229,8 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
override func didLoad() {
super.didLoad()
self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: { [weak self] in
if let strongSelf = self {
strongSelf.highlightedBackgroundNode.alpha = 0.75
}
}, willExit: { [weak self] in
if let strongSelf = self {
strongSelf.highlightedBackgroundNode.alpha = 0.0
}
self.pointerInteraction = PointerInteraction(node: self.buttonNode, style: .hover, willEnter: {
}, willExit: {
})
}
@ -3369,7 +3254,7 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
}
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
let sideInset: CGFloat = 14.0
let sideInset: CGFloat = 18.0
let verticalInset: CGFloat
let rightTextInset: CGFloat
@ -3377,7 +3262,7 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
verticalInset = 7.0
rightTextInset = 8.0
} else {
verticalInset = 12.0
verticalInset = 11.0
rightTextInset = sideInset + 36.0
}
@ -3534,11 +3419,7 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
let positionTransition: ContainedViewLayoutTransition = animatePositions ? transition : .immediate
let verticalOrigin = floor((size.height - combinedTextHeight) / 2.0)
var textFrame = CGRect(origin: CGPoint(x: sideInset + iconSize.width + 4.0, y: verticalOrigin), size: textSize)
if self.item.isEdit {
textFrame.origin.x -= 2.0
}
let textFrame = CGRect(origin: CGPoint(x: sideInset + 42.0, y: verticalOrigin), size: textSize)
positionTransition.updateFrameAdditive(node: self.textNode, frame: textFrame)
transition.updateAlpha(node: self.textNode, alpha: self.currentStats == nil ? 0.0 : 1.0)
@ -3583,9 +3464,9 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
transition.updateAlpha(node: self.shimmerNode, alpha: self.currentStats == nil ? 1.0 : 0.0)
if !iconSize.width.isZero {
var iconFrame = CGRect(origin: CGPoint(x: sideInset + 1.0, y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
var iconFrame = CGRect(origin: CGPoint(x: sideInset + 6.0, y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
if self.item.isEdit {
iconFrame.origin.x -= 2.0
iconFrame.origin.x += 1.0
}
transition.updateFrameAdditive(node: self.iconNode, frame: iconFrame)
}
@ -3622,15 +3503,14 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
}
let avatarsSize = self.avatarsNode.update(context: self.item.context, content: avatarsContent, itemSize: CGSize(width: 24.0, height: 24.0), customSpacing: 10.0, animated: false, synchronousLoad: true)
self.avatarsNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width - sideInset - 2.0 - avatarsSize.width), y: floor((size.height - avatarsSize.height) / 2.0)), size: avatarsSize)
self.avatarsNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(size.width - sideInset - 4.0 - avatarsSize.width), y: floor((size.height - avatarsSize.height) / 2.0)), size: avatarsSize)
transition.updateAlpha(node: self.avatarsNode, alpha: self.currentStats == nil ? 0.0 : 1.0)
let placeholderAvatarsSize = self.placeholderAvatarsNode.update(context: self.item.context, content: placeholderAvatarsContent, itemSize: CGSize(width: 24.0, height: 24.0), customSpacing: 10.0, animated: false, synchronousLoad: true)
self.placeholderAvatarsNode.frame = CGRect(origin: CGPoint(x: size.width - sideInset - 2.0 - placeholderAvatarsSize.width, y: floor((size.height - placeholderAvatarsSize.height) / 2.0)), size: placeholderAvatarsSize)
self.placeholderAvatarsNode.frame = CGRect(origin: CGPoint(x: size.width - sideInset - 4.0 - placeholderAvatarsSize.width, y: floor((size.height - placeholderAvatarsSize.height) / 2.0)), size: placeholderAvatarsSize)
transition.updateAlpha(node: self.placeholderAvatarsNode, alpha: self.currentStats == nil ? 1.0 : 0.0)
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
})
}
@ -3639,7 +3519,6 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
self.presentationData = presentationData
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.itemBackgroundColor
self.highlightedBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize)
@ -3695,11 +3574,6 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
}
func setIsHighlighted(_ value: Bool) {
if value {
self.highlightedBackgroundNode.alpha = 1.0
} else {
self.highlightedBackgroundNode.alpha = 0.0
}
}
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
@ -3819,7 +3693,7 @@ private final class ChatRateTranscriptionContextItemNode: ASDisplayNode, Context
}
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
let sideInset: CGFloat = 14.0
let sideInset: CGFloat = 18.0
let verticalInset: CGFloat = 9.0
let calculatedWidth = min(constrainedWidth, 250.0)

View file

@ -29,6 +29,7 @@ bool isViewPortalView(UIView * _Nonnull view);
UIView * _Nullable getPortalViewSourceView(UIView * _Nonnull portalView);
NSObject * _Nullable makeBlurFilter();
NSObject * _Nullable makeVariableBlurFilter();
NSObject * _Nullable makeLuminanceToAlphaFilter();
NSObject * _Nullable makeColorInvertFilter();
NSObject * _Nullable makeMonochromeFilter();

View file

@ -266,6 +266,10 @@ NSObject * _Nullable makeBlurFilter() {
return [(id<GraphicsFilterProtocol>)NSClassFromString(@"CAFilter") filterWithName:@"gaussianBlur"];
}
NSObject * _Nullable makeVariableBlurFilter() {
return [(id<GraphicsFilterProtocol>)NSClassFromString(@"CAFilter") filterWithName:@"variableBlur"];
}
NSObject * _Nullable makeLuminanceToAlphaFilter() {
return [(id<GraphicsFilterProtocol>)NSClassFromString(@"CAFilter") filterWithName:@"luminanceToAlpha"];
}

View file

@ -156,7 +156,7 @@ public struct WallpaperEdgeEffectEdge: Equatable {
}
public protocol WallpaperEdgeEffectNode: ASDisplayNode {
func update(rect: CGRect, edge: WallpaperEdgeEffectEdge, blur: Bool, containerSize: CGSize, transition: ContainedViewLayoutTransition)
func update(rect: CGRect, edge: WallpaperEdgeEffectEdge, alpha: CGFloat, blur: Bool, containerSize: CGSize, transition: ContainedViewLayoutTransition)
}
public protocol WallpaperBackgroundNode: ASDisplayNode {
@ -1507,7 +1507,6 @@ public final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgrou
if self.validPatternGeneratedImage != updatedGeneratedImage {
self.validPatternGeneratedImage = updatedGeneratedImage
if let cachedValidPatternImage = WallpaperBackgroundNodeImpl.cachedValidPatternImage, cachedValidPatternImage.generated == updatedGeneratedImage {
self.patternImageLayer.suspendCompositionUpdates = true
self.updatePatternPresentation()

View file

@ -12,12 +12,14 @@ final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEffectNode
private struct Params: Equatable {
let rect: CGRect
let edge: WallpaperEdgeEffectEdge
let alpha: CGFloat
let blur: Bool
let containerSize: CGSize
init(rect: CGRect, edge: WallpaperEdgeEffectEdge, blur: Bool, containerSize: CGSize) {
init(rect: CGRect, edge: WallpaperEdgeEffectEdge, alpha: CGFloat, blur: Bool, containerSize: CGSize) {
self.rect = rect
self.edge = edge
self.alpha = alpha
self.blur = blur
self.containerSize = containerSize
}
@ -58,6 +60,7 @@ final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEffectNode
self.containerNode.clipsToBounds = true
self.containerMaskingNode = ASDisplayNode()
self.containerMaskingNode.layer.allowsGroupOpacity = true
self.containerMaskingNode.addSubnode(self.containerNode)
self.overlayNode = ASDisplayNode()
@ -94,7 +97,7 @@ final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEffectNode
self.containerNode.insertSubnode(gradientNode, at: 0)
if let params = self.params {
self.updateImpl(rect: params.rect, edge: params.edge, blur: params.blur, containerSize: params.containerSize, transition: .immediate)
self.updateImpl(rect: params.rect, edge: params.edge, alpha: params.alpha, blur: params.blur, containerSize: params.containerSize, transition: .immediate)
}
}
} else {
@ -123,53 +126,27 @@ final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEffectNode
self.contentNode.isHidden = parentNode.contentNode.isHidden
}
func update(rect: CGRect, edge: WallpaperEdgeEffectEdge, blur: Bool, containerSize: CGSize, transition: ContainedViewLayoutTransition) {
let params = Params(rect: rect, edge: edge, blur: blur, containerSize: containerSize)
func update(rect: CGRect, edge: WallpaperEdgeEffectEdge, alpha: CGFloat, blur: Bool, containerSize: CGSize, transition: ContainedViewLayoutTransition) {
let params = Params(rect: rect, edge: edge, alpha: alpha, blur: blur, containerSize: containerSize)
if self.params != params {
self.params = params
self.updateImpl(rect: params.rect, edge: params.edge, blur: params.blur, containerSize: params.containerSize, transition: transition)
self.updateImpl(rect: params.rect, edge: params.edge, alpha: params.alpha, blur: params.blur, containerSize: params.containerSize, transition: transition)
}
}
private func updateImpl(rect: CGRect, edge: WallpaperEdgeEffectEdge, blur: Bool, containerSize: CGSize, transition: ContainedViewLayoutTransition) {
private func updateImpl(rect: CGRect, edge: WallpaperEdgeEffectEdge, alpha: CGFloat, blur: Bool, containerSize: CGSize, transition: ContainedViewLayoutTransition) {
transition.updateFrame(node: self.containerMaskingNode, frame: CGRect(origin: CGPoint(), size: rect.size))
transition.updateBounds(node: self.containerNode, bounds: CGRect(origin: CGPoint(x: rect.minX, y: rect.minY), size: rect.size))
if self.maskView.image?.size.height != edge.size {
let baseAlpha: CGFloat = 0.8
let expSteps = 6
let totalSteps = 18
let expEndValue: CGFloat = 0.6
var colors: [UIColor] = []
for i in 0 ..< expSteps {
let step = CGFloat(i) / CGFloat(expSteps - 1)
colors.append(UIColor(white: 1.0, alpha: bezierPoint(0.42, 0.0, 0.58, 1.0, step) * expEndValue))
}
for i in 0 ..< (totalSteps - expSteps) {
let step = CGFloat(i) / CGFloat((totalSteps - expSteps) - 1)
colors.append(UIColor(white: 1.0, alpha: expEndValue * (1.0 - step) + 1.0 * step))
}
let locations: [CGFloat] = (0 ..< colors.count).map { i in
return CGFloat(i) / CGFloat(colors.count - 1)
}
self.maskView.image = generateGradientImage(
size: CGSize(width: 8.0, height: edge.size),
colors: colors.map { $0.withMultipliedAlpha(baseAlpha) },
locations: locations
)?.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(edge.size))
self.maskView.image = EdgeEffectView.generateEdgeGradient(baseHeight: edge.size, isInverted: edge.edge == .bottom)
}
self.containerMaskingNode.alpha = alpha
let maskFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: rect.size)
ComponentTransition(transition).setPosition(view: self.maskView, position: maskFrame.center)
ComponentTransition(transition).setBounds(view: self.maskView, bounds: CGRect(origin: CGPoint(), size: maskFrame.size))
if case .top = edge.edge {
self.maskView.transform = CGAffineTransformMakeScale(1.0, -1.0)
} else {
self.maskView.transform = CGAffineTransformIdentity
}
transition.updateFrame(node: self.overlayNode, frame: CGRect(origin: CGPoint(), size: containerSize))
@ -181,45 +158,24 @@ final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEffectNode
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: containerSize))
if blur {
let blurHeight: CGFloat = max(edge.size, bounds.height - 10.0)
let blurFrame = CGRect(origin: CGPoint(x: 0.0, y: edge.edge == .bottom ? (bounds.height - blurHeight) : 0.0), size: CGSize(width: bounds.width, height: blurHeight))
let blurView: VariableBlurView
if let current = self.blurView {
blurView = current
} else {
let gradientMaskLayer = SimpleGradientLayer()
let baseGradientAlpha: CGFloat = 1.0
let numSteps = 8
let firstStep = 1
let firstLocation = 0.8
gradientMaskLayer.colors = (0 ..< numSteps).map { i in
if i < firstStep {
return UIColor(white: 1.0, alpha: 1.0).cgColor
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step)
return UIColor(white: 1.0, alpha: baseGradientAlpha * value).cgColor
}
}
gradientMaskLayer.locations = (0 ..< numSteps).map { i -> NSNumber in
if i < firstStep {
return 0.0 as NSNumber
} else {
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
return (firstLocation + (1.0 - firstLocation) * step) as NSNumber
}
}
blurView = VariableBlurView(gradientMask: self.maskView.image ?? UIImage(), maxBlurRadius: 8.0)
blurView.layer.mask = gradientMaskLayer
self.view.insertSubview(blurView, at: 0)
blurView = VariableBlurView(maxBlurRadius: 1.0)
self.view.addSubview(blurView)
self.blurView = blurView
}
blurView.update(size: bounds.size, transition: transition)
transition.updateFrame(view: blurView, frame: bounds)
if let maskLayer = blurView.layer.mask {
transition.updateFrame(layer: maskLayer, frame: bounds)
maskLayer.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
}
blurView.transform = self.maskView.transform
blurView.update(
size: blurFrame.size,
constantHeight: edge.size,
isInverted: edge.edge == .bottom,
gradient: EdgeEffectView.generateEdgeGradientData(baseHeight: edge.size),
transition: transition
)
transition.updateFrame(view: blurView, frame: blurFrame)
} else if let blurView = self.blurView {
self.blurView = nil
blurView.removeFromSuperview()

View file

@ -1,5 +1,5 @@
{
"app": "12.3.1",
"app": "12.3.2",
"xcode": "26.2",
"bazel": "8.4.2:45e9388abf21d1107e146ea366ad080eb93cb6a5f3a4a3b048f78de0bc3faffa",
"macos": "26"