mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Glass
This commit is contained in:
parent
f444587197
commit
4f04dc0bb7
12 changed files with 466 additions and 381 deletions
|
|
@ -3259,8 +3259,12 @@ public final class ChatListNode: ListView {
|
|||
default:
|
||||
continue
|
||||
}
|
||||
if case let .index(index) = transition.chatListView.filteredEntries[entryCount - 1 - i].sortIndex, case let .chatList(chatListIndex) = index, chatListIndex.pinningIndex != nil {
|
||||
pinnedOverscroll = true
|
||||
if case let .index(index) = transition.chatListView.filteredEntries[entryCount - 1 - i].sortIndex {
|
||||
if case let .chatList(chatListIndex) = index, chatListIndex.pinningIndex != nil {
|
||||
pinnedOverscroll = true
|
||||
} else if case let .forum(pinnedIndex, _, _, _, _) = index, case .index = pinnedIndex {
|
||||
pinnedOverscroll = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,9 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/AvatarNode",
|
||||
"//submodules/PhotoResources",
|
||||
"//submodules/TelegramUI/Components/EdgeEffect",
|
||||
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
|
||||
"//submodules/TelegramUI/Components/GlassControls",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@ import TranslateUI
|
|||
import TelegramNotices
|
||||
import SolidRoundedButtonNode
|
||||
import UrlHandling
|
||||
import GlassControls
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
import EdgeEffect
|
||||
|
||||
private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: .white)
|
||||
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: .white)
|
||||
|
|
@ -132,14 +136,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
private var dateTimeFormat: PresentationDateTimeFormat
|
||||
|
||||
private let contentNode: ASDisplayNode
|
||||
private let deleteButton: UIButton
|
||||
private let fullscreenButton: UIButton
|
||||
private let actionButton: UIButton
|
||||
private let editButton: UIButton
|
||||
private let maskNode: ASDisplayNode
|
||||
private let textSelectionKnobContainer: UIView
|
||||
private let textSelectionKnobSurface: UIView
|
||||
private let scrollWrapperNode: CaptionScrollWrapperNode
|
||||
private let scrollWrapperEffect: VariableBlurEffect
|
||||
private let scrollNode: ASScrollNode
|
||||
|
||||
private let textNode: ImmediateTextNodeWithEntities
|
||||
|
|
@ -147,6 +148,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
private var dustNode: InvisibleInkDustNode?
|
||||
private var buttonNode: SolidRoundedButtonNode?
|
||||
private var buttonIconNode: ASImageNode?
|
||||
private let buttonPanel = ComponentView<Empty>()
|
||||
|
||||
private var textSelectionNode: TextSelectionNode?
|
||||
|
||||
|
|
@ -174,6 +176,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
private var videoFramePreviewNode: (ASImageNode, ImmediateTextNode)?
|
||||
|
||||
private var validLayout: (CGSize, LayoutMetrics, CGFloat, CGFloat, CGFloat, CGFloat)?
|
||||
private var buttonsState: (displayDeleteButton: Bool, displayFullscreenButton: Bool, displayActionButton: Bool, displayEditButton: Bool)?
|
||||
|
||||
private var codeHighlightState: (id: EngineMessage.Id, specs: [CachedMessageSyntaxHighlight.Spec], disposable: Disposable)?
|
||||
|
||||
|
|
@ -357,21 +360,16 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
|
||||
self.contentNode = ASDisplayNode()
|
||||
|
||||
self.deleteButton = UIButton()
|
||||
self.fullscreenButton = UIButton()
|
||||
self.actionButton = UIButton()
|
||||
self.editButton = UIButton()
|
||||
|
||||
self.deleteButton.setImage(deleteImage, for: [.normal])
|
||||
self.actionButton.setImage(actionImage, for: [.normal])
|
||||
self.editButton.setImage(editImage, for: [.normal])
|
||||
|
||||
self.textSelectionKnobContainer = UIView()
|
||||
self.textSelectionKnobSurface = UIView()
|
||||
self.textSelectionKnobContainer.addSubview(self.textSelectionKnobSurface)
|
||||
|
||||
self.scrollWrapperNode = CaptionScrollWrapperNode()
|
||||
self.scrollWrapperNode.layer.allowsGroupOpacity = true
|
||||
self.scrollWrapperNode.clipsToBounds = true
|
||||
self.scrollWrapperEffect = VariableBlurEffect(layer: self.scrollWrapperNode.layer, isTransparent: true, maxBlurRadius: 3.0)
|
||||
self.scrollWrapperNode.backgroundColor = .clear
|
||||
self.scrollWrapperNode.isOpaque = false
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
self.scrollNode.clipsToBounds = false
|
||||
|
|
@ -604,10 +602,6 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
|
||||
self.textSelectionNode = textSelectionNode
|
||||
|
||||
self.contentNode.view.addSubview(self.deleteButton)
|
||||
self.contentNode.view.addSubview(self.fullscreenButton)
|
||||
self.contentNode.view.addSubview(self.actionButton)
|
||||
self.contentNode.view.addSubview(self.editButton)
|
||||
self.contentNode.addSubnode(self.scrollWrapperNode)
|
||||
self.scrollWrapperNode.addSubnode(self.scrollNode)
|
||||
self.contentNode.view.addSubview(self.textSelectionKnobContainer)
|
||||
|
|
@ -626,7 +620,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
self.contentNode.addSubnode(self.statusNode)
|
||||
self.contentNode.addSubnode(self.statusButtonNode)
|
||||
|
||||
self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: [.touchUpInside])
|
||||
/*self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: [.touchUpInside])
|
||||
self.deleteButton.accessibilityTraits = [.button]
|
||||
self.deleteButton.accessibilityLabel = presentationData.strings.Gallery_VoiceOver_Delete
|
||||
|
||||
|
|
@ -640,7 +634,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
|
||||
self.editButton.addTarget(self, action: #selector(self.editButtonPressed), for: [.touchUpInside])
|
||||
self.editButton.accessibilityTraits = [.button]
|
||||
self.editButton.accessibilityLabel = presentationData.strings.Gallery_VoiceOver_Edit
|
||||
self.editButton.accessibilityLabel = presentationData.strings.Gallery_VoiceOver_Edit*/
|
||||
|
||||
self.backwardButton.addTarget(self, action: #selector(self.backwardButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.forwardButton.addTarget(self, action: #selector(self.forwardButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
|
@ -812,6 +806,15 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
dateText = nil
|
||||
}
|
||||
|
||||
if origin == nil {
|
||||
self.buttonsState = (
|
||||
displayDeleteButton: false,
|
||||
displayFullscreenButton: false,
|
||||
displayActionButton: false,
|
||||
displayEditButton: false
|
||||
)
|
||||
}
|
||||
|
||||
if self.currentMessageText != caption || self.currentAuthorNameText != titleText || self.currentDateText != dateText {
|
||||
self.currentMessageText = caption
|
||||
self.currentAuthorNameText = titleText
|
||||
|
|
@ -842,13 +845,6 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
|
||||
self.requestLayout?(.immediate)
|
||||
}
|
||||
|
||||
if origin == nil {
|
||||
self.editButton.isHidden = true
|
||||
self.deleteButton.isHidden = true
|
||||
self.fullscreenButton.isHidden = true
|
||||
self.editButton.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
func setMessage(_ message: Message, displayInfo: Bool = true, translateToLanguage: String? = nil, peerIsCopyProtected: Bool = false) {
|
||||
|
|
@ -1044,8 +1040,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
dateText = ""
|
||||
canEdit = false
|
||||
}
|
||||
|
||||
var displayDeleteButton = false
|
||||
var displayFullscreenButton = false
|
||||
var displayActionButton = false
|
||||
var displayEditButton = false
|
||||
|
||||
if self.currentMessageText != messageText || canDelete != !self.deleteButton.isHidden || canFullscreen != !self.fullscreenButton.isHidden || canShare != !self.actionButton.isHidden || canEdit != !self.editButton.isHidden || self.currentAuthorNameText != authorNameText || self.currentDateText != dateText {
|
||||
do {
|
||||
self.currentMessageText = messageText
|
||||
|
||||
if messageText.length == 0 {
|
||||
|
|
@ -1069,15 +1070,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
self.dateNode.accessibilityLabel = self.dateNode.attributedText?.string
|
||||
|
||||
if canFullscreen {
|
||||
self.fullscreenButton.isHidden = false
|
||||
self.deleteButton.isHidden = true
|
||||
displayFullscreenButton = true
|
||||
} else {
|
||||
self.deleteButton.isHidden = !canDelete
|
||||
self.fullscreenButton.isHidden = true
|
||||
displayDeleteButton = canDelete
|
||||
}
|
||||
|
||||
self.actionButton.isHidden = !canShare
|
||||
self.editButton.isHidden = !canEdit
|
||||
displayActionButton = canShare
|
||||
displayEditButton = canEdit
|
||||
|
||||
if let adAttribute = message.adAttribute {
|
||||
if self.buttonNode == nil {
|
||||
|
|
@ -1103,6 +1102,13 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
buttonNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
self.buttonsState = (
|
||||
displayDeleteButton: displayDeleteButton,
|
||||
displayFullscreenButton: displayFullscreenButton,
|
||||
displayActionButton: displayActionButton,
|
||||
displayEditButton: displayEditButton
|
||||
)
|
||||
|
||||
self.requestLayout?(.immediate)
|
||||
}
|
||||
}
|
||||
|
|
@ -1175,17 +1181,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
self.validLayout = (size, metrics, leftInset, rightInset, bottomInset, contentInset)
|
||||
|
||||
let width = size.width
|
||||
var bottomInset = bottomInset
|
||||
if !bottomInset.isZero && bottomInset < 30.0 {
|
||||
bottomInset -= 7.0
|
||||
}
|
||||
var panelHeight = 44.0 + bottomInset
|
||||
var panelHeight = 54.0 + bottomInset
|
||||
panelHeight += contentInset
|
||||
|
||||
let isLandscape = size.width > size.height
|
||||
|
||||
self.fullscreenButton.setImage(isLandscape ? fullscreenOffImage : fullscreenOnImage, for: [.normal])
|
||||
|
||||
let displayCaption: Bool
|
||||
if case .compact = metrics.widthClass {
|
||||
displayCaption = !self.textNode.isHidden && !isLandscape
|
||||
|
|
@ -1193,17 +1193,22 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
displayCaption = !self.textNode.isHidden
|
||||
}
|
||||
|
||||
if metrics.isTablet {
|
||||
self.fullscreenButton.isHidden = true
|
||||
var buttonPanelInsets = UIEdgeInsets()
|
||||
buttonPanelInsets.left = 8.0
|
||||
buttonPanelInsets.right = 8.0
|
||||
buttonPanelInsets.bottom = bottomInset + 8.0
|
||||
if bottomInset <= 32.0 {
|
||||
buttonPanelInsets.left += 18.0
|
||||
buttonPanelInsets.right += 18.0
|
||||
}
|
||||
|
||||
if !self.textNode.isHidden {
|
||||
var textFrame = CGRect()
|
||||
var visibleTextHeight: CGFloat = 0.0
|
||||
|
||||
let sideInset: CGFloat = 8.0 + leftInset
|
||||
let sideInset: CGFloat = 16.0 + leftInset
|
||||
let topInset: CGFloat = 8.0
|
||||
let textBottomInset: CGFloat = 8.0
|
||||
let textBottomInset: CGFloat = 8.0 + 14.0
|
||||
|
||||
let constrainSize = CGSize(width: width - sideInset * 2.0, height: CGFloat.greatestFiniteMagnitude)
|
||||
let textSize = self.textNode.updateLayout(constrainSize)
|
||||
|
|
@ -1233,26 +1238,52 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
maxTextOffset -= 44.0
|
||||
}
|
||||
textOffset = min(maxTextOffset, self.scrollNode.view.contentOffset.y)
|
||||
let originalPanelHeight = panelHeight
|
||||
panelHeight = max(0.0, panelHeight + visibleTextPanelHeight + textOffset)
|
||||
|
||||
if self.scrollNode.view.isScrollEnabled {
|
||||
if self.scrollWrapperNode.layer.mask == nil, let maskImage = captionMaskImage {
|
||||
let maskLayer = CALayer()
|
||||
maskLayer.contents = maskImage.cgImage
|
||||
maskLayer.contentsScale = maskImage.scale
|
||||
maskLayer.contentsCenter = CGRect(x: 0.0, y: 0.0, width: 1.0, height: (maskImage.size.height - 16.0) / maskImage.size.height)
|
||||
self.scrollWrapperNode.layer.mask = maskLayer
|
||||
if self.scrollWrapperNode.view.mask == nil {
|
||||
self.scrollWrapperNode.layer.rasterizationScale = UIScreenScale
|
||||
let maskView = UIImageView()
|
||||
|
||||
let height: CGFloat = 70.0
|
||||
let baseGradientAlpha: CGFloat = 1.0
|
||||
let numSteps = 8
|
||||
let firstStep = 0
|
||||
let firstLocation = 0.0
|
||||
let colors = (0 ..< numSteps).map { i -> UIColor 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 = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step)
|
||||
return UIColor(white: 0.0, alpha: baseGradientAlpha * value)
|
||||
}
|
||||
}
|
||||
let locations = (0 ..< numSteps).map { i -> CGFloat in
|
||||
if i < firstStep {
|
||||
return 0.0
|
||||
} else {
|
||||
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
|
||||
return (firstLocation + (1.0 - firstLocation) * step)
|
||||
}
|
||||
}
|
||||
|
||||
maskView.image = generateGradientImage(size: CGSize(width: 8.0, height: height), colors: colors, locations: locations)!.stretchableImage(withLeftCapWidth: 0, topCapHeight: 1)
|
||||
|
||||
//maskView.image = EdgeEffectView.generateEdgeGradient(baseHeight: originalPanelHeight + 20.0, isInverted: false)
|
||||
self.scrollWrapperNode.view.mask = maskView
|
||||
}
|
||||
} else {
|
||||
self.scrollWrapperNode.layer.mask = nil
|
||||
self.scrollWrapperNode.view.mask = nil
|
||||
}
|
||||
|
||||
let scrollWrapperNodeFrame = CGRect(x: 0.0, y: 0.0, width: width, height: max(0.0, visibleTextPanelHeight + textOffset))
|
||||
let scrollWrapperNodeFrame = CGRect(x: 0.0, y: 0.0, width: width, height: max(0.0, visibleTextPanelHeight + textOffset + originalPanelHeight - 14.0))
|
||||
if self.scrollWrapperNode.frame != scrollWrapperNodeFrame {
|
||||
self.scrollWrapperNode.frame = scrollWrapperNodeFrame
|
||||
self.scrollWrapperNode.layer.mask?.frame = self.scrollWrapperNode.bounds
|
||||
self.scrollWrapperNode.layer.mask?.removeAllAnimations()
|
||||
self.scrollWrapperNode.view.mask?.frame = self.scrollWrapperNode.bounds
|
||||
self.scrollWrapperNode.view.mask?.layer.removeAllAnimations()
|
||||
self.scrollWrapperEffect.update(size: scrollWrapperNodeFrame.size, constantHeight: 80.0, placement: VariableBlurEffect.Placement(position: .bottom, extendsInwards: true), gradient: EdgeEffectView.generateEdgeGradientData(baseHeight: 80.0), transition: transition)
|
||||
}
|
||||
|
||||
if let buttonNode = self.buttonNode {
|
||||
|
|
@ -1294,11 +1325,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
panelHeight += 34.0
|
||||
}
|
||||
|
||||
var scrubberY: CGFloat = 8.0
|
||||
var scrubberY: CGFloat = 0.0
|
||||
if self.textNode.isHidden || !displayCaption {
|
||||
panelHeight += 8.0
|
||||
} else {
|
||||
scrubberY = panelHeight - bottomInset - 44.0 - 44.0
|
||||
scrubberY = panelHeight - buttonPanelInsets.bottom - 44.0 - 44.0 - 8.0
|
||||
if contentInset > 0.0 {
|
||||
scrubberY -= contentInset
|
||||
}
|
||||
|
|
@ -1308,49 +1339,118 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
panelHeight -= 44.0
|
||||
}
|
||||
|
||||
let scrubberFrame = CGRect(origin: CGPoint(x: leftInset, y: scrubberY), size: CGSize(width: width - leftInset - rightInset, height: 34.0))
|
||||
scrubberView.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
let scrubberFrame = CGRect(origin: CGPoint(x: leftInset + 4.0, y: scrubberY), size: CGSize(width: width - (leftInset + 4.0) * 2.0, height: 34.0))
|
||||
scrubberView.updateLayout(size: size, leftInset: leftInset + 4.0, rightInset: rightInset, transition: .immediate)
|
||||
transition.updateBounds(layer: scrubberView.layer, bounds: CGRect(origin: CGPoint(), size: scrubberFrame.size))
|
||||
transition.updatePosition(layer: scrubberView.layer, position: CGPoint(x: scrubberFrame.midX, y: scrubberFrame.midY))
|
||||
}
|
||||
transition.updateAlpha(node: self.scrollWrapperNode, alpha: displayCaption ? 1.0 : 0.0)
|
||||
|
||||
self.actionButton.frame = CGRect(origin: CGPoint(x: leftInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
var leftControlItems: [GlassControlGroupComponent.Item] = []
|
||||
var rightControlItems: [GlassControlGroupComponent.Item] = []
|
||||
|
||||
let deleteFrame = CGRect(origin: CGPoint(x: width - 44.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
var editFrame = CGRect(origin: CGPoint(x: width - 44.0 - 50.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
if self.deleteButton.isHidden && self.fullscreenButton.isHidden {
|
||||
editFrame = deleteFrame
|
||||
if let buttonsState = self.buttonsState {
|
||||
if buttonsState.displayActionButton {
|
||||
leftControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("forward"),
|
||||
content: .icon("Chat/Input/Accessory Panels/MessageSelectionForward"),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.actionButtonPressed()
|
||||
}
|
||||
))
|
||||
}
|
||||
if buttonsState.displayEditButton {
|
||||
rightControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("edit"),
|
||||
content: .icon("Media Gallery/Draw"),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.editButtonPressed()
|
||||
}
|
||||
))
|
||||
}
|
||||
if buttonsState.displayFullscreenButton && !metrics.isTablet {
|
||||
rightControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("fullscreen"),
|
||||
content: .icon(isLandscape ? "Chat/Context Menu/Collapse" : "Chat/Context Menu/Expand"),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.fullscreenButtonPressed()
|
||||
}
|
||||
))
|
||||
}
|
||||
if buttonsState.displayDeleteButton {
|
||||
rightControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("delete"),
|
||||
content: .icon("Chat/Input/Accessory Panels/MessageSelectionTrash"),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.deleteButtonPressed()
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let buttonPanelSize = self.buttonPanel.update(
|
||||
transition: ComponentTransition(transition),
|
||||
component: AnyComponent(GlassControlPanelComponent(
|
||||
theme: defaultDarkColorPresentationTheme,
|
||||
leftItem: GlassControlPanelComponent.Item(
|
||||
items: leftControlItems,
|
||||
background: .panel
|
||||
),
|
||||
centralItem: nil,
|
||||
rightItem: GlassControlPanelComponent.Item(
|
||||
items: rightControlItems,
|
||||
background: .panel
|
||||
),
|
||||
centerAlignmentIfPossible: true
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: size.width - buttonPanelInsets.left - buttonPanelInsets.right, height: 44.0)
|
||||
)
|
||||
let buttonPanelFrame = CGRect(origin: CGPoint(x: buttonPanelInsets.left, y: panelHeight - buttonPanelInsets.bottom - buttonPanelSize.height), size: buttonPanelSize)
|
||||
if let buttonPanelView = self.buttonPanel.view {
|
||||
if buttonPanelView.superview == nil {
|
||||
self.contentNode.view.insertSubview(buttonPanelView, belowSubview: self.authorNameNode.view)
|
||||
}
|
||||
ComponentTransition(transition).setFrame(view: buttonPanelView, frame: buttonPanelFrame)
|
||||
}
|
||||
self.deleteButton.frame = deleteFrame
|
||||
self.fullscreenButton.frame = deleteFrame
|
||||
self.editButton.frame = editFrame
|
||||
|
||||
if let image = self.backwardButton.backgroundIconNode.image {
|
||||
self.backwardButton.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0) - 66.0, y: panelHeight - bottomInset - 44.0 + 7.0), size: image.size)
|
||||
self.backwardButton.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0) - 66.0, y: panelHeight - buttonPanelInsets.bottom - 44.0 + floorToScreenPixels((44.0 - image.size.height) * 0.5)), size: image.size)
|
||||
}
|
||||
if let image = self.forwardButton.backgroundIconNode.image {
|
||||
self.forwardButton.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0) + 66.0, y: panelHeight - bottomInset - 44.0 + 7.0), size: image.size)
|
||||
self.forwardButton.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0) + 66.0, y: panelHeight - buttonPanelInsets.bottom - 44.0 + floorToScreenPixels((44.0 - image.size.height) * 0.5)), size: image.size)
|
||||
}
|
||||
|
||||
self.playbackControlButton.frame = CGRect(origin: CGPoint(x: floor((width - 44.0) / 2.0), y: panelHeight - bottomInset - 44.0 - 2.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.playPauseIconNode.frame = self.playbackControlButton.bounds.offsetBy(dx: 2.0, dy: 2.0)
|
||||
self.playbackControlButton.frame = CGRect(origin: CGPoint(x: floor((width - 44.0) / 2.0), y: panelHeight - buttonPanelInsets.bottom - 44.0 + floorToScreenPixels((44.0 - 44.0) * 0.5)), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.playPauseIconNode.frame = self.playbackControlButton.bounds.offsetBy(dx: 2.0, dy: -2.0)
|
||||
|
||||
let statusSize = CGSize(width: 28.0, height: 28.0)
|
||||
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: floor((width - statusSize.width) / 2.0), y: panelHeight - bottomInset - statusSize.height - 8.0), size: statusSize))
|
||||
|
||||
self.statusButtonNode.frame = CGRect(origin: CGPoint(x: floor((width - 44.0) / 2.0), y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
|
||||
let buttonsSideInset: CGFloat = !self.editButton.isHidden ? 88.0 : 44.0
|
||||
let authorNameSize = self.authorNameNode.measure(CGSize(width: width - buttonsSideInset * 2.0 - 8.0 * 2.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude))
|
||||
let buttonsSideInset: CGFloat = 44.0 * CGFloat(max(leftControlItems.count, rightControlItems.count))
|
||||
let authorNameSize = self.authorNameNode.measure(CGSize(width: width - buttonsSideInset * 2.0 - 8.0 * 2.0 - buttonPanelInsets.left - buttonPanelInsets.right, height: CGFloat.greatestFiniteMagnitude))
|
||||
let dateSize = self.dateNode.measure(CGSize(width: width - buttonsSideInset * 2.0 - 8.0 * 2.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
|
||||
if authorNameSize.height.isZero {
|
||||
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height) / 2.0)), size: dateSize)
|
||||
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - buttonPanelInsets.bottom - 44.0 + floor((44.0 - dateSize.height) / 2.0)), size: dateSize)
|
||||
} else {
|
||||
let labelsSpacing: CGFloat = 0.0
|
||||
self.authorNameNode.frame = CGRect(origin: CGPoint(x: floor((width - authorNameSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0)), size: authorNameSize)
|
||||
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - bottomInset - 44.0 + floor((44.0 - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0) + authorNameSize.height + labelsSpacing), size: dateSize)
|
||||
self.authorNameNode.frame = CGRect(origin: CGPoint(x: floor((width - authorNameSize.width) / 2.0), y: panelHeight - buttonPanelInsets.bottom - 44.0 + floor((44.0 - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0)), size: authorNameSize)
|
||||
self.dateNode.frame = CGRect(origin: CGPoint(x: floor((width - dateSize.width) / 2.0), y: panelHeight - buttonPanelInsets.bottom - 44.0 + floor((44.0 - dateSize.height - authorNameSize.height - labelsSpacing) / 2.0) + authorNameSize.height + labelsSpacing), size: dateSize)
|
||||
}
|
||||
|
||||
if let (videoFramePreviewNode, videoFrameTextNode) = self.videoFramePreviewNode {
|
||||
|
|
@ -1365,7 +1465,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
if size.width > size.height {
|
||||
scrubberInset = 58.0
|
||||
} else {
|
||||
scrubberInset = 13.0
|
||||
scrubberInset = 14.0 + 8.0
|
||||
}
|
||||
|
||||
let imageSize = intrinsicImageSize.aspectFitted(fitSize)
|
||||
|
|
@ -1403,10 +1503,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
self.scrollWrapperNode.alpha = 1.0
|
||||
self.dateNode.alpha = 1.0
|
||||
self.authorNameNode.alpha = 1.0
|
||||
self.deleteButton.alpha = 1.0
|
||||
self.fullscreenButton.alpha = 1.0
|
||||
self.actionButton.alpha = 1.0
|
||||
self.editButton.alpha = 1.0
|
||||
if let buttonPanelView = self.buttonPanel.view {
|
||||
buttonPanelView.alpha = 1.0
|
||||
}
|
||||
|
||||
self.backwardButton.alpha = self.hasSeekControls ? 1.0 : 0.0
|
||||
self.forwardButton.alpha = self.hasSeekControls ? 1.0 : 0.0
|
||||
self.statusNode.alpha = 1.0
|
||||
|
|
@ -1432,10 +1532,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
self.scrollWrapperNode.alpha = 0.0
|
||||
self.dateNode.alpha = 0.0
|
||||
self.authorNameNode.alpha = 0.0
|
||||
self.deleteButton.alpha = 0.0
|
||||
self.fullscreenButton.alpha = 0.0
|
||||
self.actionButton.alpha = 0.0
|
||||
self.editButton.alpha = 0.0
|
||||
|
||||
if let buttonPanelView = self.buttonPanel.view {
|
||||
buttonPanelView.alpha = 0.0
|
||||
}
|
||||
|
||||
self.backwardButton.alpha = 0.0
|
||||
self.forwardButton.alpha = 0.0
|
||||
self.statusNode.alpha = 0.0
|
||||
|
|
|
|||
|
|
@ -140,11 +140,11 @@ public func internalDocumentItemSupportsMimeType(_ type: String, fileName: Strin
|
|||
return false
|
||||
}
|
||||
|
||||
private let textFont = Font.regular(16.0)
|
||||
private let boldFont = Font.bold(16.0)
|
||||
private let italicFont = Font.italic(16.0)
|
||||
private let boldItalicFont = Font.semiboldItalic(16.0)
|
||||
private let fixedFont = UIFont(name: "Menlo-Regular", size: 15.0) ?? textFont
|
||||
private let textFont = Font.regular(17.0)
|
||||
private let boldFont = Font.bold(17.0)
|
||||
private let italicFont = Font.italic(17.0)
|
||||
private let boldItalicFont = Font.semiboldItalic(17.0)
|
||||
private let fixedFont = UIFont(name: "Menlo-Regular", size: 17.0) ?? textFont
|
||||
|
||||
public func galleryCaptionStringWithAppliedEntities(context: AccountContext, text: String, entities: [MessageTextEntity], message: Message?, cachedMessageSyntaxHighlight: CachedMessageSyntaxHighlight? = nil) -> NSAttributedString {
|
||||
var baseQuoteSecondaryTintColor: UIColor?
|
||||
|
|
@ -576,8 +576,7 @@ private func galleryEntriesForMessageHistoryEntries(_ entries: [MessageHistoryEn
|
|||
}
|
||||
|
||||
public class GalleryController: ViewController, StandalonePresentableController, KeyShortcutResponder, GalleryControllerProtocol {
|
||||
public static let darkNavigationTheme = NavigationBarTheme(overallDarkAppearance: true, buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: UIColor(white: 0.0, alpha: 0.6), enableBackgroundBlur: false, separatorColor: UIColor(white: 0.0, alpha: 0.8), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear)
|
||||
public static let lightNavigationTheme = NavigationBarTheme(overallDarkAppearance: false, buttonColor: UIColor(rgb: 0x0088ff), disabledButtonColor: UIColor(rgb: 0xd0d0d0), primaryTextColor: .black, backgroundColor: UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0), enableBackgroundBlur: false, separatorColor: UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear)
|
||||
public static let darkNavigationTheme = NavigationBarTheme(overallDarkAppearance: true, buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: UIColor(white: 0.0, alpha: 0.6), enableBackgroundBlur: false, separatorColor: UIColor(white: 0.0, alpha: 0.8), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, edgeEffectColor: .black, style: .glass)
|
||||
|
||||
private var galleryNode: GalleryControllerNode {
|
||||
return self.displayNode as! GalleryControllerNode
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import AsyncDisplayKit
|
|||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
|
||||
public final class GalleryControllerInteraction {
|
||||
public let presentController: (ViewController, ViewControllerPresentationArguments?) -> Void
|
||||
|
|
@ -39,14 +41,14 @@ open class GalleryFooterContentNode: ASDisplayNode {
|
|||
|
||||
open func animateIn(transition: ContainedViewLayoutTransition) {
|
||||
self.alpha = 0.0
|
||||
transition.updateAlpha(node: self, alpha: 1.0)
|
||||
ComponentTransition(transition).setAlpha(view: self.view, alpha: 1.0)
|
||||
}
|
||||
|
||||
open func animateIn(fromHeight: CGFloat, previousContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition) {
|
||||
}
|
||||
|
||||
open func animateOut(transition: ContainedViewLayoutTransition) {
|
||||
transition.updateAlpha(node: self, alpha: 0.0)
|
||||
ComponentTransition(transition).setAlpha(view: self.view, alpha: 0.0)
|
||||
}
|
||||
|
||||
open func animateOut(toHeight: CGFloat, nextContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@ import Foundation
|
|||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import EdgeEffect
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
|
||||
public final class GalleryFooterNode: ASDisplayNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let edgeEffectView: EdgeEffectView
|
||||
|
||||
private var currentThumbnailPanelHeight: CGFloat?
|
||||
private var currentFooterContentNode: GalleryFooterContentNode?
|
||||
|
|
@ -16,25 +19,24 @@ public final class GalleryFooterNode: ASDisplayNode {
|
|||
public init(controllerInteraction: GalleryControllerInteraction) {
|
||||
self.controllerInteraction = controllerInteraction
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.backgroundColor = UIColor(white: 0.0, alpha: 0.6)
|
||||
self.edgeEffectView = EdgeEffectView()
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.view.addSubview(self.edgeEffectView)
|
||||
}
|
||||
|
||||
private var visibilityAlpha: CGFloat = 1.0
|
||||
public func setVisibilityAlpha(_ alpha: CGFloat, animated: Bool) {
|
||||
self.visibilityAlpha = alpha
|
||||
self.backgroundNode.alpha = alpha
|
||||
self.edgeEffectView.alpha = alpha
|
||||
self.currentFooterContentNode?.setVisibilityAlpha(alpha, animated: true)
|
||||
self.currentOverlayContentNode?.setVisibilityAlpha(alpha)
|
||||
}
|
||||
|
||||
func animateIn(transition: ContainedViewLayoutTransition) {
|
||||
self.backgroundNode.alpha = 0.0
|
||||
transition.updateAlpha(node: self.backgroundNode, alpha: 1.0)
|
||||
self.edgeEffectView.alpha = 0.0
|
||||
ComponentTransition(transition).setAlpha(view: self.edgeEffectView, alpha: 1.0)
|
||||
|
||||
if let currentFooterContentNode = self.currentFooterContentNode {
|
||||
currentFooterContentNode.animateIn(transition: transition)
|
||||
|
|
@ -47,7 +49,7 @@ public final class GalleryFooterNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
func animateOut(transition: ContainedViewLayoutTransition) {
|
||||
transition.updateAlpha(node: self.backgroundNode, alpha: 0.0)
|
||||
ComponentTransition(transition).setAlpha(view: self.edgeEffectView, alpha: 0.0)
|
||||
|
||||
if let currentFooterContentNode = self.currentFooterContentNode {
|
||||
currentFooterContentNode.animateOut(transition: transition)
|
||||
|
|
@ -104,6 +106,8 @@ public final class GalleryFooterNode: ASDisplayNode {
|
|||
}
|
||||
var backgroundHeight: CGFloat = 0.0
|
||||
let verticalOffset: CGFloat = isHidden ? (layout.size.width > layout.size.height ? 44.0 : (effectiveThumbnailPanelHeight > 0.0 ? 106.0 : 54.0)) : 0.0
|
||||
let backgroundFrame: CGRect
|
||||
var edgeEffectTransition = ComponentTransition(transition)
|
||||
if let footerContentNode = self.currentFooterContentNode {
|
||||
backgroundHeight = footerContentNode.updateLayout(size: layout.size, metrics: layout.metrics, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: cleanInsets.bottom, contentInset: effectiveThumbnailPanelHeight, transition: transition)
|
||||
transition.updateFrame(node: footerContentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight)))
|
||||
|
|
@ -115,18 +119,26 @@ public final class GalleryFooterNode: ASDisplayNode {
|
|||
dismissedCurrentFooterContentNode.removeFromSupernode()
|
||||
}
|
||||
})
|
||||
contentTransition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight)))
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight))
|
||||
edgeEffectTransition = ComponentTransition(contentTransition)
|
||||
} else {
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight)))
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight))
|
||||
}
|
||||
} else {
|
||||
if let dismissedCurrentFooterContentNode = dismissedCurrentFooterContentNode {
|
||||
dismissedCurrentFooterContentNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight)))
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight))
|
||||
}
|
||||
|
||||
var edgeEffectFrame = backgroundFrame
|
||||
let edgeEffectHeight: CGFloat = 46.0
|
||||
edgeEffectFrame.origin.y -= edgeEffectHeight
|
||||
edgeEffectFrame.size.height += edgeEffectHeight
|
||||
edgeEffectTransition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
|
||||
self.edgeEffectView.update(content: .black, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(90.0, edgeEffectFrame.height), transition: edgeEffectTransition)
|
||||
|
||||
let contentTransition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||
if let overlayContentNode = self.currentOverlayContentNode {
|
||||
let insets = UIEdgeInsets(top: navigationBarHeight, left: layout.safeInsets.left, bottom: isHidden ? layout.intrinsicInsets.bottom : backgroundHeight, right: layout.safeInsets.right)
|
||||
|
|
@ -158,7 +170,7 @@ public final class GalleryFooterNode: ASDisplayNode {
|
|||
if let overlayResult = self.currentOverlayContentNode?.hitTest(point, with: event) {
|
||||
return overlayResult
|
||||
}
|
||||
if !self.backgroundNode.frame.contains(point) || self.visibilityAlpha < 1.0 {
|
||||
if !self.edgeEffectView.frame.contains(point) || self.visibilityAlpha < 1.0 {
|
||||
return nil
|
||||
}
|
||||
let result = super.hitTest(point, with: event)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ import Pasteboard
|
|||
import AdUI
|
||||
import AdsInfoScreen
|
||||
import AdsReportScreen
|
||||
import GlassBackgroundComponent
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
|
||||
enum ChatMediaGalleryThumbnail: Equatable {
|
||||
case image(ImageMediaReference)
|
||||
|
|
@ -756,7 +759,13 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
guard let controller = self.baseNavigationController()?.topViewController as? ViewController else {
|
||||
return
|
||||
}
|
||||
let contextController = makeContextController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.moreBarButton.referenceNode, actionsOnTop: false)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
||||
|
||||
var sourceView = self.moreBarButton.referenceNode.view
|
||||
if let value = self.galleryController()?.navigationBar?.navigationButtonContextContainer(sourceView: sourceView) {
|
||||
sourceView = value
|
||||
}
|
||||
|
||||
let contextController = makeContextController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, actionsOnTop: false)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
|
|
@ -1544,11 +1553,9 @@ private class RecognizedContentContainer: ASDisplayNode {
|
|||
|
||||
|
||||
private class ImageRecognitionOverlayContentNode: GalleryOverlayContentNode {
|
||||
private let backgroundNode: ASImageNode
|
||||
private let selectedBackgroundNode: ASImageNode
|
||||
private let iconNode: ASImageNode
|
||||
private let selectedIconNode: ASImageNode
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private let backgroundContainer: GlassBackgroundContainerView
|
||||
private let backgroundView: GlassBackgroundView
|
||||
private let iconView: UIImageView
|
||||
|
||||
var action: ((Bool) -> Void)?
|
||||
private var appeared = false
|
||||
|
|
@ -1556,128 +1563,111 @@ private class ImageRecognitionOverlayContentNode: GalleryOverlayContentNode {
|
|||
private var validLayout: (CGSize, LayoutMetrics, UIEdgeInsets)?
|
||||
private var interfaceIsHidden: Bool = false
|
||||
|
||||
private var isSelected: Bool = false
|
||||
|
||||
init(theme: PresentationTheme) {
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundContainer = GlassBackgroundContainerView()
|
||||
self.backgroundView = GlassBackgroundView()
|
||||
self.iconView = UIImageView()
|
||||
|
||||
self.selectedBackgroundNode = ASImageNode()
|
||||
self.selectedBackgroundNode.displaysAsynchronously = false
|
||||
self.selectedBackgroundNode.isHidden = true
|
||||
self.selectedBackgroundNode.image = generateFilledCircleImage(diameter: 32.0, color: .white)
|
||||
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
self.buttonNode.alpha = 0.0
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/LiveTextIcon"), color: .white)
|
||||
self.iconNode.contentMode = .center
|
||||
|
||||
self.selectedIconNode = ASImageNode()
|
||||
self.selectedIconNode.displaysAsynchronously = false
|
||||
self.selectedIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/LiveTextIcon"), color: .black)
|
||||
self.selectedIconNode.contentMode = .center
|
||||
self.selectedIconNode.isHidden = true
|
||||
self.iconView.image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/LiveTextIcon"), color: .white)?.withRenderingMode(.alwaysTemplate)
|
||||
self.iconView.contentMode = .center
|
||||
|
||||
super.init()
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
self.addSubnode(self.buttonNode)
|
||||
self.buttonNode.addSubnode(self.backgroundNode)
|
||||
self.buttonNode.addSubnode(self.selectedBackgroundNode)
|
||||
self.buttonNode.addSubnode(self.iconNode)
|
||||
self.buttonNode.addSubnode(self.selectedIconNode)
|
||||
|
||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.iconNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.iconNode.alpha = 0.4
|
||||
} else {
|
||||
strongSelf.iconNode.alpha = 1.0
|
||||
strongSelf.iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.backgroundContainer.contentView.addSubview(self.backgroundView)
|
||||
self.backgroundView.contentView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.onTapGesture(_:))))
|
||||
|
||||
self.view.addSubview(self.backgroundContainer)
|
||||
self.backgroundView.contentView.addSubview(self.iconView)
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
let newValue = !self.buttonNode.isSelected
|
||||
self.buttonNode.isSelected = newValue
|
||||
self.selectedBackgroundNode.isHidden = !newValue
|
||||
self.selectedIconNode.isHidden = !newValue
|
||||
|
||||
if !newValue && !self.interfaceIsHidden, let (size, metrics, insets) = self.validLayout {
|
||||
self.updateLayout(size: size, metrics: metrics, insets: insets, isHidden: self.interfaceIsHidden, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
}
|
||||
|
||||
self.action?(newValue)
|
||||
|
||||
if self.interfaceIsHidden && !newValue {
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.2, curve: .easeInOut)
|
||||
transition.updateAlpha(node: self.buttonNode, alpha: 0.0)
|
||||
@objc private func onTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
let newValue = !self.isSelected
|
||||
self.isSelected = newValue
|
||||
|
||||
if !newValue && !self.interfaceIsHidden, let (size, metrics, insets) = self.validLayout {
|
||||
self.updateLayout(size: size, metrics: metrics, insets: insets, isHidden: self.interfaceIsHidden, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
}
|
||||
|
||||
self.action?(newValue)
|
||||
|
||||
if self.interfaceIsHidden && !newValue {
|
||||
let transition = ComponentTransition.easeInOut(duration: 0.2)
|
||||
transition.setAlpha(view: self.backgroundContainer, alpha: 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func transitionIn() {
|
||||
guard self.buttonNode.alpha.isZero else {
|
||||
guard self.backgroundContainer.alpha.isZero else {
|
||||
return
|
||||
}
|
||||
self.appeared = true
|
||||
self.buttonNode.alpha = 1.0
|
||||
self.buttonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
ComponentTransition.easeInOut(duration: 0.2).setAlpha(view: self.backgroundContainer, alpha: 1.0)
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, metrics: LayoutMetrics, insets: UIEdgeInsets, isHidden: Bool, transition: ContainedViewLayoutTransition) {
|
||||
let transition = ComponentTransition(transition)
|
||||
|
||||
self.validLayout = (size, metrics, insets)
|
||||
|
||||
self.interfaceIsHidden = isHidden
|
||||
|
||||
let buttonSize = CGSize(width: 32.0, height: 32.0)
|
||||
self.backgroundNode.frame = CGRect(origin: CGPoint(x: 12.0, y: 12.0), size: buttonSize)
|
||||
self.selectedBackgroundNode.frame = CGRect(origin: CGPoint(x: 12.0, y: 12.0), size: buttonSize)
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: 12.0, y: 12.0), size: buttonSize)
|
||||
self.selectedIconNode.frame = CGRect(origin: CGPoint(x: 12.0, y: 12.0), size: buttonSize)
|
||||
let buttonSize = CGSize(width: 44.0, height: 44.0)
|
||||
|
||||
self.backgroundContainer.update(size: buttonSize, isDark: true, transition: transition)
|
||||
|
||||
self.backgroundView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: buttonSize)
|
||||
let tintColor: GlassBackgroundView.TintColor
|
||||
if self.isSelected {
|
||||
tintColor = .init(kind: .custom, color: UIColor(white: 1.0, alpha: 1.0))
|
||||
} else {
|
||||
tintColor = .init(kind: .panel, color: UIColor(white: 0.0, alpha: 0.6))
|
||||
}
|
||||
self.backgroundView.update(size: buttonSize, cornerRadius: buttonSize.height * 0.5, isDark: true, tintColor: tintColor, isInteractive: true, transition: transition)
|
||||
|
||||
self.iconView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: buttonSize)
|
||||
transition.setTintColor(view: self.iconView, color: self.isSelected ? .black : .white)
|
||||
|
||||
if self.appeared {
|
||||
if !self.buttonNode.isSelected && isHidden {
|
||||
transition.updateAlpha(node: self.buttonNode, alpha: 0.0)
|
||||
if !self.isSelected && isHidden {
|
||||
transition.setAlpha(view: self.backgroundContainer, alpha: 0.0)
|
||||
} else {
|
||||
transition.updateAlpha(node: self.buttonNode, alpha: 1.0)
|
||||
transition.setAlpha(view: self.backgroundContainer, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
var buttonPosition: CGPoint
|
||||
if isHidden && !self.buttonNode.isSelected {
|
||||
buttonPosition = CGPoint(x: size.width - insets.right - buttonSize.width - 59.0, y: -50.0)
|
||||
if isHidden && !self.isSelected {
|
||||
buttonPosition = CGPoint(x: size.width - insets.right - buttonSize.width - 66.0 - 10.0, y: -52.0)
|
||||
} else {
|
||||
buttonPosition = CGPoint(x: size.width - insets.right - buttonSize.width - (self.buttonNode.isSelected ? 24.0 : 59.0), y: insets.top - 58.0)
|
||||
buttonPosition = CGPoint(x: size.width - insets.right - buttonSize.width - (self.isSelected ? 24.0 : 70.0), y: insets.top - 50.0)
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: buttonPosition, size: CGSize(width: buttonSize.width + 24.0, height: buttonSize.height + 24.0)))
|
||||
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: buttonPosition, size: buttonSize))
|
||||
}
|
||||
|
||||
override func animateIn(previousContentNode: GalleryOverlayContentNode?, transition: ContainedViewLayoutTransition) {
|
||||
guard self.appeared && (!self.interfaceIsHidden || self.buttonNode.isSelected) else {
|
||||
guard self.appeared && (!self.interfaceIsHidden || self.isSelected) else {
|
||||
return
|
||||
}
|
||||
self.buttonNode.alpha = 1.0
|
||||
if let previousContentNode = previousContentNode as? ImageRecognitionOverlayContentNode, previousContentNode.appeared {
|
||||
|
||||
self.backgroundContainer.alpha = 1.0
|
||||
} else {
|
||||
self.buttonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
self.backgroundContainer.alpha = 0.0
|
||||
ComponentTransition.easeInOut(duration: 0.2).setAlpha(view: self.backgroundContainer, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
override func animateOut(nextContentNode: GalleryOverlayContentNode?, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
let previousAlpha = self.buttonNode.alpha
|
||||
self.buttonNode.alpha = 0.0
|
||||
self.buttonNode.layer.animateAlpha(from: previousAlpha, to: 0.0, duration: 0.2)
|
||||
ComponentTransition.easeInOut(duration: 0.2).setAlpha(view: self.backgroundContainer, alpha: 0.0)
|
||||
completion()
|
||||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
if self.buttonNode.alpha > 0.0 && self.buttonNode.frame.contains(point) {
|
||||
if self.backgroundContainer.alpha > 0.0 && self.backgroundContainer.frame.contains(point) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -434,14 +434,18 @@ func optionsBackgroundImage(dark: Bool) -> UIImage? {
|
|||
}
|
||||
|
||||
func optionsCircleImage(dark: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: 22.0, height: 22.0), contextGenerator: { size, context in
|
||||
return generateImage(CGSize(width: 44.0, height: 44.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setStrokeColor(UIColor.white.cgColor)
|
||||
let lineWidth: CGFloat = 1.3
|
||||
context.setLineWidth(lineWidth)
|
||||
|
||||
context.strokeEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: lineWidth, dy: lineWidth))
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
|
||||
let spacing: CGFloat = 3.66
|
||||
let width: CGFloat = 4.33
|
||||
for i in 0 ..< 3 {
|
||||
let x: CGFloat = floorToScreenPixels((size.width - width * 3.0 - spacing * 2.0) * 0.5) + CGFloat(i) * (width + spacing)
|
||||
let y: CGFloat = floorToScreenPixels((size.height - width) * 0.5)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(x: x, y: y), size: CGSize(width: width, height: width)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -491,7 +495,6 @@ final class MoreHeaderButton: HighlightableButtonNode {
|
|||
let referenceNode: ContextReferenceContentNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
private let iconNode: ASImageNode
|
||||
private var animationNode: AnimationNode?
|
||||
|
||||
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
|
||||
|
|
@ -527,29 +530,17 @@ final class MoreHeaderButton: HighlightableButtonNode {
|
|||
strongSelf.contextAction?(strongSelf.containerNode, gesture)
|
||||
}
|
||||
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 26.0, height: 44.0))
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.referenceNode.frame = self.containerNode.bounds
|
||||
|
||||
self.iconNode.image = optionsCircleImage(dark: false)
|
||||
if let image = self.iconNode.image {
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size)
|
||||
}
|
||||
|
||||
self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -4.0, bottom: 0.0, right: -4.0)
|
||||
}
|
||||
|
||||
private var content: Content?
|
||||
func setContent(_ content: Content, animated: Bool = false) {
|
||||
if case .more = content, self.animationNode == nil {
|
||||
let iconColor = UIColor(rgb: 0xffffff)
|
||||
let animationNode = AnimationNode(animation: "anim_profilemore", colors: ["Point 2.Group 1.Fill 1": iconColor,
|
||||
"Point 3.Group 1.Fill 1": iconColor,
|
||||
"Point 1.Group 1.Fill 1": iconColor], scale: 1.0)
|
||||
let animationSize = CGSize(width: 22.0, height: 22.0)
|
||||
animationNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - animationSize.width) / 2.0), y: floor((self.containerNode.bounds.height - animationSize.height) / 2.0)), size: animationSize)
|
||||
self.addSubnode(animationNode)
|
||||
self.animationNode = animationNode
|
||||
}
|
||||
if animated {
|
||||
if let snapshotView = self.referenceNode.view.snapshotContentTree() {
|
||||
snapshotView.frame = self.referenceNode.frame
|
||||
|
|
@ -562,9 +553,6 @@ final class MoreHeaderButton: HighlightableButtonNode {
|
|||
|
||||
self.iconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.iconNode.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3)
|
||||
|
||||
self.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.animationNode?.layer.animateScale(from: 0.1, to: 1.0, duration: 0.3)
|
||||
}
|
||||
|
||||
switch content {
|
||||
|
|
@ -575,7 +563,6 @@ final class MoreHeaderButton: HighlightableButtonNode {
|
|||
|
||||
self.iconNode.image = image
|
||||
self.iconNode.isHidden = false
|
||||
self.animationNode?.isHidden = true
|
||||
case let .more(image):
|
||||
if let image = image {
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size)
|
||||
|
|
@ -583,7 +570,6 @@ final class MoreHeaderButton: HighlightableButtonNode {
|
|||
|
||||
self.iconNode.image = image
|
||||
self.iconNode.isHidden = false
|
||||
self.animationNode?.isHidden = false
|
||||
}
|
||||
} else {
|
||||
self.content = content
|
||||
|
|
@ -595,7 +581,6 @@ final class MoreHeaderButton: HighlightableButtonNode {
|
|||
|
||||
self.iconNode.image = image
|
||||
self.iconNode.isHidden = false
|
||||
self.animationNode?.isHidden = true
|
||||
case let .more(image):
|
||||
if let image = image {
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - image.size.width) / 2.0), y: floor((self.containerNode.bounds.height - image.size.height) / 2.0)), size: image.size)
|
||||
|
|
@ -603,7 +588,6 @@ final class MoreHeaderButton: HighlightableButtonNode {
|
|||
|
||||
self.iconNode.image = image
|
||||
self.iconNode.isHidden = false
|
||||
self.animationNode?.isHidden = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -614,14 +598,13 @@ final class MoreHeaderButton: HighlightableButtonNode {
|
|||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
return CGSize(width: wide ? 32.0 : 22.0, height: 44.0)
|
||||
return CGSize(width: 44.0, height: 44.0)
|
||||
}
|
||||
|
||||
func onLayout() {
|
||||
}
|
||||
|
||||
func play() {
|
||||
self.animationNode?.playOnce()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -688,10 +671,8 @@ final class SettingsHeaderButton: HighlightableButtonNode {
|
|||
strongSelf.contextAction?(strongSelf.containerNode, gesture)
|
||||
}
|
||||
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 26.0, height: 44.0))
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.referenceNode.frame = self.containerNode.bounds
|
||||
|
||||
self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -4.0, bottom: 0.0, right: -4.0)
|
||||
|
||||
if let image = self.gearsLayer.image {
|
||||
let iconInnerInsets = UIEdgeInsets(top: 4.0, left: 8.0, bottom: 4.0, right: 6.0)
|
||||
|
|
@ -725,7 +706,7 @@ final class SettingsHeaderButton: HighlightableButtonNode {
|
|||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
return CGSize(width: wide ? 32.0 : 22.0, height: 44.0)
|
||||
return CGSize(width: 44.0, height: 44.0)
|
||||
}
|
||||
|
||||
func onLayout() {
|
||||
|
|
@ -3254,7 +3235,14 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
})
|
||||
}
|
||||
|
||||
let contextController = makeContextController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: sourceNode, actionsOnTop: actionsOnTop)), items: items |> map { items in
|
||||
var sourceView = sourceNode.view
|
||||
if !isSettings {
|
||||
if let value = self.galleryController()?.navigationBar?.navigationButtonContextContainer(sourceView: sourceView) {
|
||||
sourceView = value
|
||||
}
|
||||
}
|
||||
|
||||
let contextController = makeContextController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, actionsOnTop: actionsOnTop)), items: items |> map { items in
|
||||
if !items.topItems.isEmpty {
|
||||
return ContextController.Items(id: AnyHashable(0), content: .twoLists(items.items, items.topItems))
|
||||
} else {
|
||||
|
|
@ -4087,17 +4075,17 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
|
||||
final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceNode: ContextReferenceContentNode
|
||||
private let sourceView: UIView
|
||||
private let actionsOnTop: Bool
|
||||
|
||||
init(controller: ViewController, sourceNode: ContextReferenceContentNode, actionsOnTop: Bool) {
|
||||
init(controller: ViewController, sourceView: UIView, actionsOnTop: Bool) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
self.sourceView = sourceView
|
||||
self.actionsOnTop = actionsOnTop
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceNode.view, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: self.actionsOnTop ? .top : .bottom)
|
||||
return ContextControllerReferenceViewInfo(referenceView: self.sourceView, contentAreaInScreenSpace: UIScreen.main.bounds, actionsPosition: self.actionsOnTop ? .top : .bottom)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ public class EdgeEffectView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
public static func generateEdgeGradientData(baseHeight: CGFloat) -> VariableBlurView.Gradient {
|
||||
public static func generateEdgeGradientData(baseHeight: CGFloat) -> VariableBlurEffect.Gradient {
|
||||
let gradientColors: [CGFloat] = [
|
||||
0.8470588235294118,
|
||||
0.8431372549019608,
|
||||
|
|
@ -267,14 +267,14 @@ public class EdgeEffectView: UIView {
|
|||
1.0,
|
||||
]
|
||||
|
||||
return VariableBlurView.Gradient(
|
||||
return VariableBlurEffect.Gradient(
|
||||
height: baseHeight,
|
||||
alpha: gradientColors.map { $0 / gradientColorNorm },
|
||||
positions: gradientLocations
|
||||
)
|
||||
}
|
||||
|
||||
public static func generateEdgeGradient(baseHeight: CGFloat, isInverted: Bool) -> UIImage {
|
||||
public static func generateEdgeGradient(baseHeight: CGFloat, isInverted: Bool, extendsInwards: Bool = false) -> UIImage {
|
||||
let gradientColors: [CGFloat] = [
|
||||
0.8470588235294118,
|
||||
0.8431372549019608,
|
||||
|
|
@ -463,12 +463,18 @@ public class EdgeEffectView: UIView {
|
|||
1.0,
|
||||
]
|
||||
|
||||
var resizingInverted = isInverted
|
||||
if extendsInwards {
|
||||
resizingInverted = false
|
||||
}
|
||||
let _ = resizingInverted
|
||||
|
||||
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)
|
||||
)!.resizableImage(withCapInsets: UIEdgeInsets(top: resizingInverted ? baseHeight : 0.0, left: 0.0, bottom: resizingInverted ? 0.0 : baseHeight, right: 0.0), resizingMode: .stretch)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -535,17 +541,17 @@ public final class EdgeEffectComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
public final class VariableBlurView: UIView {
|
||||
public final class VariableBlurEffect {
|
||||
private struct Params: Equatable {
|
||||
let size: CGSize
|
||||
let constantHeight: CGFloat
|
||||
let isInverted: Bool
|
||||
let placement: Placement
|
||||
let gradient: Gradient
|
||||
|
||||
init(size: CGSize, constantHeight: CGFloat, isInverted: Bool, gradient: Gradient) {
|
||||
init(size: CGSize, constantHeight: CGFloat, placement: Placement, gradient: Gradient) {
|
||||
self.size = size
|
||||
self.constantHeight = constantHeight
|
||||
self.isInverted = isInverted
|
||||
self.placement = placement
|
||||
self.gradient = gradient
|
||||
}
|
||||
}
|
||||
|
|
@ -578,156 +584,61 @@ public final class VariableBlurView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
public let maxBlurRadius: CGFloat
|
||||
public struct Placement: Equatable {
|
||||
public enum Position {
|
||||
case top
|
||||
case bottom
|
||||
}
|
||||
|
||||
public let position: Position
|
||||
public let extendsInwards: Bool
|
||||
|
||||
public init(position: Position, extendsInwards: Bool) {
|
||||
self.position = position
|
||||
self.extendsInwards = extendsInwards
|
||||
}
|
||||
}
|
||||
|
||||
private let effectLayerDelegate: SimpleLayerDelegate
|
||||
private var mainEffectLayer: CALayer?
|
||||
private var additionalEffectLayer: CALayer?
|
||||
private let layer: CALayer
|
||||
private let isTransparent: Bool
|
||||
private let maxBlurRadius: CGFloat
|
||||
|
||||
private var params: Params?
|
||||
private var gradientImage: UIImage?
|
||||
private var isAnimating: Bool = false
|
||||
|
||||
private let imageSubview: UIImageView
|
||||
private let imageSubview: UIImageView?
|
||||
|
||||
public init(maxBlurRadius: CGFloat = 20.0) {
|
||||
public init(layer: CALayer, isTransparent: Bool = false, maxBlurRadius: CGFloat = 20.0) {
|
||||
self.layer = layer
|
||||
self.isTransparent = isTransparent
|
||||
self.maxBlurRadius = maxBlurRadius
|
||||
|
||||
self.effectLayerDelegate = SimpleLayerDelegate()
|
||||
|
||||
self.mainEffectLayer = createBackdropLayer()
|
||||
if #available(iOS 26.0, *) {
|
||||
if let mainEffectLayer = self.mainEffectLayer, let variableBlur = CALayer.variableBlur() {
|
||||
let imageSubview = UIImageView()
|
||||
self.imageSubview = imageSubview
|
||||
imageSubview.layer.name = "mask_source"
|
||||
|
||||
if let variableBlur = CALayer.variableBlur() {
|
||||
variableBlur.setValue(self.maxBlurRadius, forKey: "inputRadius")
|
||||
variableBlur.setValue("mask_source", forKey: "inputSourceSublayerName")
|
||||
variableBlur.setValue(true, forKey: "inputNormalizeEdges")
|
||||
mainEffectLayer.filters = [variableBlur]
|
||||
|
||||
/*#if DEBUG
|
||||
if let classValue = NSClassFromString("CAFilter") as AnyObject as? NSObjectProtocol {
|
||||
let makeSelector = NSSelectorFromString("filterWithName:")
|
||||
let filter = classValue.perform(makeSelector, with: "colorMatrix").takeUnretainedValue() as? NSObject
|
||||
|
||||
if let filter {
|
||||
/// Builds a 4x5 (RGBA) color matrix (20 floats, row-major).
|
||||
/// Applies: Desaturate(s) then TintToward(t, k) in linear RGB.
|
||||
/// - s: saturation in [0,1] (1 = unchanged, 0 = grayscale)
|
||||
/// - k: tint strength in [0,1] (0 = none, 1 = fully "brightness->tint" mapping)
|
||||
/// - t: tint color (linear RGB). Doesn't have to be normalized, but should be non-negative.
|
||||
/// - bias: optional constant "cast" added in the bias column, scaled by tint color.
|
||||
let makeDesatTintMatrix: (Float32, Float32, (Float32, Float32, Float32), Float32) -> [Float32] = { s, k, t, bias in
|
||||
// Rec.709 luma weights (good default for linear RGB)
|
||||
let wr: Float32 = 0.2126
|
||||
let wg: Float32 = 0.7152
|
||||
let wb: Float32 = 0.0722
|
||||
|
||||
let (tr, tg, tb) = t
|
||||
|
||||
// --- D = desaturation matrix ---
|
||||
let a = 1 - s
|
||||
|
||||
let D00 = wr * a + s, D01 = wg * a, D02 = wb * a
|
||||
let D10 = wr * a, D11 = wg * a + s, D12 = wb * a
|
||||
let D20 = wr * a, D21 = wg * a, D22 = wb * a + s
|
||||
|
||||
// --- T = outer(t, w): maps luma to tinted RGB (preserves luma per weights) ---
|
||||
let T00 = tr * wr, T01 = tr * wg, T02 = tr * wb
|
||||
let T10 = tg * wr, T11 = tg * wg, T12 = tg * wb
|
||||
let T20 = tb * wr, T21 = tb * wg, T22 = tb * wb
|
||||
|
||||
// --- A = (1-k)I + kT ---
|
||||
let ik = 1 - k
|
||||
|
||||
let A00 = ik + k * T00, A01 = k * T01, A02 = k * T02
|
||||
let A10 = k * T10, A11 = ik + k * T11, A12 = k * T12
|
||||
let A20 = k * T20, A21 = k * T21, A22 = ik + k * T22
|
||||
|
||||
// --- M = A * D (3x3 multiply) ---
|
||||
let M00 = A00 * D00 + A01 * D10 + A02 * D20
|
||||
let M01 = A00 * D01 + A01 * D11 + A02 * D21
|
||||
let M02 = A00 * D02 + A01 * D12 + A02 * D22
|
||||
|
||||
let M10 = A10 * D00 + A11 * D10 + A12 * D20
|
||||
let M11 = A10 * D01 + A11 * D11 + A12 * D21
|
||||
let M12 = A10 * D02 + A11 * D12 + A12 * D22
|
||||
|
||||
let M20 = A20 * D00 + A21 * D10 + A22 * D20
|
||||
let M21 = A20 * D01 + A21 * D11 + A22 * D21
|
||||
let M22 = A20 * D02 + A21 * D12 + A22 * D22
|
||||
|
||||
// Optional constant cast in bias column
|
||||
let bR = bias * tr
|
||||
let bG = bias * tg
|
||||
let bB = bias * tb
|
||||
|
||||
// 4x5 row-major, alpha passthrough:
|
||||
return [
|
||||
M00, M01, M02, 0, bR,
|
||||
M10, M11, M12, 0, bG,
|
||||
M20, M21, M22, 0, bB,
|
||||
0, 0, 0, 1, 0
|
||||
]
|
||||
}
|
||||
|
||||
var matrix: [Float32] = makeDesatTintMatrix(
|
||||
0.1, // more desaturated
|
||||
0.1, // moderate tinting
|
||||
(0.0, 1.0, 0.0), // warm tint target
|
||||
0.0 // set e.g. 0.03 for a gentle warm cast
|
||||
)
|
||||
filter.setValue(NSValue(bytes: &matrix, objCType: "{CAColorMatrix=ffffffffffffffffffff}"), forKey: "inputColorMatrix")
|
||||
mainEffectLayer.filters = [variableBlur, filter]
|
||||
}
|
||||
if isTransparent {
|
||||
variableBlur.setValue(true, forKey: "inputNormalizeEdgesTransparent")
|
||||
} else {
|
||||
variableBlur.setValue(true, forKey: "inputNormalizeEdges")
|
||||
}
|
||||
#endif*/
|
||||
self.layer.filters = [variableBlur]
|
||||
}
|
||||
|
||||
self.layer.addSublayer(imageSubview.layer)
|
||||
} else {
|
||||
self.additionalEffectLayer = createBackdropLayer()
|
||||
self.imageSubview = nil
|
||||
}
|
||||
|
||||
self.imageSubview = UIImageView()
|
||||
self.imageSubview.layer.name = "mask_source"
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
let groupName = "group-\(UInt32.random(in: 0 ... UInt32.max))"
|
||||
|
||||
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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func updateLegacyEffect() {
|
||||
guard let params = self.params else {
|
||||
return
|
||||
}
|
||||
guard let mainEffectLayer = self.mainEffectLayer else {
|
||||
return
|
||||
}
|
||||
guard let variableBlur = CALayer.variableBlur() else {
|
||||
return
|
||||
}
|
||||
|
|
@ -735,7 +646,12 @@ public final class VariableBlurView: UIView {
|
|||
return
|
||||
}
|
||||
variableBlur.setValue(self.maxBlurRadius, forKey: "inputRadius")
|
||||
variableBlur.setValue(true, forKey: "inputNormalizeEdges")
|
||||
|
||||
if self.isTransparent {
|
||||
variableBlur.setValue(true, forKey: "inputNormalizeEdgesTransparent")
|
||||
} else {
|
||||
variableBlur.setValue(true, forKey: "inputNormalizeEdges")
|
||||
}
|
||||
|
||||
let image: UIImage? = generateImage(CGSize(width: 1.0, height: min(800.0, params.size.height)), rotatedContext: { size, context in
|
||||
UIGraphicsPushContext(context)
|
||||
|
|
@ -748,7 +664,10 @@ public final class VariableBlurView: UIView {
|
|||
let mainEffectFrame: CGRect
|
||||
let additionalEffectFrame: CGRect
|
||||
|
||||
if params.isInverted {
|
||||
if params.placement.extendsInwards {
|
||||
mainEffectFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))
|
||||
additionalEffectFrame = CGRect()
|
||||
} else if params.placement.position == .bottom {
|
||||
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 {
|
||||
|
|
@ -766,11 +685,11 @@ public final class VariableBlurView: UIView {
|
|||
variableBlur.setValue(cgImage, forKey: "inputMaskImage")
|
||||
}
|
||||
|
||||
mainEffectLayer.filters = [variableBlur]
|
||||
self.layer.filters = [variableBlur]
|
||||
}
|
||||
|
||||
public func update(size: CGSize, constantHeight: CGFloat, isInverted: Bool, gradient: Gradient, transition: ContainedViewLayoutTransition) {
|
||||
let params = Params(size: size, constantHeight: constantHeight, isInverted: isInverted, gradient: gradient)
|
||||
public func update(size: CGSize, constantHeight: CGFloat, placement: Placement, gradient: Gradient, transition: ContainedViewLayoutTransition) {
|
||||
let params = Params(size: size, constantHeight: constantHeight, placement: placement, gradient: gradient)
|
||||
if params == self.params {
|
||||
return
|
||||
}
|
||||
|
|
@ -779,30 +698,83 @@ public final class VariableBlurView: UIView {
|
|||
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)
|
||||
if params.placement.extendsInwards {
|
||||
let baseHeight = max(1.0, params.gradient.height)
|
||||
let resizingInverted = params.placement.position != .bottom
|
||||
self.gradientImage = generateImage(CGSize(width: 1.0, height: baseHeight), opaque: false, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
let gradientColors = [UIColor.white.withAlphaComponent(1.0).cgColor, UIColor.white.withAlphaComponent(0.0).cgColor] as CFArray
|
||||
|
||||
var locations: [CGFloat] = [0.0, 1.0]
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
||||
|
||||
if params.placement.position == .bottom {
|
||||
context.drawLinearGradient(gradient, start: 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: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
}
|
||||
})?.resizableImage(withCapInsets: UIEdgeInsets(top: resizingInverted ? baseHeight : 0.0, left: 0.0, bottom: resizingInverted ? 0.0 : baseHeight, right: 0.0), resizingMode: .stretch)
|
||||
} else {
|
||||
self.gradientImage = EdgeEffectView.generateEdgeGradient(baseHeight: max(1.0, params.gradient.height), isInverted: params.placement.position == .bottom, extendsInwards: params.placement.extendsInwards)
|
||||
}
|
||||
}
|
||||
|
||||
self.params = params
|
||||
|
||||
guard let mainEffectLayer = self.mainEffectLayer else {
|
||||
return
|
||||
}
|
||||
|
||||
let transition = ComponentTransition(transition)
|
||||
|
||||
if #available(iOS 26.0, *) {
|
||||
if let imageSubview = self.imageSubview {
|
||||
if isGradientUpdated {
|
||||
self.imageSubview.image = self.gradientImage
|
||||
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))
|
||||
transition.setFrame(layer: self.layer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.setFrame(view: imageSubview, frame: CGRect(origin: CGPoint(), size: size))
|
||||
} else {
|
||||
if isHeightUpdated || isGradientUpdated {
|
||||
self.updateLegacyEffect()
|
||||
transition.setFrame(layer: mainEffectLayer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.setFrame(layer: self.layer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
} else {
|
||||
transition.setFrame(layer: mainEffectLayer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
transition.setFrame(layer: self.layer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class VariableBlurView: UIView {
|
||||
public let maxBlurRadius: CGFloat
|
||||
|
||||
private var effect: VariableBlurEffect?
|
||||
private let effectLayerDelegate: SimpleLayerDelegate
|
||||
private var mainEffectLayer: CALayer?
|
||||
|
||||
public init(maxBlurRadius: CGFloat = 20.0) {
|
||||
self.maxBlurRadius = maxBlurRadius
|
||||
|
||||
self.effectLayerDelegate = SimpleLayerDelegate()
|
||||
|
||||
self.mainEffectLayer = createBackdropLayer()
|
||||
if let mainEffectLayer = self.mainEffectLayer {
|
||||
self.effect = VariableBlurEffect(layer: mainEffectLayer, maxBlurRadius: maxBlurRadius)
|
||||
}
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
if let mainEffectLayer = self.mainEffectLayer {
|
||||
mainEffectLayer.delegate = self.effectLayerDelegate
|
||||
mainEffectLayer.setValue(0.5, forKey: "scale")
|
||||
|
||||
self.layer.addSublayer(mainEffectLayer)
|
||||
}
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func update(size: CGSize, constantHeight: CGFloat, isInverted: Bool, gradient: VariableBlurEffect.Gradient, transition: ContainedViewLayoutTransition) {
|
||||
self.effect?.update(size: size, constantHeight: constantHeight, placement: VariableBlurEffect.Placement(position: isInverted ? .bottom : .top, extendsInwards: false), gradient: gradient, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -653,6 +653,7 @@ public final class NavigationBarImpl: ASDisplayNode, NavigationBar {
|
|||
|
||||
if case .glass = presentationData.theme.style {
|
||||
let edgeEffectView = EdgeEffectView()
|
||||
edgeEffectView.isUserInteractionEnabled = false
|
||||
self.edgeEffectView = edgeEffectView
|
||||
self.view.addSubview(edgeEffectView)
|
||||
|
||||
|
|
|
|||
|
|
@ -1638,6 +1638,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
dismissedFloatingTopicsPanel = floatingTopicsPanel
|
||||
}
|
||||
|
||||
if floatingTopicsPanelInsets.top != 0.0 {
|
||||
floatingTopicsPanelInsets.top += 8.0
|
||||
}
|
||||
|
||||
var isSidebarOpen = false
|
||||
if let floatingTopicsPanel = self.floatingTopicsPanel {
|
||||
isSidebarOpen = floatingTopicsPanel.component.location == .side
|
||||
|
|
@ -2330,7 +2334,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
overlayNavigationBar.updateLayout(size: barFrame.size, transition: transition)
|
||||
}
|
||||
|
||||
var listInsets = UIEdgeInsets(top: containerInsets.bottom + contentBottomInset, left: containerInsets.right, bottom: containerInsets.top + 6.0, right: containerInsets.left)
|
||||
var listInsets = UIEdgeInsets(top: containerInsets.bottom + contentBottomInset, left: containerInsets.right, bottom: containerInsets.top, right: containerInsets.left)
|
||||
let listScrollIndicatorInsets = UIEdgeInsets(top: containerInsets.bottom + inputPanelsHeight, left: containerInsets.right, bottom: containerInsets.top, right: containerInsets.left)
|
||||
|
||||
var childContentInsets: UIEdgeInsets = containerInsets
|
||||
|
|
|
|||
|
|
@ -3560,6 +3560,15 @@ private final class ChatReadReportContextItemNode: ASDisplayNode, ContextMenuCus
|
|||
if self.item.action == nil {
|
||||
return false
|
||||
}
|
||||
if self.item.isEdit {
|
||||
return false
|
||||
}
|
||||
if self.item.message.id.peerId.namespace == Namespaces.Peer.CloudUser {
|
||||
if let stats = self.currentStats, stats.peers.isEmpty {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
var reactionCount = 0
|
||||
for reaction in mergedMessageReactionsAndPeers(accountPeerId: self.item.context.account.peerId, accountPeer: nil, message: self.item.message).reactions {
|
||||
reactionCount += Int(reaction.count)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue