mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'temp-12'
This commit is contained in:
commit
76c3cb47c3
23 changed files with 1000 additions and 592 deletions
|
|
@ -22,9 +22,7 @@ private let avatarFont = avatarPlaceholderFont(size: 16.0)
|
|||
|
||||
public final class ReactionListContextMenuContent: ContextControllerItemsContent {
|
||||
private final class BackButtonNode: HighlightTrackingButtonNode {
|
||||
let highlightBackgroundNode: ASDisplayNode
|
||||
let titleLabelNode: ImmediateTextNode
|
||||
let separatorNode: ASDisplayNode
|
||||
let iconNode: ASImageNode
|
||||
|
||||
var action: (() -> Void)?
|
||||
|
|
@ -32,10 +30,6 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
private var theme: PresentationTheme?
|
||||
|
||||
init() {
|
||||
self.highlightBackgroundNode = ASDisplayNode()
|
||||
self.highlightBackgroundNode.isAccessibilityElement = false
|
||||
self.highlightBackgroundNode.alpha = 0.0
|
||||
|
||||
self.titleLabelNode = ImmediateTextNode()
|
||||
self.titleLabelNode.isAccessibilityElement = false
|
||||
self.titleLabelNode.maximumNumberOfLines = 1
|
||||
|
|
@ -44,31 +38,13 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
self.iconNode = ASImageNode()
|
||||
self.iconNode.isAccessibilityElement = false
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isAccessibilityElement = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.highlightBackgroundNode)
|
||||
self.addSubnode(self.titleLabelNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
|
||||
self.isAccessibilityElement = true
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if highlighted {
|
||||
strongSelf.highlightBackgroundNode.alpha = 1.0
|
||||
} else {
|
||||
let previousAlpha = strongSelf.highlightBackgroundNode.alpha
|
||||
strongSelf.highlightBackgroundNode.alpha = 0.0
|
||||
strongSelf.highlightBackgroundNode.layer.animateAlpha(from: previousAlpha, to: 0.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
|
|
@ -78,8 +54,8 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
|
||||
func update(size: CGSize, presentationData: PresentationData, isLast: Bool) {
|
||||
let standardIconWidth: CGFloat = 32.0
|
||||
let sideInset: CGFloat = 16.0
|
||||
let iconSideInset: CGFloat = 12.0
|
||||
let sideInset: CGFloat = 18.0
|
||||
let iconSideInset: CGFloat = 23.0
|
||||
|
||||
if self.theme !== presentationData.theme {
|
||||
self.theme = presentationData.theme
|
||||
|
|
@ -88,24 +64,16 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
self.accessibilityLabel = presentationData.strings.Common_Back
|
||||
}
|
||||
|
||||
self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
||||
self.separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
|
||||
|
||||
self.highlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
let titleFontSize = presentationData.listsFontSize.baseDisplaySize * 17.0 / 17.0
|
||||
|
||||
self.titleLabelNode.attributedText = NSAttributedString(string: presentationData.strings.Common_Back, font: Font.regular(titleFontSize), textColor: presentationData.theme.contextMenu.primaryColor)
|
||||
let titleSize = self.titleLabelNode.updateLayout(CGSize(width: size.width - sideInset - standardIconWidth, height: 100.0))
|
||||
self.titleLabelNode.frame = CGRect(origin: CGPoint(x: sideInset + 36.0, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
|
||||
self.titleLabelNode.frame = CGRect(origin: CGPoint(x: sideInset + 42.0, y: floor((size.height - titleSize.height) / 2.0)), size: titleSize)
|
||||
|
||||
if let iconImage = self.iconNode.image {
|
||||
let iconFrame = CGRect(origin: CGPoint(x: iconSideInset, y: floor((size.height - iconImage.size.height) / 2.0)), size: iconImage.size)
|
||||
self.iconNode.frame = iconFrame
|
||||
}
|
||||
|
||||
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height - UIScreenPixel), size: CGSize(width: size.width, height: UIScreenPixel))
|
||||
self.separatorNode.isHidden = isLast
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -405,13 +373,11 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
let availableReactions: AvailableReactions?
|
||||
let animationCache: AnimationCache
|
||||
let animationRenderer: MultiAnimationRenderer
|
||||
let highlightBackgroundNode: ASDisplayNode
|
||||
let avatarNode: AvatarNode
|
||||
let titleLabelNode: ImmediateTextNode
|
||||
let textLabelNode: ImmediateTextNode
|
||||
let readIconView: UIImageView
|
||||
var credibilityIconView: ComponentView<Empty>?
|
||||
let separatorNode: ASDisplayNode
|
||||
|
||||
private var reactionLayer: InlineStickerItemLayer?
|
||||
private var iconFrame: CGRect?
|
||||
|
|
@ -434,10 +400,6 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
self.avatarNode = AvatarNode(font: avatarFont)
|
||||
self.avatarNode.isAccessibilityElement = false
|
||||
|
||||
self.highlightBackgroundNode = ASDisplayNode()
|
||||
self.highlightBackgroundNode.isAccessibilityElement = false
|
||||
self.highlightBackgroundNode.alpha = 0.0
|
||||
|
||||
self.titleLabelNode = ImmediateTextNode()
|
||||
self.titleLabelNode.isAccessibilityElement = false
|
||||
self.titleLabelNode.maximumNumberOfLines = 1
|
||||
|
|
@ -450,33 +412,15 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
|
||||
self.readIconView = UIImageView(image: readIconImage)
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isAccessibilityElement = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.isAccessibilityElement = true
|
||||
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.highlightBackgroundNode)
|
||||
self.addSubnode(self.avatarNode)
|
||||
self.addSubnode(self.titleLabelNode)
|
||||
self.addSubnode(self.textLabelNode)
|
||||
self.view.addSubview(self.readIconView)
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if highlighted {
|
||||
strongSelf.highlightBackgroundNode.alpha = 1.0
|
||||
} else {
|
||||
let previousAlpha = strongSelf.highlightBackgroundNode.alpha
|
||||
strongSelf.highlightBackgroundNode.alpha = 0.0
|
||||
strongSelf.highlightBackgroundNode.layer.animateAlpha(from: previousAlpha, to: 0.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
|
|
@ -553,9 +497,9 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
}
|
||||
|
||||
func update(size: CGSize, presentationData: PresentationData, item: EngineMessageReactionListContext.Item, isLast: Bool, syncronousLoad: Bool) {
|
||||
let avatarInset: CGFloat = 12.0
|
||||
let avatarSpacing: CGFloat = 8.0
|
||||
let avatarSize: CGFloat = 28.0
|
||||
let avatarInset: CGFloat = 20.0
|
||||
let avatarSpacing: CGFloat = 10.0
|
||||
let avatarSize: CGFloat = 30.0
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
let reaction: MessageReaction.Reaction? = item.reaction
|
||||
|
|
@ -671,11 +615,6 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
additionalTitleInset += 3.0 + credibilityIconSize.width
|
||||
}
|
||||
|
||||
self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
||||
self.separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
|
||||
|
||||
self.highlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
self.avatarNode.frame = CGRect(origin: CGPoint(x: avatarInset, y: floor((size.height - avatarSize) / 2.0)), size: CGSize(width: avatarSize, height: avatarSize))
|
||||
self.avatarNode.setPeer(context: self.context, theme: presentationData.theme, peer: item.peer, synchronousLoad: true)
|
||||
|
||||
|
|
@ -765,9 +704,6 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
}
|
||||
reactionLayer.frame = iconFrame
|
||||
}
|
||||
|
||||
self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: size.height), size: CGSize(width: size.width, height: UIScreenPixel))
|
||||
self.separatorNode.isHidden = isLast
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -961,6 +897,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
|
||||
let heightFraction: CGFloat = presentationData.listsFontSize.baseDisplaySize / 17.0
|
||||
|
||||
let topInset: CGFloat = 5.0
|
||||
let itemHeight: CGFloat = (self.displayReadTimestamps ? 56.0 : 44.0) * heightFraction
|
||||
let visibleBounds = self.scrollNode.bounds.insetBy(dx: 0.0, dy: -180.0)
|
||||
|
||||
|
|
@ -972,7 +909,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
|
||||
if minVisibleIndex <= maxVisibleIndex {
|
||||
for index in minVisibleIndex ... maxVisibleIndex {
|
||||
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: CGFloat(index) * itemHeight), size: CGSize(width: size.width, height: itemHeight))
|
||||
let itemFrame = CGRect(origin: CGPoint(x: 0.0, y: topInset + CGFloat(index) * itemHeight), size: CGSize(width: size.width, height: itemHeight))
|
||||
|
||||
if let item = self.state.item(at: index) {
|
||||
validIds.insert(index)
|
||||
|
|
@ -1059,12 +996,13 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
func update(presentationData: PresentationData, constrainedSize: CGSize, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) -> (height: CGFloat, apparentHeight: CGFloat) {
|
||||
let heightFraction: CGFloat = presentationData.listsFontSize.baseDisplaySize / 17.0
|
||||
let itemHeight: CGFloat = (self.displayReadTimestamps ? 56.0 : 44.0) * heightFraction
|
||||
let topInset: CGFloat = 5.0
|
||||
|
||||
if self.presentationData?.theme !== presentationData.theme {
|
||||
let sideInset: CGFloat = 40.0
|
||||
let avatarInset: CGFloat = 12.0
|
||||
let avatarSpacing: CGFloat = 8.0
|
||||
let avatarSize: CGFloat = 28.0
|
||||
let avatarInset: CGFloat = 20.0
|
||||
let avatarSpacing: CGFloat = 10.0
|
||||
let avatarSize: CGFloat = 30.0
|
||||
let lineHeight: CGFloat = 8.0
|
||||
|
||||
let shimmeringForegroundColor: UIColor
|
||||
|
|
@ -1096,7 +1034,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
}
|
||||
self.presentationData = presentationData
|
||||
|
||||
let size = CGSize(width: constrainedSize.width, height: CGFloat(self.state.totalCount) * itemHeight)
|
||||
let size = CGSize(width: constrainedSize.width, height: topInset + CGFloat(self.state.totalCount) * itemHeight + topInset)
|
||||
|
||||
let containerSize = CGSize(width: size.width, height: min(constrainedSize.height, size.height))
|
||||
self.currentSize = containerSize
|
||||
|
|
@ -1311,6 +1249,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
let constrainedSize = CGSize(width: min(self.displayReadTimestamps ? 280.0 : 260.0, constrainedWidth), height: maxHeight)
|
||||
|
||||
var topContentHeight: CGFloat = 0.0
|
||||
topContentHeight += 6.0
|
||||
if let backButtonNode = self.backButtonNode {
|
||||
let backButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: topContentHeight), size: CGSize(width: constrainedSize.width, height: 44.0))
|
||||
backButtonNode.update(size: backButtonFrame.size, presentationData: self.presentationData, isLast: self.tabListNode == nil)
|
||||
|
|
@ -1325,10 +1264,10 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
|
|||
topContentHeight += tabListFrame.height
|
||||
}
|
||||
if let separatorNode = self.separatorNode {
|
||||
let separatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topContentHeight), size: CGSize(width: constrainedSize.width, height: 7.0))
|
||||
separatorNode.backgroundColor = self.presentationData.theme.contextMenu.sectionSeparatorColor
|
||||
let separatorFrame = CGRect(origin: CGPoint(x: 18.0, y: topContentHeight + 4.0), size: CGSize(width: max(0.0, constrainedSize.width - 18.0 * 2.0), height: 1.0))
|
||||
separatorNode.backgroundColor = self.presentationData.theme.contextMenu.itemSeparatorColor
|
||||
transition.updateFrame(node: separatorNode, frame: separatorFrame)
|
||||
topContentHeight += separatorFrame.height
|
||||
topContentHeight += 5.0
|
||||
}
|
||||
|
||||
var tabLayouts: [Int: (height: CGFloat, apparentHeight: CGFloat)] = [:]
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ swift_library(
|
|||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/UIKitRuntimeUtils",
|
||||
"//submodules/TelegramUI/Components/EmojiStatusComponent",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import AnimationCache
|
|||
import MultiAnimationRenderer
|
||||
import AnimationUI
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
import GlassBackgroundComponent
|
||||
import LottieComponent
|
||||
import TextNodeWithEntities
|
||||
|
||||
|
|
@ -102,7 +104,6 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||
private let requestUpdateAction: (AnyHashable, ContextMenuActionItem) -> Void
|
||||
private var item: ContextMenuActionItem
|
||||
|
||||
private let highlightBackgroundNode: ASDisplayNode
|
||||
private let titleLabelNode: ImmediateTextNodeWithEntities
|
||||
private let subtitleNode: ImmediateTextNode
|
||||
private let iconNode: ASImageNode
|
||||
|
|
@ -130,11 +131,6 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||
self.requestUpdateAction = requestUpdateAction
|
||||
self.item = item
|
||||
|
||||
self.highlightBackgroundNode = ASDisplayNode()
|
||||
self.highlightBackgroundNode.isAccessibilityElement = false
|
||||
self.highlightBackgroundNode.isUserInteractionEnabled = false
|
||||
self.highlightBackgroundNode.alpha = 0.0
|
||||
|
||||
self.titleLabelNode = ImmediateTextNodeWithEntities()
|
||||
self.titleLabelNode.isAccessibilityElement = false
|
||||
self.titleLabelNode.displaysAsynchronously = false
|
||||
|
|
@ -158,7 +154,6 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||
self.accessibilityLabel = item.text
|
||||
self.accessibilityTraits = [.button]
|
||||
|
||||
self.addSubnode(self.highlightBackgroundNode)
|
||||
self.addSubnode(self.titleLabelNode)
|
||||
self.addSubnode(self.subtitleNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
|
|
@ -171,10 +166,8 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||
return
|
||||
}
|
||||
if highlighted {
|
||||
strongSelf.highlightBackgroundNode.alpha = 1.0
|
||||
strongSelf.startTimer()
|
||||
} else {
|
||||
strongSelf.highlightBackgroundNode.alpha = 0.0
|
||||
strongSelf.invalidateTimer()
|
||||
}
|
||||
}
|
||||
|
|
@ -256,7 +249,6 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||
}
|
||||
|
||||
public func updateIsHighlighted(isHighlighted: Bool) {
|
||||
self.highlightBackgroundNode.alpha = isHighlighted ? 1.0 : 0.0
|
||||
}
|
||||
|
||||
public func performAction() {
|
||||
|
|
@ -279,14 +271,12 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||
}
|
||||
|
||||
public func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
let sideInset: CGFloat = 18.0
|
||||
let verticalInset: CGFloat = 11.0
|
||||
let titleSubtitleSpacing: CGFloat = 1.0
|
||||
let iconSideInset: CGFloat = 12.0
|
||||
let iconSideInset: CGFloat = 18.0 + 6.0
|
||||
let standardIconWidth: CGFloat = 32.0
|
||||
let iconSpacing: CGFloat = 8.0
|
||||
|
||||
self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
||||
|
||||
var forcedHeight: CGFloat?
|
||||
var titleVerticalOffset: CGFloat?
|
||||
|
|
@ -610,30 +600,24 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||
if let titleVerticalOffset {
|
||||
titleFrame = titleFrame.offsetBy(dx: 0.0, dy: titleVerticalOffset)
|
||||
}
|
||||
var subtitleFrame = CGRect(origin: CGPoint(x: sideInset, y: titleFrame.maxY + titleSubtitleSpacing), size: subtitleSize)
|
||||
var subtitleFrame = CGRect(origin: CGPoint(x: titleFrame.minX, y: titleFrame.maxY + titleSubtitleSpacing), size: subtitleSize)
|
||||
if self.item.additionalLeftIcon != nil {
|
||||
titleFrame = titleFrame.offsetBy(dx: 26.0, dy: 0.0)
|
||||
subtitleFrame = subtitleFrame.offsetBy(dx: 26.0, dy: 0.0)
|
||||
} else if self.item.iconPosition == .left {
|
||||
titleFrame = titleFrame.offsetBy(dx: 36.0, dy: 0.0)
|
||||
subtitleFrame = subtitleFrame.offsetBy(dx: 36.0, dy: 0.0)
|
||||
} else if iconSize != nil {
|
||||
titleFrame.origin.x = iconSideInset + 36.0
|
||||
subtitleFrame.origin.x = titleFrame.minX
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.highlightBackgroundNode, frame: CGRect(origin: CGPoint(), size: size), beginWithCurrentState: true)
|
||||
transition.updateFrameAdditive(node: self.titleLabelNode, frame: titleFrame)
|
||||
transition.updateFrameAdditive(node: self.subtitleNode, frame: subtitleFrame)
|
||||
|
||||
if let badgeIconNode = self.badgeIconNode {
|
||||
if let iconSize = badgeIconNode.image?.size {
|
||||
transition.updateFrame(node: badgeIconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 8.0, y: titleFrame.minY + floor((titleFrame.height - iconSize.height) * 0.5)), size: iconSize))
|
||||
}
|
||||
if let badgeIconNode = self.badgeIconNode, let iconSize = badgeIconNode.image?.size {
|
||||
transition.updateFrame(node: badgeIconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 8.0, y: titleFrame.minY + floor((titleFrame.height - iconSize.height) * 0.5)), size: iconSize))
|
||||
}
|
||||
|
||||
if let iconSize = iconSize {
|
||||
let iconWidth = max(standardIconWidth, iconSize.width)
|
||||
if let iconSize {
|
||||
let iconFrame = CGRect(
|
||||
origin: CGPoint(
|
||||
x: self.item.iconPosition == .left ? iconSideInset : size.width - iconSideInset - iconWidth + floor((iconWidth - iconSize.width) / 2.0),
|
||||
x: iconSideInset,
|
||||
y: floor((size.height - iconSize.height) / 2.0)
|
||||
),
|
||||
size: iconSize
|
||||
|
|
@ -660,16 +644,13 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||
}
|
||||
|
||||
if let additionalIconSize {
|
||||
var iconFrame = CGRect(
|
||||
let iconFrame = CGRect(
|
||||
origin: CGPoint(
|
||||
x: 10.0,
|
||||
y: floor((size.height - additionalIconSize.height) / 2.0)
|
||||
),
|
||||
size: additionalIconSize
|
||||
)
|
||||
if self.item.iconPosition == .left {
|
||||
iconFrame.origin.x = size.width - additionalIconSize.width - 10.0
|
||||
}
|
||||
transition.updateFrame(node: self.additionalIconNode, frame: iconFrame, beginWithCurrentState: true)
|
||||
}
|
||||
})
|
||||
|
|
@ -677,6 +658,8 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||
}
|
||||
|
||||
private final class ContextControllerActionsListSeparatorItemNode: ASDisplayNode, ContextControllerActionsListItemNode {
|
||||
private let separatorView: UIImageView
|
||||
|
||||
func canBeHighlighted() -> Bool {
|
||||
return false
|
||||
}
|
||||
|
|
@ -688,12 +671,23 @@ private final class ContextControllerActionsListSeparatorItemNode: ASDisplayNode
|
|||
}
|
||||
|
||||
override init() {
|
||||
self.separatorView = UIImageView()
|
||||
self.separatorView.image = generateImage(CGSize(width: 1.0, height: 1.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - 1.0) * 0.5)), size: CGSize(width: size.width, height: 1.0)))
|
||||
})?.withRenderingMode(.alwaysTemplate)
|
||||
|
||||
super.init()
|
||||
|
||||
self.view.addSubview(self.separatorView)
|
||||
}
|
||||
|
||||
func update(presentationData: PresentationData, constrainedSize: CGSize) -> (minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void) {
|
||||
return (minSize: CGSize(width: 0.0, height: 7.0), apply: { _, _ in
|
||||
self.backgroundColor = presentationData.theme.contextMenu.sectionSeparatorColor
|
||||
return (minSize: CGSize(width: 0.0, height: 10.0), apply: { size, transition in
|
||||
let sideInset: CGFloat = 18.0
|
||||
self.separatorView.tintColor = presentationData.theme.contextMenu.itemSeparatorColor
|
||||
transition.updateFrame(view: self.separatorView, frame: CGRect(origin: CGPoint(x: sideInset, y: floorToScreenPixels((size.height - 1.0) * 0.5)), size: CGSize(width: max(0.0, size.width - sideInset * 2.0), height: 1.0)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -777,13 +771,27 @@ private final class ContextControllerActionsListCustomItemNode: ASDisplayNode, C
|
|||
|
||||
public final class ContextControllerActionsListStackItem: ContextControllerActionsStackItem {
|
||||
final class Node: ASDisplayNode, ContextControllerActionsStackItemNode {
|
||||
private final class Params {
|
||||
let presentationData: PresentationData
|
||||
let constrainedSize: CGSize
|
||||
let standardMinWidth: CGFloat
|
||||
let standardMaxWidth: CGFloat
|
||||
let additionalBottomInset: CGFloat
|
||||
|
||||
init(presentationData: PresentationData, constrainedSize: CGSize, standardMinWidth: CGFloat, standardMaxWidth: CGFloat, additionalBottomInset: CGFloat) {
|
||||
self.presentationData = presentationData
|
||||
self.constrainedSize = constrainedSize
|
||||
self.standardMinWidth = standardMinWidth
|
||||
self.standardMaxWidth = standardMaxWidth
|
||||
self.additionalBottomInset = additionalBottomInset
|
||||
}
|
||||
}
|
||||
|
||||
private final class Item {
|
||||
let node: ContextControllerActionsListItemNode
|
||||
let separatorNode: ASDisplayNode?
|
||||
|
||||
init(node: ContextControllerActionsListItemNode, separatorNode: ASDisplayNode?) {
|
||||
init(node: ContextControllerActionsListItemNode) {
|
||||
self.node = node
|
||||
self.separatorNode = separatorNode
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -795,8 +803,11 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
|||
private var itemNodes: [Item]
|
||||
|
||||
private var hapticFeedback: HapticFeedback?
|
||||
|
||||
private let highlightedItemBackgroundView: UIView
|
||||
private var highlightedItemNode: Item?
|
||||
|
||||
private var params: Params?
|
||||
private var invalidatedItemNodes: Bool = false
|
||||
|
||||
var wantsFullWidth: Bool {
|
||||
|
|
@ -829,13 +840,11 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
|||
requestUpdateAction?(id, action)
|
||||
},
|
||||
item: actionItem
|
||||
),
|
||||
separatorNode: ASDisplayNode()
|
||||
)
|
||||
)
|
||||
case .separator:
|
||||
return Item(
|
||||
node: ContextControllerActionsListSeparatorItemNode(),
|
||||
separatorNode: nil
|
||||
node: ContextControllerActionsListSeparatorItemNode()
|
||||
)
|
||||
case let .custom(customItem, _):
|
||||
return Item(
|
||||
|
|
@ -843,19 +852,18 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
|||
getController: getController,
|
||||
item: customItem,
|
||||
requestDismiss: requestDismiss
|
||||
),
|
||||
separatorNode: ASDisplayNode()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
self.highlightedItemBackgroundView = UIView()
|
||||
self.highlightedItemBackgroundView.alpha = 0.0
|
||||
|
||||
super.init()
|
||||
|
||||
for item in self.itemNodes {
|
||||
if let separatorNode = item.separatorNode {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
}
|
||||
self.view.addSubview(self.highlightedItemBackgroundView)
|
||||
|
||||
for item in self.itemNodes {
|
||||
self.addSubnode(item.node)
|
||||
}
|
||||
|
|
@ -889,7 +897,6 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
|||
if currentAction.id == id {
|
||||
let previousNode = self.itemNodes[i]
|
||||
previousNode.node.removeFromSupernode()
|
||||
previousNode.separatorNode?.removeFromSupernode()
|
||||
|
||||
let addedNode = Item(
|
||||
node: ContextControllerActionsListActionItemNode(
|
||||
|
|
@ -903,13 +910,9 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
|||
self.requestUpdateAction(id: id, action: action)
|
||||
},
|
||||
item: action
|
||||
),
|
||||
separatorNode: ASDisplayNode()
|
||||
)
|
||||
)
|
||||
self.itemNodes[i] = addedNode
|
||||
if let separatorNode = addedNode.separatorNode {
|
||||
self.insertSubnode(separatorNode, at: 0)
|
||||
}
|
||||
self.addSubnode(addedNode.node)
|
||||
|
||||
self.requestUpdate(.immediate)
|
||||
|
|
@ -922,6 +925,19 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
|||
}
|
||||
}
|
||||
|
||||
private func update(transition: ContainedViewLayoutTransition) {
|
||||
if let params = self.params {
|
||||
let _ = self.update(
|
||||
presentationData: params.presentationData,
|
||||
constrainedSize: params.constrainedSize,
|
||||
standardMinWidth: params.standardMinWidth,
|
||||
standardMaxWidth: params.standardMaxWidth,
|
||||
additionalBottomInset: params.additionalBottomInset,
|
||||
transition: transition
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func update(
|
||||
presentationData: PresentationData,
|
||||
constrainedSize: CGSize,
|
||||
|
|
@ -930,11 +946,21 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
|||
additionalBottomInset: CGFloat,
|
||||
transition: ContainedViewLayoutTransition
|
||||
) -> (size: CGSize, apparentHeight: CGFloat) {
|
||||
self.params = Params(
|
||||
presentationData: presentationData,
|
||||
constrainedSize: constrainedSize,
|
||||
standardMinWidth: standardMinWidth,
|
||||
standardMaxWidth: standardMaxWidth,
|
||||
additionalBottomInset: additionalBottomInset
|
||||
)
|
||||
|
||||
var highlightedItemFrame: CGRect?
|
||||
|
||||
let verticalInset: CGFloat = 10.0
|
||||
|
||||
var itemNodeLayouts: [(minSize: CGSize, apply: (_ size: CGSize, _ transition: ContainedViewLayoutTransition) -> Void)] = []
|
||||
var combinedSize = CGSize()
|
||||
var combinedSize = CGSize(width: 0.0, height: verticalInset)
|
||||
for item in self.itemNodes {
|
||||
item.separatorNode?.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
|
||||
|
||||
let itemNodeLayout = item.node.update(
|
||||
presentationData: presentationData,
|
||||
constrainedSize: CGSize(width: standardMaxWidth, height: constrainedSize.height)
|
||||
|
|
@ -943,10 +969,11 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
|||
combinedSize.width = max(combinedSize.width, itemNodeLayout.minSize.width)
|
||||
combinedSize.height += itemNodeLayout.minSize.height
|
||||
}
|
||||
combinedSize.height += verticalInset
|
||||
self.invalidatedItemNodes = false
|
||||
combinedSize.width = max(combinedSize.width, standardMinWidth)
|
||||
|
||||
var nextItemOrigin = CGPoint()
|
||||
var nextItemOrigin = CGPoint(x: 0.0, y: verticalInset)
|
||||
for i in 0 ..< self.itemNodes.count {
|
||||
let item = self.itemNodes[i]
|
||||
let itemNodeLayout = itemNodeLayouts[i]
|
||||
|
|
@ -960,34 +987,38 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
|||
let itemFrame = CGRect(origin: nextItemOrigin, size: itemSize)
|
||||
itemTransition.updateFrame(node: item.node, frame: itemFrame, beginWithCurrentState: true)
|
||||
|
||||
if let separatorNode = item.separatorNode {
|
||||
itemTransition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: itemFrame.minX, y: itemFrame.maxY), size: CGSize(width: itemFrame.width, height: UIScreenPixel)), beginWithCurrentState: true)
|
||||
|
||||
var separatorHidden = false
|
||||
if i != self.itemNodes.count - 1 {
|
||||
switch self.items[i + 1] {
|
||||
case .separator:
|
||||
separatorHidden = true
|
||||
case .action:
|
||||
separatorHidden = false
|
||||
case .custom:
|
||||
separatorHidden = false
|
||||
}
|
||||
} else {
|
||||
separatorHidden = true
|
||||
}
|
||||
|
||||
if let itemContainerNode = item.node as? ContextControllerActionsListCustomItemNode, let itemNode = itemContainerNode.itemNode {
|
||||
if !itemNode.needsSeparator {
|
||||
separatorHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
separatorNode.isHidden = separatorHidden
|
||||
}
|
||||
|
||||
itemNodeLayout.apply(itemSize, itemTransition)
|
||||
nextItemOrigin.y += itemSize.height
|
||||
|
||||
if self.highlightedItemNode === item {
|
||||
highlightedItemFrame = itemFrame
|
||||
}
|
||||
}
|
||||
|
||||
if let highlightedItemFrame {
|
||||
self.highlightedItemBackgroundView.backgroundColor = presentationData.theme.overallDarkAppearance ? UIColor.white : UIColor.black
|
||||
self.highlightedItemBackgroundView.setMonochromaticEffect(tintColor: self.highlightedItemBackgroundView.backgroundColor)
|
||||
|
||||
var highlightTransition = ComponentTransition(transition)
|
||||
var animateIn = false
|
||||
if self.highlightedItemBackgroundView.alpha == 0.0 {
|
||||
if self.highlightedItemBackgroundView.layer.animation(forKey: "opacity") == nil {
|
||||
highlightTransition = .immediate
|
||||
}
|
||||
animateIn = true
|
||||
}
|
||||
let highlightFrame = CGRect(origin: CGPoint(x: 10.0, y: highlightedItemFrame.minY), size: CGSize(width: combinedSize.width - 10.0 * 2.0, height: highlightedItemFrame.height))
|
||||
highlightTransition.setFrame(view: self.highlightedItemBackgroundView, frame: highlightFrame)
|
||||
highlightTransition.setCornerRadius(layer: self.highlightedItemBackgroundView.layer, cornerRadius: min(20.0, highlightFrame.height * 0.5))
|
||||
if animateIn {
|
||||
var alphaTransition = transition
|
||||
if transition.isAnimated {
|
||||
alphaTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
}
|
||||
ComponentTransition(alphaTransition).setAlpha(view: self.highlightedItemBackgroundView, alpha: 0.1)
|
||||
}
|
||||
} else if self.highlightedItemBackgroundView.alpha != 0.0 {
|
||||
ComponentTransition(transition).setAlpha(view: self.highlightedItemBackgroundView, alpha: 0.0)
|
||||
}
|
||||
|
||||
return (combinedSize, combinedSize.height)
|
||||
|
|
@ -1012,6 +1043,8 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
|||
self.hapticFeedback = HapticFeedback()
|
||||
}
|
||||
self.hapticFeedback?.tap()
|
||||
|
||||
self.update(transition: .animated(duration: 0.16, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1022,6 +1055,8 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
|||
if performAction {
|
||||
highlightedItemNode.node.performAction()
|
||||
}
|
||||
|
||||
self.update(transition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1228,6 +1263,70 @@ func makeContextControllerActionsStackItem(items: ContextController.Items) -> [C
|
|||
}
|
||||
}
|
||||
|
||||
private final class ItemSelectionRecognizer: UIGestureRecognizer {
|
||||
var shouldBegin: (() -> Bool)?
|
||||
|
||||
private var initialLocation: CGPoint?
|
||||
private var currentLocation: CGPoint?
|
||||
|
||||
public override init(target: Any?, action: Selector?) {
|
||||
super.init(target: target, action: action)
|
||||
|
||||
self.delaysTouchesBegan = false
|
||||
self.delaysTouchesEnded = false
|
||||
}
|
||||
|
||||
public override func reset() {
|
||||
super.reset()
|
||||
|
||||
self.initialLocation = nil
|
||||
}
|
||||
|
||||
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
if let shouldBegin = self.shouldBegin, !shouldBegin() {
|
||||
self.state = .failed
|
||||
return
|
||||
}
|
||||
|
||||
if self.initialLocation == nil {
|
||||
self.initialLocation = touches.first?.location(in: self.view)
|
||||
}
|
||||
self.currentLocation = self.initialLocation
|
||||
|
||||
self.state = .began
|
||||
}
|
||||
|
||||
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
|
||||
self.state = .ended
|
||||
}
|
||||
|
||||
public override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesCancelled(touches, with: event)
|
||||
|
||||
self.state = .cancelled
|
||||
}
|
||||
|
||||
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
self.currentLocation = touches.first?.location(in: self.view)
|
||||
|
||||
self.state = .changed
|
||||
}
|
||||
|
||||
public func translation(in: UIView?) -> CGPoint {
|
||||
if let initialLocation = self.initialLocation, let currentLocation = self.currentLocation {
|
||||
return CGPoint(x: currentLocation.x - initialLocation.x, y: currentLocation.y - initialLocation.y)
|
||||
}
|
||||
return CGPoint()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
public enum Presentation {
|
||||
case modal
|
||||
|
|
@ -1236,8 +1335,9 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
final class NavigationContainer: ASDisplayNode, ASGestureRecognizerDelegate {
|
||||
let backgroundNode: NavigationBackgroundNode
|
||||
let parentShadowNode: ASImageNode
|
||||
let backgroundContainer: GlassBackgroundContainerView
|
||||
let backgroundView: GlassBackgroundView
|
||||
let contentContainer: UIView
|
||||
|
||||
var requestUpdate: ((ContainedViewLayoutTransition) -> Void)?
|
||||
var requestPop: (() -> Void)?
|
||||
|
|
@ -1252,16 +1352,18 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
override init() {
|
||||
self.backgroundNode = NavigationBackgroundNode(color: .clear, enableBlur: false)
|
||||
self.parentShadowNode = ASImageNode()
|
||||
self.parentShadowNode.image = UIImage(bundleImageName: "Components/Context Menu/Shadow")?.stretchableImage(withLeftCapWidth: 60, topCapHeight: 48)
|
||||
self.backgroundContainer = GlassBackgroundContainerView()
|
||||
self.backgroundView = GlassBackgroundView()
|
||||
self.backgroundContainer.contentView.addSubview(self.backgroundView)
|
||||
|
||||
self.contentContainer = UIView()
|
||||
self.contentContainer.clipsToBounds = true
|
||||
self.contentContainer.layer.cornerRadius = 30.0
|
||||
self.backgroundView.contentView.addSubview(self.contentContainer)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.cornerRadius = 14.0
|
||||
self.view.addSubview(self.backgroundContainer)
|
||||
|
||||
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { [weak self] point in
|
||||
guard let strongSelf = self else {
|
||||
|
|
@ -1316,18 +1418,32 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
func update(presentationData: PresentationData, presentation: Presentation, size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||
switch presentation {
|
||||
case .modal:
|
||||
self.backgroundNode.updateColor(color: presentationData.theme.contextMenu.backgroundColor, enableBlur: false, forceKeepBlur: false, transition: transition)
|
||||
self.parentShadowNode.isHidden = true
|
||||
case .inline:
|
||||
self.backgroundNode.updateColor(color: presentationData.theme.contextMenu.backgroundColor, enableBlur: true, forceKeepBlur: true, transition: transition)
|
||||
self.parentShadowNode.isHidden = false
|
||||
case .additional:
|
||||
self.backgroundNode.updateColor(color: presentationData.theme.contextMenu.backgroundColor.withMultipliedAlpha(0.5), enableBlur: true, forceKeepBlur: true, transition: transition)
|
||||
self.parentShadowNode.isHidden = false
|
||||
let transition = ComponentTransition(transition)
|
||||
|
||||
transition.setFrame(view: self.contentContainer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
var backgroundContainerSize = size
|
||||
if !transition.animation.isImmediate && (size.width < self.backgroundContainer.bounds.width || size.height < self.backgroundContainer.bounds.height) {
|
||||
backgroundContainerSize = CGSize(
|
||||
width: max(self.backgroundContainer.bounds.width, size.width),
|
||||
height: max(self.backgroundContainer.bounds.height, size.height)
|
||||
)
|
||||
}
|
||||
self.backgroundNode.update(size: size, transition: transition)
|
||||
|
||||
if self.backgroundContainer.bounds.size != size {
|
||||
self.backgroundContainer.update(size: backgroundContainerSize, isDark: presentationData.theme.overallDarkAppearance, transition: transition)
|
||||
|
||||
let isDark = presentationData.theme.overallDarkAppearance
|
||||
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size), completion: { [weak self] completed in
|
||||
guard let self, completed else {
|
||||
return
|
||||
}
|
||||
self.backgroundContainer.update(size: self.backgroundContainer.bounds.size, isDark: isDark, transition: .immediate)
|
||||
})
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.backgroundView.update(size: size, cornerRadius: 30.0, isDark: presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel, color: UIColor(white: presentationData.theme.overallDarkAppearance ? 0.0 : 1.0, alpha: 0.6)), isInteractive: false, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1510,7 +1626,7 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||
private var itemContainers: [ItemContainer] = []
|
||||
private var dismissingItemContainers: [(container: ItemContainer, isPopped: Bool)] = []
|
||||
|
||||
private var selectionPanGesture: UIPanGestureRecognizer?
|
||||
private var selectionPanGesture: ItemSelectionRecognizer?
|
||||
|
||||
public var topReactionItems: ContextControllerReactionItems? {
|
||||
return self.itemContainers.last?.reactionItems
|
||||
|
|
@ -1543,7 +1659,6 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.navigationContainer.parentShadowNode)
|
||||
self.addSubnode(self.navigationContainer)
|
||||
|
||||
self.navigationContainer.requestUpdate = { [weak self] transition in
|
||||
|
|
@ -1560,7 +1675,18 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||
strongSelf.pop()
|
||||
}
|
||||
|
||||
let selectionPanGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
||||
let selectionPanGesture = ItemSelectionRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
||||
selectionPanGesture.shouldBegin = { [weak self] in
|
||||
guard let self, let topItemContainer = self.itemContainers.last else {
|
||||
return false
|
||||
}
|
||||
|
||||
if topItemContainer.item is ContextControllerActionsCustomStackItem {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
self.selectionPanGesture = selectionPanGesture
|
||||
self.view.addGestureRecognizer(selectionPanGesture)
|
||||
selectionPanGesture.isEnabled = false
|
||||
|
|
@ -1568,7 +1694,7 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||
|
||||
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .changed:
|
||||
case .began, .changed:
|
||||
let location = recognizer.location(in: self.view)
|
||||
self.highlightGestureMoved(location: location)
|
||||
case .ended:
|
||||
|
|
@ -1661,7 +1787,7 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||
positionLock: positionLock
|
||||
)
|
||||
self.itemContainers.append(itemContainer)
|
||||
self.navigationContainer.addSubnode(itemContainer)
|
||||
self.navigationContainer.contentContainer.addSubview(itemContainer.view)
|
||||
self.navigationContainer.isNavigationEnabled = self.itemContainers.count > 1
|
||||
|
||||
let transition: ContainedViewLayoutTransition
|
||||
|
|
@ -1826,10 +1952,9 @@ public final class ContextControllerActionsStackNode: ASDisplayNode {
|
|||
}
|
||||
let previousNavigationContainerFrame = self.navigationContainer.frame
|
||||
transition.updateFrame(node: self.navigationContainer, frame: navigationContainerFrame, beginWithCurrentState: true)
|
||||
self.navigationContainer.update(presentationData: presentationData, presentation: presentation, size: navigationContainerFrame.size, transition: transition)
|
||||
|
||||
let navigationContainerShadowFrame = navigationContainerFrame.insetBy(dx: -30.0, dy: -30.0)
|
||||
transition.updateFrame(node: self.navigationContainer.parentShadowNode, frame: navigationContainerShadowFrame, beginWithCurrentState: true)
|
||||
if !navigationContainerFrame.isEmpty {
|
||||
self.navigationContainer.update(presentationData: presentationData, presentation: presentation, size: navigationContainerFrame.size, transition: transition)
|
||||
}
|
||||
|
||||
for i in 0 ..< self.itemContainers.count {
|
||||
let xOffset: CGFloat
|
||||
|
|
|
|||
|
|
@ -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()!
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -881,6 +881,10 @@ public extension CALayer {
|
|||
return makeBlurFilter()
|
||||
}
|
||||
|
||||
static func variableBlur() -> NSObject? {
|
||||
return makeVariableBlurFilter()
|
||||
}
|
||||
|
||||
static func luminanceToAlpha() -> NSObject? {
|
||||
return makeLuminanceToAlphaFilter()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)] {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ swift_library(
|
|||
"//submodules/Display",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -266,6 +266,10 @@ NSObject * _Nullable makeBlurFilter() {
|
|||
return [(id<GraphicsFilterProtocol>)NSClassFromString(@"CAFilter") filterWithName:@"gaussianBlur"];
|
||||
}
|
||||
|
||||
NSObject * _Nullable makeVariableBlurFilter() {
|
||||
return [(id<GraphicsFilterProtocol>)NSClassFromString(@"CAFilter") filterWithName:@"variableBlur"];
|
||||
}
|
||||
|
||||
NSObject * _Nullable makeLuminanceToAlphaFilter() {
|
||||
return [(id<GraphicsFilterProtocol>)NSClassFromString(@"CAFilter") filterWithName:@"luminanceToAlpha"];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"app": "12.3.1",
|
||||
"app": "12.3.2",
|
||||
"xcode": "26.2",
|
||||
"bazel": "8.4.2:45e9388abf21d1107e146ea366ad080eb93cb6a5f3a4a3b048f78de0bc3faffa",
|
||||
"macos": "26"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue