diff --git a/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift b/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift index d6922664cd..dc7bbbb27a 100644 --- a/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift +++ b/submodules/Components/ReactionListContextMenuContent/Sources/ReactionListContextMenuContent.swift @@ -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? - 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)] = [:] diff --git a/submodules/ContextUI/BUILD b/submodules/ContextUI/BUILD index c65c236c99..a605ed5742 100644 --- a/submodules/ContextUI/BUILD +++ b/submodules/ContextUI/BUILD @@ -33,6 +33,7 @@ swift_library( "//submodules/Components/MultilineTextComponent", "//submodules/UIKitRuntimeUtils", "//submodules/TelegramUI/Components/EmojiStatusComponent", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift index 2618766169..f2ff84a502 100644 --- a/submodules/ContextUI/Sources/ContextActionsContainerNode.swift +++ b/submodules/ContextUI/Sources/ContextActionsContainerNode.swift @@ -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) diff --git a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift index 2bb3470ff8..e0c71fabe3 100644 --- a/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift +++ b/submodules/ContextUI/Sources/ContextControllerActionsStackNode.swift @@ -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, 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, with event: UIEvent) { + super.touchesEnded(touches, with: event) + + self.state = .ended + } + + public override func touchesCancelled(_ touches: Set, with event: UIEvent) { + super.touchesCancelled(touches, with: event) + + self.state = .cancelled + } + + public override func touchesMoved(_ touches: Set, 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 diff --git a/submodules/Display/Source/GenerateImage.swift b/submodules/Display/Source/GenerateImage.swift index 96c8ec0eb8..9ce05cc1d4 100644 --- a/submodules/Display/Source/GenerateImage.swift +++ b/submodules/Display/Source/GenerateImage.swift @@ -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()! diff --git a/submodules/Display/Source/SimpleLayer.swift b/submodules/Display/Source/SimpleLayer.swift index 7033bd1f85..8a79366074 100644 --- a/submodules/Display/Source/SimpleLayer.swift +++ b/submodules/Display/Source/SimpleLayer.swift @@ -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 + } +} diff --git a/submodules/Display/Source/UIKitUtils.swift b/submodules/Display/Source/UIKitUtils.swift index 23a90c4c9e..2253b63daa 100644 --- a/submodules/Display/Source/UIKitUtils.swift +++ b/submodules/Display/Source/UIKitUtils.swift @@ -881,6 +881,10 @@ public extension CALayer { return makeBlurFilter() } + static func variableBlur() -> NSObject? { + return makeVariableBlurFilter() + } + static func luminanceToAlpha() -> NSObject? { return makeLuminanceToAlphaFilter() } diff --git a/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift b/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift index fd95382431..76713dbe01 100644 --- a/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift +++ b/submodules/TelegramPresentationData/Sources/WallpaperUtils.swift @@ -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: diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift index 63ccc3c4cf..881982906f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift @@ -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)] { diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift index 4a9be3f3d0..9caa3e9d81 100644 --- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift +++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/ChatListNavigationBar.swift @@ -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 diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift index 9b62a00244..3eb7f3a3ba 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleComponent.swift @@ -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, diff --git a/submodules/TelegramUI/Components/EdgeEffect/BUILD b/submodules/TelegramUI/Components/EdgeEffect/BUILD index e5bc97ded7..7956ed68ae 100644 --- a/submodules/TelegramUI/Components/EdgeEffect/BUILD +++ b/submodules/TelegramUI/Components/EdgeEffect/BUILD @@ -13,6 +13,7 @@ swift_library( "//submodules/Display", "//submodules/ComponentFlow", "//submodules/Components/ComponentDisplayAdapters", + "//submodules/TelegramUI/Components/GlassBackgroundComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift b/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift index ecd4bc66e6..cad11e4241 100644 --- a/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift +++ b/submodules/TelegramUI/Components/EdgeEffect/Sources/EdgeEffect.swift @@ -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)) + } } } } diff --git a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift index b688f816d3..54a0ef4a41 100644 --- a/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift +++ b/submodules/TelegramUI/Components/GlassBackgroundComponent/Sources/LegacyGlassView.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index abb4967aed..f86312a7dd 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatControllerEditChat.swift b/submodules/TelegramUI/Sources/ChatControllerEditChat.swift index acec0b494f..270ae7b1ee 100644 --- a/submodules/TelegramUI/Sources/ChatControllerEditChat.swift +++ b/submodules/TelegramUI/Sources/ChatControllerEditChat.swift @@ -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, diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index fbbb87460f..5fd702eb1d 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index e19ccb1dd7..3fae86f473 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -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) diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h index 2efe921c88..4674f18208 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.h @@ -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(); diff --git a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m index 97de630895..37d54ca72e 100644 --- a/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m +++ b/submodules/UIKitRuntimeUtils/Source/UIKitRuntimeUtils/UIKitUtils.m @@ -266,6 +266,10 @@ NSObject * _Nullable makeBlurFilter() { return [(id)NSClassFromString(@"CAFilter") filterWithName:@"gaussianBlur"]; } +NSObject * _Nullable makeVariableBlurFilter() { + return [(id)NSClassFromString(@"CAFilter") filterWithName:@"variableBlur"]; +} + NSObject * _Nullable makeLuminanceToAlphaFilter() { return [(id)NSClassFromString(@"CAFilter") filterWithName:@"luminanceToAlpha"]; } diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift index 80b974c689..58c4626c55 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift @@ -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() diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperEdgeEffectNodeImpl.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperEdgeEffectNodeImpl.swift index 8898e7f8a1..e2b04a07d1 100644 --- a/submodules/WallpaperBackgroundNode/Sources/WallpaperEdgeEffectNodeImpl.swift +++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperEdgeEffectNodeImpl.swift @@ -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() diff --git a/versions.json b/versions.json index 08f8cf3a19..0f806a12b6 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "12.3.1", + "app": "12.3.2", "xcode": "26.2", "bazel": "8.4.2:45e9388abf21d1107e146ea366ad080eb93cb6a5f3a4a3b048f78de0bc3faffa", "macos": "26"