mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-05 19:28:46 +02:00
Glass gallery
This commit is contained in:
parent
7f59789ffc
commit
8422b48216
15 changed files with 611 additions and 398 deletions
|
|
@ -35,6 +35,8 @@ import GlassControls
|
|||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
import EdgeEffect
|
||||
import RasterizedCompositionComponent
|
||||
import BadgeComponent
|
||||
|
||||
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)
|
||||
|
|
@ -45,25 +47,6 @@ private let forwardImage = generateTintedImage(image: UIImage(bundleImageName: "
|
|||
|
||||
private let cloudFetchIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FileCloudFetch"), color: UIColor.white)
|
||||
|
||||
private let fullscreenOnImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Expand"), color: .white)
|
||||
private let fullscreenOffImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Collapse"), color: .white)
|
||||
|
||||
private let captionMaskImage = generateImage(CGSize(width: 1.0, height: 17.0), 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)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 17.0), options: CGGradientDrawingOptions())
|
||||
})
|
||||
|
||||
private let titleFont = Font.medium(15.0)
|
||||
private let dateFont = Font.regular(14.0)
|
||||
|
||||
enum ChatItemGalleryFooterContent: Equatable {
|
||||
case info
|
||||
case fetch(status: MediaResourceStatus, seekable: Bool)
|
||||
|
|
@ -128,6 +111,26 @@ class CaptionScrollWrapperNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScrollViewDelegate {
|
||||
public final class SettingsButtonState: Equatable {
|
||||
public let speed: String?
|
||||
public let quality: String?
|
||||
|
||||
public init(speed: String?, quality: String?) {
|
||||
self.speed = speed
|
||||
self.quality = quality
|
||||
}
|
||||
|
||||
public static func ==(lhs: SettingsButtonState, rhs: SettingsButtonState) -> Bool {
|
||||
if lhs.speed != rhs.speed {
|
||||
return false
|
||||
}
|
||||
if lhs.quality != rhs.quality {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private var presentationData: PresentationData
|
||||
private var theme: PresentationTheme
|
||||
|
|
@ -140,6 +143,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
private let textSelectionKnobContainer: UIView
|
||||
private let textSelectionKnobSurface: UIView
|
||||
private let scrollWrapperNode: CaptionScrollWrapperNode
|
||||
private let scrollWrapperMask: EdgeMaskView
|
||||
private let scrollWrapperEffect: VariableBlurEffect
|
||||
private let scrollNode: ASScrollNode
|
||||
|
||||
|
|
@ -178,7 +182,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
displayActionButton: Bool,
|
||||
displayEditButton: Bool,
|
||||
displayPictureInPictureButton: Bool,
|
||||
displaySettingsButton: Bool
|
||||
settingsButtonState: SettingsButtonState?,
|
||||
displayTextRecognitionButton: Bool,
|
||||
displayStickersButton: Bool
|
||||
)?
|
||||
|
||||
private var codeHighlightState: (id: EngineMessage.Id, specs: [CachedMessageSyntaxHighlight.Spec], disposable: Disposable)?
|
||||
|
|
@ -304,7 +310,6 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
}
|
||||
didSet {
|
||||
if let scrubberView = self.scrubberView {
|
||||
scrubberView.setCollapsed(self.visibilityAlpha < 1.0, animated: false)
|
||||
self.view.addSubview(scrubberView)
|
||||
scrubberView.updateScrubbingVisual = { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
|
|
@ -338,8 +343,16 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
|
||||
override func setVisibilityAlpha(_ alpha: CGFloat, animated: Bool) {
|
||||
self.visibilityAlpha = alpha
|
||||
self.contentNode.alpha = alpha
|
||||
self.scrubberView?.setCollapsed(alpha < 1.0, animated: animated)
|
||||
|
||||
let transition: ComponentTransition = animated ? .easeInOut(duration: 0.2) : .immediate
|
||||
transition.animateView {
|
||||
self.contentNode.alpha = alpha
|
||||
}
|
||||
//transition.setAlpha(view: self.contentNode.view, alpha: alpha)
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
let _ = self.updateLayout(size: validLayout.0, metrics: validLayout.1, leftInset: validLayout.2, rightInset: validLayout.3, bottomInset: validLayout.4, contentInset: validLayout.5, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
private var hasExpandedCaptionPromise = ValuePromise<Bool>(false)
|
||||
|
|
@ -368,6 +381,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
self.scrollWrapperNode.backgroundColor = .clear
|
||||
self.scrollWrapperNode.isOpaque = false
|
||||
|
||||
self.scrollWrapperMask = EdgeMaskView()
|
||||
//self.scrollWrapperNode.view.addSubview(self.scrollWrapperMask)
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
self.scrollNode.clipsToBounds = false
|
||||
|
||||
|
|
@ -795,7 +811,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
displayActionButton: false,
|
||||
displayEditButton: false,
|
||||
displayPictureInPictureButton: false,
|
||||
displaySettingsButton: false
|
||||
settingsButtonState: nil,
|
||||
displayTextRecognitionButton: false,
|
||||
displayStickersButton: false
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -815,7 +833,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
}
|
||||
}
|
||||
|
||||
func setMessage(_ message: Message, displayInfo: Bool = true, translateToLanguage: String? = nil, peerIsCopyProtected: Bool = false, displayPictureInPictureButton: Bool = false, displaySettingsButton: Bool = false) {
|
||||
func setMessage(_ message: Message, displayInfo: Bool = true, translateToLanguage: String? = nil, peerIsCopyProtected: Bool = false, displayPictureInPictureButton: Bool = false, settingsButtonState: SettingsButtonState? = nil, displayTextRecognitionButton: Bool = false, displayStickersButton: Bool = false, animated: Bool = false) {
|
||||
self.currentMessage = message
|
||||
|
||||
var displayInfo = displayInfo
|
||||
|
|
@ -1061,10 +1079,12 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
displayActionButton: displayActionButton,
|
||||
displayEditButton: displayEditButton,
|
||||
displayPictureInPictureButton: displayPictureInPictureButton,
|
||||
displaySettingsButton: displaySettingsButton
|
||||
settingsButtonState: settingsButtonState,
|
||||
displayTextRecognitionButton: displayTextRecognitionButton,
|
||||
displayStickersButton: displayStickersButton
|
||||
)
|
||||
|
||||
self.requestLayout?(.immediate)
|
||||
self.requestLayout?(animated ? .animated(duration: 0.4, curve: .spring) : .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1169,6 +1189,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
let textSize = self.textNode.updateLayout(constrainSize)
|
||||
|
||||
var textOffset: CGFloat = 0.0
|
||||
var additionalTextHeight: CGFloat = 0.0
|
||||
if displayCaption {
|
||||
visibleTextHeight = textSize.height
|
||||
if visibleTextHeight > 100.0 {
|
||||
|
|
@ -1190,7 +1211,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
|
||||
var maxTextOffset: CGFloat = size.height - bottomInset - 238.0 - UIScreenPixel
|
||||
if let _ = self.scrubberView {
|
||||
maxTextOffset -= 44.0
|
||||
//maxTextOffset -= 100.0
|
||||
maxTextOffset += 0.0
|
||||
additionalTextHeight += 44.0
|
||||
}
|
||||
textOffset = min(maxTextOffset, self.scrollNode.view.contentOffset.y)
|
||||
let originalPanelHeight = panelHeight
|
||||
|
|
@ -1198,47 +1221,22 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
|
||||
if self.scrollNode.view.isScrollEnabled {
|
||||
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
|
||||
self.scrollWrapperNode.view.mask = self.scrollWrapperMask
|
||||
}
|
||||
} else {
|
||||
self.scrollWrapperNode.view.mask = nil
|
||||
}
|
||||
|
||||
let scrollWrapperNodeFrame = CGRect(x: 0.0, y: 0.0, width: width, height: max(0.0, visibleTextPanelHeight + textOffset + originalPanelHeight - 14.0))
|
||||
let scrollWrapperNodeFrame = CGRect(x: 0.0, y: 0.0, width: width, height: max(0.0, visibleTextPanelHeight + textOffset + originalPanelHeight - 14.0 + additionalTextHeight))
|
||||
if self.scrollWrapperNode.frame != scrollWrapperNodeFrame {
|
||||
self.scrollWrapperNode.frame = scrollWrapperNodeFrame
|
||||
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)
|
||||
do {
|
||||
let mask = self.scrollWrapperMask
|
||||
mask.update(size: self.scrollWrapperNode.bounds.size, color: UIColor.blue, gradientHeight: 90.0 + additionalTextHeight + contentInset, extensionHeight: 0.0, transition: ComponentTransition(transition))
|
||||
mask.frame = self.scrollWrapperNode.bounds
|
||||
mask.layer.removeAllAnimations()
|
||||
}
|
||||
self.scrollWrapperEffect.update(size: CGSize(width: scrollWrapperNodeFrame.width, height: scrollWrapperNodeFrame.height), constantHeight: 90.0, placement: VariableBlurEffect.Placement(position: .bottom, inwardsExtension: additionalTextHeight + contentInset), gradient: EdgeEffectView.generateEdgeGradientData(baseHeight: 90.0), transition: transition)
|
||||
}
|
||||
|
||||
if let buttonNode = self.buttonNode {
|
||||
|
|
@ -1295,7 +1293,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
}
|
||||
|
||||
let scrubberFrame = CGRect(origin: CGPoint(x: buttonPanelInsets.left, y: scrubberY), size: CGSize(width: width - buttonPanelInsets.left - buttonPanelInsets.right, height: 44.0))
|
||||
scrubberView.updateLayout(size: scrubberFrame.size, leftInset: 0.0, rightInset: 0.0, transition: .immediate)
|
||||
scrubberView.updateLayout(size: scrubberFrame.size, leftInset: 0.0, rightInset: 0.0, isCollapsed: self.visibilityAlpha < 1.0, transition: transition)
|
||||
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))
|
||||
}
|
||||
|
|
@ -1330,10 +1328,14 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
}
|
||||
))
|
||||
}
|
||||
if buttonsState.displaySettingsButton {
|
||||
if let settingsButtonState = buttonsState.settingsButtonState {
|
||||
centerControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("settings"),
|
||||
content: .icon("Chat/Context Menu/Settings"),
|
||||
content: .customIcon(id: AnyHashable("settings"), component: AnyComponent(SettingsNavigationIconComponent(
|
||||
speed: settingsButtonState.speed,
|
||||
quality: settingsButtonState.quality,
|
||||
isOpen: false
|
||||
))),
|
||||
action: { [weak self] in
|
||||
guard let self, let buttonPanelView = self.buttonPanel.view as? GlassControlPanelComponent.View, let centerItemView = buttonPanelView.centerItemView else {
|
||||
return
|
||||
|
|
@ -1357,6 +1359,30 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
}
|
||||
))
|
||||
}
|
||||
if buttonsState.displayTextRecognitionButton {
|
||||
centerControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("textRecognition"),
|
||||
content: .icon("Media Gallery/LiveTextIcon"),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.textRecognitionButtonPressed()
|
||||
}
|
||||
))
|
||||
}
|
||||
if buttonsState.displayStickersButton {
|
||||
centerControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("stickers"),
|
||||
content: .icon("Media Gallery/Stickers"),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.stickersButtonPressed()
|
||||
}
|
||||
))
|
||||
}
|
||||
if buttonsState.displayFullscreenButton && !metrics.isTablet {
|
||||
centerControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("fullscreen"),
|
||||
|
|
@ -2126,6 +2152,21 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
self.controllerInteraction?.editMedia(message.id)
|
||||
}
|
||||
|
||||
private func textRecognitionButtonPressed() {
|
||||
guard let currentItemNode = self.controllerInteraction?.currentItemNode() as? ChatImageGalleryItemNode else {
|
||||
return
|
||||
}
|
||||
currentItemNode.textRecognitionButtonPressed()
|
||||
}
|
||||
|
||||
private func stickersButtonPressed() {
|
||||
if let currentItemNode = self.controllerInteraction?.currentItemNode() as? ChatImageGalleryItemNode {
|
||||
currentItemNode.openStickersButtonPressed()
|
||||
} else if let currentItemNode = self.controllerInteraction?.currentItemNode() as? UniversalVideoGalleryItemNode {
|
||||
currentItemNode.openStickersButtonPressed()
|
||||
}
|
||||
}
|
||||
|
||||
private func pipButtonPressed() {
|
||||
guard let currentItemNode = self.controllerInteraction?.currentItemNode() as? UniversalVideoGalleryItemNode else {
|
||||
return
|
||||
|
|
@ -2333,3 +2374,225 @@ private final class PlaybackButtonNode: HighlightTrackingButtonNode {
|
|||
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0) + UIScreenPixel), size: textSize)
|
||||
}
|
||||
}
|
||||
|
||||
final class SettingsNavigationIconComponent: Component {
|
||||
let speed: String?
|
||||
let quality: String?
|
||||
let isOpen: Bool
|
||||
|
||||
init(speed: String?, quality: String?, isOpen: Bool) {
|
||||
self.speed = speed
|
||||
self.quality = quality
|
||||
self.isOpen = isOpen
|
||||
}
|
||||
|
||||
static func ==(lhs: SettingsNavigationIconComponent, rhs: SettingsNavigationIconComponent) -> Bool {
|
||||
if lhs.speed != rhs.speed {
|
||||
return false
|
||||
}
|
||||
if lhs.quality != rhs.quality {
|
||||
return false
|
||||
}
|
||||
if lhs.isOpen != rhs.isOpen {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let iconLayer: RasterizedCompositionMonochromeLayer
|
||||
|
||||
private let gearsLayer: RasterizedCompositionImageLayer
|
||||
private let dotLayer: RasterizedCompositionImageLayer
|
||||
|
||||
private var speedBadge: ComponentView<Empty>?
|
||||
private var qualityBadge: ComponentView<Empty>?
|
||||
|
||||
private var speedBadgeText: String?
|
||||
private var qualityBadgeText: String?
|
||||
|
||||
private let badgeFont: UIFont
|
||||
|
||||
private var isMenuOpen: Bool = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.iconLayer = RasterizedCompositionMonochromeLayer()
|
||||
|
||||
self.gearsLayer = RasterizedCompositionImageLayer()
|
||||
self.gearsLayer.image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/NavigationSettingsNoDot"), color: .white)
|
||||
|
||||
self.dotLayer = RasterizedCompositionImageLayer()
|
||||
self.dotLayer.image = generateFilledCircleImage(diameter: 4.0, color: .white)
|
||||
|
||||
self.iconLayer.contentsLayer.addSublayer(self.gearsLayer)
|
||||
self.iconLayer.contentsLayer.addSublayer(self.dotLayer)
|
||||
|
||||
self.badgeFont = Font.with(size: 8.0, design: .round, weight: .bold)
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.layer.addSublayer(self.iconLayer)
|
||||
|
||||
let size = CGSize(width: 44.0, height: 44.0)
|
||||
|
||||
if let image = self.gearsLayer.image {
|
||||
let iconInnerInsets = UIEdgeInsets(top: 4.0, left: 8.0, bottom: 4.0, right: 6.0)
|
||||
let iconSize = CGSize(width: image.size.width + iconInnerInsets.left + iconInnerInsets.right, height: image.size.height + iconInnerInsets.top + iconInnerInsets.bottom)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
self.iconLayer.position = iconFrame.center
|
||||
self.iconLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
|
||||
self.iconLayer.contentsLayer.position = CGRect(origin: CGPoint(), size: iconFrame.size).center
|
||||
self.iconLayer.contentsLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
|
||||
self.iconLayer.maskedLayer.position = CGRect(origin: CGPoint(), size: iconFrame.size).center
|
||||
self.iconLayer.maskedLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
self.iconLayer.maskedLayer.backgroundColor = UIColor.white.cgColor
|
||||
|
||||
let gearsFrame = CGRect(origin: CGPoint(x: floor((iconSize.width - image.size.width) * 0.5), y: floor((iconSize.height - image.size.height) * 0.5)), size: image.size)
|
||||
self.gearsLayer.position = gearsFrame.center
|
||||
self.gearsLayer.bounds = CGRect(origin: CGPoint(), size: gearsFrame.size)
|
||||
|
||||
if let dotImage = self.dotLayer.image {
|
||||
let dotFrame = CGRect(origin: CGPoint(x: gearsFrame.minX + floorToScreenPixels((gearsFrame.width - dotImage.size.width) * 0.5), y: gearsFrame.minY + floorToScreenPixels((gearsFrame.height - dotImage.size.height) * 0.5)), size: dotImage.size)
|
||||
self.dotLayer.position = dotFrame.center
|
||||
self.dotLayer.bounds = CGRect(origin: CGPoint(), size: dotFrame.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setIsMenuOpen(isMenuOpen: Bool) {
|
||||
if self.isMenuOpen == isMenuOpen {
|
||||
return
|
||||
}
|
||||
self.isMenuOpen = isMenuOpen
|
||||
|
||||
let rotationTransition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
|
||||
rotationTransition.updateTransform(layer: self.gearsLayer, transform: CGAffineTransformMakeRotation(isMenuOpen ? (CGFloat.pi * 2.0 / 6.0) : 0.0))
|
||||
self.gearsLayer.animateScale(from: 1.0, to: 1.07, duration: 0.1, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
guard let self, finished else {
|
||||
return
|
||||
}
|
||||
self.gearsLayer.animateScale(from: 1.07, to: 1.0, duration: 0.1, removeOnCompletion: true)
|
||||
})
|
||||
|
||||
self.dotLayer.animateScale(from: 1.0, to: 0.8, duration: 0.1, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
guard let self, finished else {
|
||||
return
|
||||
}
|
||||
self.dotLayer.animateScale(from: 0.8, to: 1.0, duration: 0.1, removeOnCompletion: true)
|
||||
})
|
||||
}
|
||||
|
||||
func setBadges(speed: String?, quality: String?, transition: ComponentTransition) {
|
||||
if self.speedBadgeText == speed && self.qualityBadgeText == quality {
|
||||
return
|
||||
}
|
||||
self.speedBadgeText = speed
|
||||
self.qualityBadgeText = quality
|
||||
|
||||
if let badgeText = speed {
|
||||
var badgeTransition = transition
|
||||
let speedBadge: ComponentView<Empty>
|
||||
if let current = self.speedBadge {
|
||||
speedBadge = current
|
||||
} else {
|
||||
speedBadge = ComponentView()
|
||||
self.speedBadge = speedBadge
|
||||
badgeTransition = badgeTransition.withAnimation(.none)
|
||||
}
|
||||
let badgeSize = speedBadge.update(
|
||||
transition: badgeTransition,
|
||||
component: AnyComponent(BadgeComponent(
|
||||
text: badgeText,
|
||||
font: self.badgeFont,
|
||||
cornerRadius: .custom(3.0),
|
||||
insets: UIEdgeInsets(top: 1.33, left: 1.66, bottom: 1.33, right: 1.66),
|
||||
outerInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
if let speedBadgeView = speedBadge.view {
|
||||
if speedBadgeView.layer.superlayer == nil {
|
||||
self.iconLayer.contentsLayer.addSublayer(speedBadgeView.layer)
|
||||
|
||||
transition.animateAlpha(layer: speedBadgeView.layer, from: 0.0, to: 1.0)
|
||||
transition.animateScale(layer: speedBadgeView.layer, from: 0.001, to: 1.0)
|
||||
}
|
||||
badgeTransition.setFrame(layer: speedBadgeView.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: badgeSize))
|
||||
}
|
||||
} else if let speedBadge = self.speedBadge {
|
||||
self.speedBadge = nil
|
||||
if let speedBadgeView = speedBadge.view {
|
||||
let speedBadgeLayer = speedBadgeView.layer
|
||||
transition.setAlpha(layer: speedBadgeLayer, alpha: 0.0, completion: { [weak speedBadgeLayer] _ in
|
||||
speedBadgeLayer?.removeFromSuperlayer()
|
||||
})
|
||||
transition.setScale(layer: speedBadgeLayer, scale: 0.001)
|
||||
}
|
||||
}
|
||||
|
||||
if let badgeText = quality {
|
||||
var badgeTransition = transition
|
||||
let qualityBadge: ComponentView<Empty>
|
||||
if let current = self.qualityBadge {
|
||||
qualityBadge = current
|
||||
} else {
|
||||
qualityBadge = ComponentView()
|
||||
self.qualityBadge = qualityBadge
|
||||
badgeTransition = badgeTransition.withAnimation(.none)
|
||||
}
|
||||
let badgeSize = qualityBadge.update(
|
||||
transition: badgeTransition,
|
||||
component: AnyComponent(BadgeComponent(
|
||||
text: badgeText,
|
||||
font: self.badgeFont,
|
||||
cornerRadius: .custom(3.0),
|
||||
insets: UIEdgeInsets(top: 1.33, left: 1.66, bottom: 1.33, right: 1.66),
|
||||
outerInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
if let qualityBadgeView = qualityBadge.view {
|
||||
if qualityBadgeView.layer.superlayer == nil {
|
||||
self.iconLayer.contentsLayer.addSublayer(qualityBadgeView.layer)
|
||||
|
||||
transition.animateAlpha(layer: qualityBadgeView.layer, from: 0.0, to: 1.0)
|
||||
transition.animateScale(layer: qualityBadgeView.layer, from: 0.001, to: 1.0)
|
||||
}
|
||||
badgeTransition.setFrame(layer: qualityBadgeView.layer, frame: CGRect(origin: CGPoint(x: self.iconLayer.bounds.width - badgeSize.width, y: self.iconLayer.bounds.height - badgeSize.height), size: badgeSize))
|
||||
}
|
||||
} else if let qualityBadge = self.qualityBadge {
|
||||
self.qualityBadge = nil
|
||||
if let qualityBadgeView = qualityBadge.view {
|
||||
let qualityBadgeLayer = qualityBadgeView.layer
|
||||
transition.setAlpha(layer: qualityBadgeLayer, alpha: 0.0, completion: { [weak qualityBadgeLayer] _ in
|
||||
qualityBadgeLayer?.removeFromSuperlayer()
|
||||
})
|
||||
transition.setScale(layer: qualityBadgeLayer, scale: 0.001)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: SettingsNavigationIconComponent, availableSize: CGSize, state: State, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.setBadges(speed: component.speed, quality: component.quality, transition: transition)
|
||||
self.setIsMenuOpen(isMenuOpen: component.isOpen)
|
||||
|
||||
return CGSize(width: 44.0, height: 44.0)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: State, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ private let scrubberForegroundColor = UIColor.white
|
|||
private let scrubberBufferingColor = UIColor(rgb: 0xffffff, alpha: 0.5)
|
||||
|
||||
final class ChatVideoGalleryItemScrubberView: UIView {
|
||||
private var containerLayout: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat)?
|
||||
private var containerLayout: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat, isCollapsed: Bool)?
|
||||
|
||||
private let backgroundContainer: GlassBackgroundContainerView
|
||||
private let backgroundView: GlassBackgroundView
|
||||
|
|
@ -80,6 +80,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
|
||||
self.chapters = chapters
|
||||
self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 8.0, lineCap: .round, scrubberHandle: .none, backgroundColor: scrubberBackgroundColor, foregroundColor: scrubberForegroundColor, bufferingColor: scrubberBufferingColor, chapters: chapters))
|
||||
self.scrubberNode.layer.allowsGroupOpacity = true
|
||||
self.shimmerEffectNode = ShimmerEffectForegroundNode()
|
||||
|
||||
self.leftTimestampNode = MediaPlayerTimeTextNode(textColor: .white)
|
||||
|
|
@ -127,9 +128,9 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
self.backgroundView.contentView.addSubview(self.scrubberNode.view)
|
||||
self.backgroundView.contentView.addSubview(self.leftTimestampNode.view)
|
||||
self.backgroundView.contentView.addSubview(self.rightTimestampNode.view)
|
||||
self.addSubview(self.scrubberNode.view)
|
||||
//self.backgroundView.contentView.addSubview(self.infoNode.view)
|
||||
}
|
||||
|
||||
|
|
@ -146,20 +147,6 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
|
||||
var isLoading = false
|
||||
var isCollapsed: Bool?
|
||||
func setCollapsed(_ collapsed: Bool, animated: Bool) {
|
||||
guard self.isCollapsed != collapsed else {
|
||||
return
|
||||
}
|
||||
|
||||
self.isCollapsed = collapsed
|
||||
|
||||
self.updateTimestampsVisibility(animated: animated)
|
||||
self.updateScrubberVisibility(animated: animated)
|
||||
|
||||
if let (size, _, _) = self.containerLayout {
|
||||
self.infoNode.alpha = size.width < size.height && !collapsed ? 1.0 : 0.0
|
||||
}
|
||||
}
|
||||
|
||||
func updateTimestampsVisibility(animated: Bool) {
|
||||
if self.isAnimatedOut {
|
||||
|
|
@ -174,15 +161,20 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
private func updateScrubberVisibility(animated: Bool) {
|
||||
var collapsed = self.isCollapsed
|
||||
var alpha: CGFloat = 1.0
|
||||
var controlAlpha: CGFloat = 1.0
|
||||
if let playbackStatus = self.playbackStatus, playbackStatus.duration <= 30.0 {
|
||||
alpha = self.isCollapsed == true ? 0.0 : 1.0
|
||||
controlAlpha = 1.0
|
||||
} else {
|
||||
alpha = self.isCollapsed == true ? 0.0 : 1.0
|
||||
controlAlpha = alpha
|
||||
collapsed = false
|
||||
}
|
||||
self.scrubberNode.setCollapsed(collapsed == true, animated: animated)
|
||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .linear) : .immediate
|
||||
|
||||
ComponentTransition(transition).setAlpha(view: self.backgroundContainer, alpha: alpha)
|
||||
ComponentTransition(transition).setAlpha(view: self.scrubberNode.view, alpha: controlAlpha)
|
||||
}
|
||||
|
||||
func animateTo(_ timestamp: Double) {
|
||||
|
|
@ -232,8 +224,8 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
strongSelf.currentLeftString = leftString
|
||||
strongSelf.currentRightString = rightString
|
||||
|
||||
if let (size, leftInset, rightInset) = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
if let (size, leftInset, rightInset, isCollapsed) = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isCollapsed: isCollapsed, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -284,8 +276,8 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
}
|
||||
strongSelf.infoNode.attributedText = NSAttributedString(string: chapter.title, font: textFont, textColor: .white)
|
||||
|
||||
if let (size, leftInset, rightInset) = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
if let (size, leftInset, rightInset, isCollapsed) = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isCollapsed: isCollapsed, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -315,7 +307,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
strongSelf.infoNodePushed = infoNodePushed
|
||||
|
||||
if let layout = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: layout.0, leftInset: layout.1, rightInset: layout.2, transition: .animated(duration: 0.35, curve: .spring))
|
||||
strongSelf.updateLayout(size: layout.0, leftInset: layout.1, rightInset: layout.2, isCollapsed: layout.3, transition: .animated(duration: 0.35, curve: .spring))
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
@ -343,8 +335,8 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
}
|
||||
strongSelf.infoNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: .white)
|
||||
|
||||
if let (size, leftInset, rightInset) = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
if let (size, leftInset, rightInset, isCollapsed) = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isCollapsed: isCollapsed, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
@ -356,8 +348,10 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = (size, leftInset, rightInset)
|
||||
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, isCollapsed: Bool, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = (size, leftInset, rightInset, isCollapsed)
|
||||
|
||||
self.isCollapsed = isCollapsed
|
||||
|
||||
let transition = ComponentTransition(transition)
|
||||
|
||||
|
|
@ -386,6 +380,11 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
rightTimestampOffset = 14.0
|
||||
infoOffset = 0.0
|
||||
|
||||
if isCollapsed {
|
||||
scrubberLeftInset = 0.0
|
||||
scrubberRightInset = 0.0
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.leftTimestampNode.view, frame: CGRect(origin: CGPoint(x: 16.0, y: leftTimestampOffset), size: CGSize(width: 60.0, height: 20.0)))
|
||||
transition.setFrame(view: self.rightTimestampNode.view, frame: CGRect(origin: CGPoint(x: size.width - leftInset - rightInset - 60.0 - 16.0, y: rightTimestampOffset), size: CGSize(width: 60.0, height: 20.0)))
|
||||
|
||||
|
|
@ -397,8 +396,21 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
transition.setPosition(view: self.infoNode.view, position: CGPoint(x: size.width / 2.0, y: infoOffset + infoSize.height / 2.0))
|
||||
self.infoNode.alpha = size.width < size.height && self.isCollapsed == false ? 1.0 : 0.0
|
||||
|
||||
let scrubberFrame = CGRect(origin: CGPoint(x: scrubberLeftInset, y: 15.0), size: CGSize(width: size.width - leftInset - rightInset - scrubberLeftInset - scrubberRightInset, height: scrubberHeight))
|
||||
self.scrubberNode.frame = scrubberFrame
|
||||
var scrubberFrame = CGRect(origin: CGPoint(x: scrubberLeftInset, y: 15.0), size: CGSize(width: size.width - leftInset - rightInset - scrubberLeftInset - scrubberRightInset, height: scrubberHeight))
|
||||
if isCollapsed {
|
||||
scrubberFrame.origin.y = size.height - scrubberHeight
|
||||
}
|
||||
transition.setFrame(view: self.scrubberNode.view, frame: scrubberFrame)
|
||||
|
||||
let scrubberTransition: ControlledTransition
|
||||
switch transition.animation {
|
||||
case let .curve(duration, curve):
|
||||
scrubberTransition = ControlledTransition(duration: duration, curve: curve.containedViewLayoutTransitionCurve, interactive: false)
|
||||
default:
|
||||
scrubberTransition = ControlledTransition(duration: 0.0, curve: .linear, interactive: false)
|
||||
}
|
||||
|
||||
self.scrubberNode.update(size: scrubberFrame.size, animator: scrubberTransition.legacyAnimator)
|
||||
self.shimmerEffectNode.updateAbsoluteRect(CGRect(origin: .zero, size: scrubberFrame.size), within: scrubberFrame.size)
|
||||
self.shimmerEffectNode.update(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.75), horizontal: true, effectSize: nil, globalTimeOffset: false, duration: nil)
|
||||
self.shimmerEffectNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 4.0), size: CGSize(width: scrubberFrame.size.width, height: 5.0))
|
||||
|
|
@ -409,6 +421,9 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.backgroundView.update(size: size, cornerRadius: min(44.0 * 0.5, size.height * 0.5), isDark: true, tintColor: .init(kind: .panel), transition: transition)
|
||||
|
||||
self.updateTimestampsVisibility(animated: !transition.animation.isImmediate)
|
||||
self.updateScrubberVisibility(animated: !transition.animation.isImmediate)
|
||||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
|
|||
}
|
||||
|
||||
self.headerEdgeEffectView = EdgeEffectView()
|
||||
self.headerEdgeEffectView.isUserInteractionEnabled = false
|
||||
|
||||
self.pager = GalleryPagerNode(pageGap: pageGap, disableTapNavigation: disableTapNavigation)
|
||||
self.footerNode = GalleryFooterNode(controllerInteraction: controllerInteraction)
|
||||
|
|
@ -303,7 +304,7 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
|
|||
edgeEffectFrame.origin.y -= navigationBarHeight
|
||||
}
|
||||
transition.updateFrame(view: self.headerEdgeEffectView, frame: edgeEffectFrame)
|
||||
self.headerEdgeEffectView.update(content: .black, alpha: 0.35, rect: edgeEffectFrame, edge: .top, edgeSize: min(edgeEffectHeight, edgeEffectFrame.height), transition: ComponentTransition(transition))
|
||||
self.headerEdgeEffectView.update(content: .black, alpha: 0.6, rect: edgeEffectFrame, edge: .top, edgeSize: min(edgeEffectHeight, edgeEffectFrame.height), transition: ComponentTransition(transition))
|
||||
transition.updateAlpha(layer: self.headerEdgeEffectView.layer, alpha: self.areControlsHidden ? 0.0 : 1.0)
|
||||
|
||||
if let navigationBar = self.navigationBar {
|
||||
|
|
@ -363,16 +364,16 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
|
|||
self.areControlsHidden = hidden
|
||||
self.controlsVisibilityChanged?(!hidden)
|
||||
if animated {
|
||||
let alpha: CGFloat = self.areControlsHidden ? 0.0 : 1.0
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
let alpha: CGFloat = self.areControlsHidden ? 0.0 : 1.0
|
||||
self.navigationBar?.alpha = alpha
|
||||
self.statusBar?.updateAlpha(alpha, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
self.footerNode.setVisibilityAlpha(alpha, animated: animated)
|
||||
self.updateThumbnailContainerNodeAlpha(.immediate)
|
||||
})
|
||||
self.footerNode.setVisibilityAlpha(alpha, animated: animated)
|
||||
self.updateThumbnailContainerNodeAlpha(.immediate)
|
||||
|
||||
if let (navigationBarHeight, layout) = self.containerLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
} else {
|
||||
let alpha: CGFloat = self.areControlsHidden ? 0.0 : 1.0
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import ComponentDisplayAdapters
|
|||
|
||||
public final class GalleryFooterNode: ASDisplayNode {
|
||||
private let edgeEffectView: EdgeEffectView
|
||||
private var contentsFrame = CGRect()
|
||||
|
||||
private var currentThumbnailPanelHeight: CGFloat?
|
||||
private var currentFooterContentNode: GalleryFooterContentNode?
|
||||
|
|
@ -20,6 +21,7 @@ public final class GalleryFooterNode: ASDisplayNode {
|
|||
self.controllerInteraction = controllerInteraction
|
||||
|
||||
self.edgeEffectView = EdgeEffectView()
|
||||
self.edgeEffectView.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
@ -29,7 +31,8 @@ public final class GalleryFooterNode: ASDisplayNode {
|
|||
private var visibilityAlpha: CGFloat = 1.0
|
||||
public func setVisibilityAlpha(_ alpha: CGFloat, animated: Bool) {
|
||||
self.visibilityAlpha = alpha
|
||||
self.edgeEffectView.alpha = alpha
|
||||
let transition: ComponentTransition = animated ? .easeInOut(duration: 0.2) : .immediate
|
||||
transition.setAlpha(view: self.edgeEffectView, alpha: alpha)
|
||||
self.currentFooterContentNode?.setVisibilityAlpha(alpha, animated: true)
|
||||
self.currentOverlayContentNode?.setVisibilityAlpha(alpha)
|
||||
}
|
||||
|
|
@ -132,13 +135,15 @@ public final class GalleryFooterNode: ASDisplayNode {
|
|||
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight))
|
||||
}
|
||||
|
||||
self.contentsFrame = backgroundFrame
|
||||
|
||||
var edgeEffectFrame = backgroundFrame
|
||||
let edgeEffectHeight: CGFloat = 120.0
|
||||
let edgeEffectOffset: CGFloat = 70.0
|
||||
edgeEffectFrame.origin.y -= edgeEffectOffset
|
||||
edgeEffectFrame.size.height += edgeEffectOffset
|
||||
edgeEffectTransition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
|
||||
self.edgeEffectView.update(content: .black, alpha: 0.35, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectHeight, edgeEffectFrame.height), transition: edgeEffectTransition)
|
||||
self.edgeEffectView.update(content: .black, alpha: 0.6, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectHeight, edgeEffectFrame.height), transition: edgeEffectTransition)
|
||||
|
||||
let contentTransition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||
if let overlayContentNode = self.currentOverlayContentNode {
|
||||
|
|
@ -171,7 +176,7 @@ public final class GalleryFooterNode: ASDisplayNode {
|
|||
if let overlayResult = self.currentOverlayContentNode?.hitTest(point, with: event) {
|
||||
return overlayResult
|
||||
}
|
||||
if !self.edgeEffectView.frame.contains(point) || self.visibilityAlpha < 1.0 {
|
||||
if !self.contentsFrame.contains(point) || self.visibilityAlpha < 1.0 {
|
||||
return nil
|
||||
}
|
||||
let result = super.hitTest(point, with: event)
|
||||
|
|
|
|||
|
|
@ -232,6 +232,7 @@ class ChatImageGalleryItem: GalleryItem {
|
|||
final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private let context: AccountContext
|
||||
private var message: Message?
|
||||
private var displayInfo: Bool = false
|
||||
private var translateToLanguage: String?
|
||||
private var peerIsCopyProtected: Bool = false
|
||||
private var isSecret: Bool = false
|
||||
|
|
@ -242,6 +243,9 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
|
||||
private let recognitionOverlayContentNode: ImageRecognitionOverlayContentNode
|
||||
|
||||
private var displayTextRecognitionButton: Bool = false
|
||||
private var displayStickersButton: Bool = false
|
||||
|
||||
private let moreBarButton: MoreHeaderButton
|
||||
|
||||
private var tilingNode: TilingNode?
|
||||
|
|
@ -445,6 +449,11 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
strongSelf.imageNode.addSubnode(recognizedContentNode)
|
||||
strongSelf.recognizedContentNode = recognizedContentNode
|
||||
strongSelf.recognitionOverlayContentNode.transitionIn()
|
||||
|
||||
if !strongSelf.displayTextRecognitionButton {
|
||||
strongSelf.displayTextRecognitionButton = true
|
||||
strongSelf.updateFooter(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
@ -452,11 +461,19 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
|
||||
fileprivate func setMessage(_ message: Message, displayInfo: Bool, translateToLanguage: String?, peerIsCopyProtected: Bool, isSecret: Bool) {
|
||||
self.message = message
|
||||
self.displayInfo = displayInfo
|
||||
self.translateToLanguage = translateToLanguage
|
||||
self.peerIsCopyProtected = peerIsCopyProtected
|
||||
self.isSecret = isSecret
|
||||
self.imageNode.captureProtected = message.id.peerId.namespace == Namespaces.Peer.SecretChat || message.isCopyProtected() || peerIsCopyProtected || isSecret || message.paidContent != nil
|
||||
self.footerContentNode.setMessage(message, displayInfo: displayInfo, translateToLanguage: translateToLanguage, peerIsCopyProtected: peerIsCopyProtected)
|
||||
self.updateFooter(animated: false)
|
||||
}
|
||||
|
||||
private func updateFooter(animated: Bool) {
|
||||
guard let message = self.message else {
|
||||
return
|
||||
}
|
||||
self.footerContentNode.setMessage(message, displayInfo: self.displayInfo, translateToLanguage: self.translateToLanguage, peerIsCopyProtected: self.peerIsCopyProtected, displayTextRecognitionButton: self.displayTextRecognitionButton, displayStickersButton: self.displayStickersButton, animated: animated)
|
||||
}
|
||||
|
||||
fileprivate func setImage(userLocation: MediaResourceUserLocation, imageReference: ImageMediaReference) {
|
||||
|
|
@ -497,9 +514,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
|
||||
var barButtonItems: [UIBarButtonItem] = []
|
||||
if imageReference.media.flags.contains(.hasStickers) {
|
||||
let rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: .white), style: .plain, target: self, action: #selector(self.openStickersButtonPressed))
|
||||
rightBarButtonItem.accessibilityLabel = self.presentationData.strings.Gallery_VoiceOver_Stickers
|
||||
barButtonItems.append(rightBarButtonItem)
|
||||
self.displayStickersButton = true
|
||||
}
|
||||
if self.message != nil {
|
||||
let moreMenuItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)!
|
||||
|
|
@ -509,6 +524,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
self._rightBarButtonItems.set(.single(barButtonItems))
|
||||
}
|
||||
self.contextAndMedia = (self.context, imageReference.abstract)
|
||||
self.updateFooter(animated: false)
|
||||
}
|
||||
|
||||
private func updateImageFromFile(path: String) {
|
||||
|
|
@ -768,11 +784,16 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
func textRecognitionButtonPressed() {
|
||||
self.recognitionOverlayContentNode.isSelected = true
|
||||
self.recognitionOverlayContentNode.action?(true)
|
||||
}
|
||||
|
||||
@objc func openStickersButtonPressed() {
|
||||
guard let (context, media) = self.contextAndMedia else {
|
||||
return
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
let topController = (self.baseNavigationController()?.topViewController as? ViewController)
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
|
||||
|
|
@ -800,7 +821,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
let baseNavigationController = strongSelf.baseNavigationController()
|
||||
baseNavigationController?.view.endEditing(true)
|
||||
let controller = StickerPackScreen(context: context, mainStickerPack: packs[0], stickerPacks: packs, sendSticker: nil, actionPerformed: { actions in
|
||||
let controller = StickerPackScreen(context: context, updatedPresentationData: (initial: presentationData, signal: .single(presentationData)), mainStickerPack: packs[0], stickerPacks: packs, sendSticker: nil, actionPerformed: { actions in
|
||||
if let (info, items, action) = actions.first {
|
||||
let animateInAsReplacement = false
|
||||
switch action {
|
||||
|
|
@ -1562,7 +1583,7 @@ private class ImageRecognitionOverlayContentNode: GalleryOverlayContentNode {
|
|||
private var validLayout: (CGSize, LayoutMetrics, UIEdgeInsets)?
|
||||
private var interfaceIsHidden: Bool = false
|
||||
|
||||
private var isSelected: Bool = false
|
||||
var isSelected: Bool = false
|
||||
|
||||
init(theme: PresentationTheme) {
|
||||
self.backgroundContainer = GlassBackgroundContainerView()
|
||||
|
|
@ -1619,33 +1640,24 @@ private class ImageRecognitionOverlayContentNode: GalleryOverlayContentNode {
|
|||
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(style: .default, color: UIColor(white: 1.0, alpha: 1.0)))
|
||||
} else {
|
||||
tintColor = .init(kind: .panel)
|
||||
}
|
||||
let tintColor: GlassBackgroundView.TintColor = .init(kind: .custom(style: .default, color: UIColor(white: 1.0, alpha: 1.0)))
|
||||
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)
|
||||
transition.setTintColor(view: self.iconView, color: .black)
|
||||
|
||||
if self.appeared {
|
||||
if !self.isSelected && isHidden {
|
||||
transition.setAlpha(view: self.backgroundContainer, alpha: 0.0)
|
||||
} else {
|
||||
transition.setAlpha(view: self.backgroundContainer, alpha: 1.0)
|
||||
transition.setAlpha(view: self.backgroundContainer, alpha: self.isSelected ? 1.0 : 0.0)
|
||||
}
|
||||
} else {
|
||||
transition.setAlpha(view: self.backgroundContainer, alpha: 0.0)
|
||||
}
|
||||
|
||||
var buttonPosition: CGPoint
|
||||
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.isSelected ? 24.0 : 70.0), y: insets.top - 50.0)
|
||||
}
|
||||
buttonPosition = CGPoint(x: size.width - insets.right - buttonSize.width - 16.0, y: insets.top - 50.0)
|
||||
|
||||
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: buttonPosition, size: buttonSize))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -611,226 +611,6 @@ final class MoreHeaderButton: HighlightableButtonNode {
|
|||
}
|
||||
}
|
||||
|
||||
final class SettingsHeaderButton: HighlightableButtonNode {
|
||||
let referenceNode: ContextReferenceContentNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
|
||||
private let iconLayer: RasterizedCompositionMonochromeLayer
|
||||
|
||||
private let gearsLayer: RasterizedCompositionImageLayer
|
||||
private let dotLayer: RasterizedCompositionImageLayer
|
||||
|
||||
private var speedBadge: ComponentView<Empty>?
|
||||
private var qualityBadge: ComponentView<Empty>?
|
||||
|
||||
private var speedBadgeText: String?
|
||||
private var qualityBadgeText: String?
|
||||
|
||||
private let badgeFont: UIFont
|
||||
|
||||
private var isMenuOpen: Bool = false
|
||||
|
||||
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
|
||||
private let wide: Bool
|
||||
|
||||
init(wide: Bool = false) {
|
||||
self.wide = wide
|
||||
|
||||
self.referenceNode = ContextReferenceContentNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.containerNode.animateScale = false
|
||||
|
||||
self.iconLayer = RasterizedCompositionMonochromeLayer()
|
||||
//self.iconLayer.backgroundColor = UIColor.green.cgColor
|
||||
|
||||
self.gearsLayer = RasterizedCompositionImageLayer()
|
||||
self.gearsLayer.image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/NavigationSettingsNoDot"), color: .white)
|
||||
|
||||
self.dotLayer = RasterizedCompositionImageLayer()
|
||||
self.dotLayer.image = generateFilledCircleImage(diameter: 4.0, color: .white)
|
||||
|
||||
self.iconLayer.contentsLayer.addSublayer(self.gearsLayer)
|
||||
self.iconLayer.contentsLayer.addSublayer(self.dotLayer)
|
||||
|
||||
self.badgeFont = Font.with(size: 8.0, design: .round, weight: .bold)
|
||||
|
||||
super.init()
|
||||
|
||||
self.containerNode.addSubnode(self.referenceNode)
|
||||
self.referenceNode.layer.addSublayer(self.iconLayer)
|
||||
self.addSubnode(self.containerNode)
|
||||
|
||||
self.containerNode.shouldBegin = { [weak self] location in
|
||||
guard let strongSelf = self, let _ = strongSelf.contextAction else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
self.containerNode.activated = { [weak self] gesture, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.contextAction?(strongSelf.containerNode, gesture)
|
||||
}
|
||||
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.referenceNode.frame = self.containerNode.bounds
|
||||
|
||||
if let image = self.gearsLayer.image {
|
||||
let iconInnerInsets = UIEdgeInsets(top: 4.0, left: 8.0, bottom: 4.0, right: 6.0)
|
||||
let iconSize = CGSize(width: image.size.width + iconInnerInsets.left + iconInnerInsets.right, height: image.size.height + iconInnerInsets.top + iconInnerInsets.bottom)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - iconSize.width) / 2.0), y: floor((self.containerNode.bounds.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
self.iconLayer.position = iconFrame.center
|
||||
self.iconLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
|
||||
self.iconLayer.contentsLayer.position = CGRect(origin: CGPoint(), size: iconFrame.size).center
|
||||
self.iconLayer.contentsLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
|
||||
self.iconLayer.maskedLayer.position = CGRect(origin: CGPoint(), size: iconFrame.size).center
|
||||
self.iconLayer.maskedLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
self.iconLayer.maskedLayer.backgroundColor = UIColor.white.cgColor
|
||||
|
||||
let gearsFrame = CGRect(origin: CGPoint(x: floor((iconSize.width - image.size.width) * 0.5), y: floor((iconSize.height - image.size.height) * 0.5)), size: image.size)
|
||||
self.gearsLayer.position = gearsFrame.center
|
||||
self.gearsLayer.bounds = CGRect(origin: CGPoint(), size: gearsFrame.size)
|
||||
|
||||
if let dotImage = self.dotLayer.image {
|
||||
let dotFrame = CGRect(origin: CGPoint(x: gearsFrame.minX + floorToScreenPixels((gearsFrame.width - dotImage.size.width) * 0.5), y: gearsFrame.minY + floorToScreenPixels((gearsFrame.height - dotImage.size.height) * 0.5)), size: dotImage.size)
|
||||
self.dotLayer.position = dotFrame.center
|
||||
self.dotLayer.bounds = CGRect(origin: CGPoint(), size: dotFrame.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
self.view.isOpaque = false
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
return CGSize(width: 44.0, height: 44.0)
|
||||
}
|
||||
|
||||
func onLayout() {
|
||||
}
|
||||
|
||||
func setIsMenuOpen(isMenuOpen: Bool) {
|
||||
if self.isMenuOpen == isMenuOpen {
|
||||
return
|
||||
}
|
||||
self.isMenuOpen = isMenuOpen
|
||||
|
||||
let rotationTransition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
|
||||
rotationTransition.updateTransform(layer: self.gearsLayer, transform: CGAffineTransformMakeRotation(isMenuOpen ? (CGFloat.pi * 2.0 / 6.0) : 0.0))
|
||||
self.gearsLayer.animateScale(from: 1.0, to: 1.07, duration: 0.1, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
guard let self, finished else {
|
||||
return
|
||||
}
|
||||
self.gearsLayer.animateScale(from: 1.07, to: 1.0, duration: 0.1, removeOnCompletion: true)
|
||||
})
|
||||
|
||||
self.dotLayer.animateScale(from: 1.0, to: 0.8, duration: 0.1, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
guard let self, finished else {
|
||||
return
|
||||
}
|
||||
self.dotLayer.animateScale(from: 0.8, to: 1.0, duration: 0.1, removeOnCompletion: true)
|
||||
})
|
||||
}
|
||||
|
||||
func setBadges(speed: String?, quality: String?, transition: ComponentTransition) {
|
||||
if self.speedBadgeText == speed && self.qualityBadgeText == quality {
|
||||
return
|
||||
}
|
||||
self.speedBadgeText = speed
|
||||
self.qualityBadgeText = quality
|
||||
|
||||
if let badgeText = speed {
|
||||
var badgeTransition = transition
|
||||
let speedBadge: ComponentView<Empty>
|
||||
if let current = self.speedBadge {
|
||||
speedBadge = current
|
||||
} else {
|
||||
speedBadge = ComponentView()
|
||||
self.speedBadge = speedBadge
|
||||
badgeTransition = badgeTransition.withAnimation(.none)
|
||||
}
|
||||
let badgeSize = speedBadge.update(
|
||||
transition: badgeTransition,
|
||||
component: AnyComponent(BadgeComponent(
|
||||
text: badgeText,
|
||||
font: self.badgeFont,
|
||||
cornerRadius: .custom(3.0),
|
||||
insets: UIEdgeInsets(top: 1.33, left: 1.66, bottom: 1.33, right: 1.66),
|
||||
outerInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
if let speedBadgeView = speedBadge.view {
|
||||
if speedBadgeView.layer.superlayer == nil {
|
||||
self.iconLayer.contentsLayer.addSublayer(speedBadgeView.layer)
|
||||
|
||||
transition.animateAlpha(layer: speedBadgeView.layer, from: 0.0, to: 1.0)
|
||||
transition.animateScale(layer: speedBadgeView.layer, from: 0.001, to: 1.0)
|
||||
}
|
||||
badgeTransition.setFrame(layer: speedBadgeView.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: badgeSize))
|
||||
}
|
||||
} else if let speedBadge = self.speedBadge {
|
||||
self.speedBadge = nil
|
||||
if let speedBadgeView = speedBadge.view {
|
||||
let speedBadgeLayer = speedBadgeView.layer
|
||||
transition.setAlpha(layer: speedBadgeLayer, alpha: 0.0, completion: { [weak speedBadgeLayer] _ in
|
||||
speedBadgeLayer?.removeFromSuperlayer()
|
||||
})
|
||||
transition.setScale(layer: speedBadgeLayer, scale: 0.001)
|
||||
}
|
||||
}
|
||||
|
||||
if let badgeText = quality {
|
||||
var badgeTransition = transition
|
||||
let qualityBadge: ComponentView<Empty>
|
||||
if let current = self.qualityBadge {
|
||||
qualityBadge = current
|
||||
} else {
|
||||
qualityBadge = ComponentView()
|
||||
self.qualityBadge = qualityBadge
|
||||
badgeTransition = badgeTransition.withAnimation(.none)
|
||||
}
|
||||
let badgeSize = qualityBadge.update(
|
||||
transition: badgeTransition,
|
||||
component: AnyComponent(BadgeComponent(
|
||||
text: badgeText,
|
||||
font: self.badgeFont,
|
||||
cornerRadius: .custom(3.0),
|
||||
insets: UIEdgeInsets(top: 1.33, left: 1.66, bottom: 1.33, right: 1.66),
|
||||
outerInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
if let qualityBadgeView = qualityBadge.view {
|
||||
if qualityBadgeView.layer.superlayer == nil {
|
||||
self.iconLayer.contentsLayer.addSublayer(qualityBadgeView.layer)
|
||||
|
||||
transition.animateAlpha(layer: qualityBadgeView.layer, from: 0.0, to: 1.0)
|
||||
transition.animateScale(layer: qualityBadgeView.layer, from: 0.001, to: 1.0)
|
||||
}
|
||||
badgeTransition.setFrame(layer: qualityBadgeView.layer, frame: CGRect(origin: CGPoint(x: self.iconLayer.bounds.width - badgeSize.width, y: self.iconLayer.bounds.height - badgeSize.height), size: badgeSize))
|
||||
}
|
||||
} else if let qualityBadge = self.qualityBadge {
|
||||
self.qualityBadge = nil
|
||||
if let qualityBadgeView = qualityBadge.view {
|
||||
let qualityBadgeLayer = qualityBadgeView.layer
|
||||
transition.setAlpha(layer: qualityBadgeLayer, alpha: 0.0, completion: { [weak qualityBadgeLayer] _ in
|
||||
qualityBadgeLayer?.removeFromSuperlayer()
|
||||
})
|
||||
transition.setScale(layer: qualityBadgeLayer, scale: 0.001)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
private final class NativePictureInPictureContentImpl: NSObject, AVPictureInPictureControllerDelegate {
|
||||
private final class PlaybackDelegate: NSObject, AVPictureInPictureSampleBufferPlaybackDelegate {
|
||||
|
|
@ -1113,8 +893,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
private var moreBarButtonRate: Double = 1.0
|
||||
private var moreBarButtonRateTimestamp: Double?
|
||||
|
||||
private let settingsBarButton: SettingsHeaderButton
|
||||
|
||||
private var videoNode: UniversalVideoNode?
|
||||
private var videoNodeUserInteractionEnabled: Bool = false
|
||||
private var videoFramePreview: FramePreview?
|
||||
|
|
@ -1139,7 +917,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
private var dismissOnOrientationChange = false
|
||||
private var keepSoundOnDismiss = false
|
||||
private var hasPictureInPicture = false
|
||||
private var hasSettingsButton = false
|
||||
private var displayStickersButton: Bool = false
|
||||
private var settingsButtonState: ChatItemGalleryFooterContentNode.SettingsButtonState?
|
||||
|
||||
private var pictureInPictureButton: UIBarButtonItem?
|
||||
|
||||
|
|
@ -1155,7 +934,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
private let statusDisposable = MetaDisposable()
|
||||
|
||||
private let moreButtonStateDisposable = MetaDisposable()
|
||||
private let settingsButtonStateDisposable = MetaDisposable()
|
||||
private let mediaPlaybackStateDisposable = MetaDisposable()
|
||||
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
|
|
@ -1215,9 +993,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
self.moreBarButton.isUserInteractionEnabled = true
|
||||
self.moreBarButton.setContent(.more(optionsCircleImage(dark: false)))
|
||||
|
||||
self.settingsBarButton = SettingsHeaderButton()
|
||||
self.settingsBarButton.isUserInteractionEnabled = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
|
@ -1247,7 +1022,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
|
||||
self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
|
||||
//self.settingsBarButton.addTarget(self, action: #selector(self.settingsButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.footerContentNode.interacting = { [weak self] value in
|
||||
self?.isInteractingPromise.set(value)
|
||||
|
|
@ -1256,8 +1030,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
self.statusButtonNode.addSubnode(self.statusNode)
|
||||
self.statusButtonNode.addTarget(self, action: #selector(self.statusButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
//self.addSubnode(self.statusButtonNode)
|
||||
|
||||
self.footerContentNode.playbackControl = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if !strongSelf.isPaused {
|
||||
|
|
@ -1391,7 +1163,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
deinit {
|
||||
self.statusDisposable.dispose()
|
||||
self.moreButtonStateDisposable.dispose()
|
||||
self.settingsButtonStateDisposable.dispose()
|
||||
self.mediaPlaybackStateDisposable.dispose()
|
||||
self.scrubbingFrameDisposable?.dispose()
|
||||
self.hideControlsDisposable?.dispose()
|
||||
|
|
@ -1864,15 +1635,27 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
}
|
||||
|
||||
self.settingsBarButton.setBadges(speed: rateString, quality: qualityString, transition: .spring(duration: 0.35))
|
||||
}))
|
||||
|
||||
self.settingsButtonStateDisposable.set((self.isShowingSettingsMenuPromise.get()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] isShowingSettingsMenu in
|
||||
guard let self else {
|
||||
return
|
||||
if self.settingsButtonState != nil {
|
||||
let settingsButtonState = ChatItemGalleryFooterContentNode.SettingsButtonState(
|
||||
speed: rateString,
|
||||
quality: qualityString
|
||||
)
|
||||
if self.settingsButtonState != settingsButtonState {
|
||||
self.settingsButtonState = settingsButtonState
|
||||
if let validLayout = self.validLayout {
|
||||
if let contentInfo = item.contentInfo {
|
||||
switch contentInfo {
|
||||
case let .message(message, _):
|
||||
self.footerContentNode.setMessage(message, displayInfo: !item.displayInfoOnTop, peerIsCopyProtected: item.peerIsCopyProtected, displayPictureInPictureButton: self.hasPictureInPicture, settingsButtonState: self.settingsButtonState, displayStickersButton: self.displayStickersButton, animated: true)
|
||||
case let .webPage(webPage, media, _):
|
||||
self.footerContentNode.setWebPage(webPage, media: media)
|
||||
}
|
||||
}
|
||||
|
||||
self.containerLayoutUpdated(validLayout.layout, navigationBarHeight: validLayout.navigationBarHeight, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
}
|
||||
self.settingsBarButton.setIsMenuOpen(isMenuOpen: isShowingSettingsMenu)
|
||||
}))
|
||||
|
||||
self.statusDisposable.set((combineLatest(queue: .mainQueue(), videoNode.status, mediaFileStatus)
|
||||
|
|
@ -1936,11 +1719,13 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
isPaused = false
|
||||
}
|
||||
} else if strongSelf.actionAtEnd == .stop {
|
||||
strongSelf.isPlayingPromise.set(false)
|
||||
strongSelf.isPlaying = false
|
||||
if strongSelf.isCentral == true {
|
||||
if !item.isSecret && !strongSelf.playOnDismiss {
|
||||
strongSelf.updateControlsVisibility(true)
|
||||
if strongSelf.isPlaying {
|
||||
strongSelf.isPlayingPromise.set(false)
|
||||
strongSelf.isPlaying = false
|
||||
if strongSelf.isCentral == true {
|
||||
if !item.isSecret && !strongSelf.playOnDismiss {
|
||||
strongSelf.updateControlsVisibility(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2030,9 +1815,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
|
||||
var barButtonItems: [UIBarButtonItem] = []
|
||||
if hasLinkedStickers {
|
||||
let rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: .white), style: .plain, target: self, action: #selector(self.openStickersButtonPressed))
|
||||
rightBarButtonItem.accessibilityLabel = self.presentationData.strings.Gallery_VoiceOver_Stickers
|
||||
barButtonItems.append(rightBarButtonItem)
|
||||
self.displayStickersButton = true
|
||||
}
|
||||
|
||||
if forceEnablePiP || (!isAnimated && !disablePlayerControls && !disablePictureInPicture) {
|
||||
|
|
@ -2080,9 +1863,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
|
||||
if !isAnimated && !disablePlayerControls {
|
||||
/*let settingsMenuItem = UIBarButtonItem(customDisplayNode: self.settingsBarButton)!
|
||||
settingsMenuItem.accessibilityLabel = self.presentationData.strings.Settings_Title
|
||||
barButtonItems.append(settingsMenuItem)*/
|
||||
hasSettingsButton = true
|
||||
}
|
||||
|
||||
|
|
@ -2093,7 +1873,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
}
|
||||
|
||||
self.hasSettingsButton = hasSettingsButton
|
||||
if hasSettingsButton {
|
||||
self.settingsButtonState = ChatItemGalleryFooterContentNode.SettingsButtonState(
|
||||
speed: nil,
|
||||
quality: nil
|
||||
)
|
||||
}
|
||||
self._rightBarButtonItems.set(.single(barButtonItems))
|
||||
|
||||
videoNode.playbackCompleted = { [weak self, weak videoNode] in
|
||||
|
|
@ -2148,7 +1933,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
switch contentInfo {
|
||||
case let .message(message, _):
|
||||
isAd = message.adAttribute != nil
|
||||
self.footerContentNode.setMessage(message, displayInfo: !item.displayInfoOnTop, peerIsCopyProtected: item.peerIsCopyProtected, displayPictureInPictureButton: self.hasPictureInPicture, displaySettingsButton: self.hasSettingsButton)
|
||||
self.footerContentNode.setMessage(message, displayInfo: !item.displayInfoOnTop, peerIsCopyProtected: item.peerIsCopyProtected, displayPictureInPictureButton: self.hasPictureInPicture, settingsButtonState: self.settingsButtonState, displayStickersButton: self.displayStickersButton)
|
||||
case let .webPage(webPage, media, _):
|
||||
self.footerContentNode.setWebPage(webPage, media: media)
|
||||
}
|
||||
|
|
@ -3111,7 +2896,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
switch contentInfo {
|
||||
case let .message(message, _):
|
||||
isAd = message.adAttribute != nil
|
||||
self.footerContentNode.setMessage(message, displayInfo: !item.displayInfoOnTop, peerIsCopyProtected: item.peerIsCopyProtected, displayPictureInPictureButton: self.hasPictureInPicture, displaySettingsButton: self.hasSettingsButton)
|
||||
self.footerContentNode.setMessage(message, displayInfo: !item.displayInfoOnTop, peerIsCopyProtected: item.peerIsCopyProtected, displayPictureInPictureButton: self.hasPictureInPicture, settingsButtonState: self.settingsButtonState, displayStickersButton: self.displayStickersButton)
|
||||
case let .webPage(webPage, media, _):
|
||||
self.footerContentNode.setWebPage(webPage, media: media)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -891,8 +891,13 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((bounds.size.height - node.lineHeight) / 2.0)), size: CGSize(width: bounds.size.width, height: node.lineHeight))
|
||||
let foregroundContentFrame = CGRect(origin: CGPoint(), size: CGSize(width: backgroundFrame.size.width, height: backgroundFrame.size.height))
|
||||
|
||||
node.backgroundNode.position = CGPoint(x: backgroundFrame.midX, y: backgroundFrame.midY)
|
||||
node.backgroundNode.bounds = CGRect(origin: CGPoint(), size: backgroundFrame.size)
|
||||
if let animator {
|
||||
animator.updatePosition(layer: node.backgroundNode.layer, position: CGPoint(x: backgroundFrame.midX, y: backgroundFrame.midY), completion: nil)
|
||||
animator.updateBounds(layer: node.backgroundNode.layer, bounds: CGRect(origin: CGPoint(), size: backgroundFrame.size), completion: nil)
|
||||
} else {
|
||||
node.backgroundNode.position = CGPoint(x: backgroundFrame.midX, y: backgroundFrame.midY)
|
||||
node.backgroundNode.bounds = CGRect(origin: CGPoint(), size: backgroundFrame.size)
|
||||
}
|
||||
|
||||
node.foregroundContentNode.position = CGPoint(x: foregroundContentFrame.midX, y: foregroundContentFrame.midY)
|
||||
node.foregroundContentNode.bounds = CGRect(origin: CGPoint(), size: foregroundContentFrame.size)
|
||||
|
|
@ -1090,10 +1095,14 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||
switch self.contentNodes {
|
||||
case let .standard(node):
|
||||
if let handleNodeContainer = node.handleNodeContainer, handleNodeContainer.isUserInteractionEnabled, handleNodeContainer.frame.insetBy(dx: 0.0, dy: -16.0).contains(point) {
|
||||
if let handleNode = node.handleNode, handleNode.convert(handleNode.bounds, to: self).insetBy(dx: -32.0, dy: -16.0).contains(point) {
|
||||
if case .none = node.handle {
|
||||
return handleNodeContainer.view
|
||||
} else {
|
||||
return nil
|
||||
if let handleNode = node.handleNode, handleNode.convert(handleNode.bounds, to: self).insetBy(dx: -32.0, dy: -16.0).contains(point) {
|
||||
return handleNodeContainer.view
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -40,9 +40,11 @@ private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat, shado
|
|||
|
||||
final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
struct GlassParams {
|
||||
var isDark: Bool
|
||||
var isTinted: Bool
|
||||
|
||||
init(isTinted: Bool) {
|
||||
init(isDark: Bool, isTinted: Bool) {
|
||||
self.isDark = isDark
|
||||
self.isTinted = isTinted
|
||||
}
|
||||
}
|
||||
|
|
@ -244,7 +246,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||
transition.updateFrame(view: self.backgroundView, frame: contentBounds, beginWithCurrentState: true)
|
||||
self.backgroundView.update(size: contentBounds.size, transition: transition)
|
||||
|
||||
if let glassBackgroundView = self.glassBackgroundView {
|
||||
if let glass = self.glass, let glassBackgroundView = self.glassBackgroundView {
|
||||
var glassBackgroundFrame = contentBounds.insetBy(dx: 10.0, dy: 10.0)
|
||||
glassBackgroundFrame.size.height -= 8.0
|
||||
transition.updateFrame(view: glassBackgroundView.container, frame: glassBackgroundFrame, beginWithCurrentState: true)
|
||||
|
|
@ -255,7 +257,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||
} else {
|
||||
glassTintColor = .init(kind: .panel)
|
||||
}
|
||||
glassBackgroundView.container.update(size: glassBackgroundFrame.size, isDark: true, transition: ComponentTransition(transition))
|
||||
glassBackgroundView.container.update(size: glassBackgroundFrame.size, isDark: glass.isDark, transition: ComponentTransition(transition))
|
||||
glassBackgroundView.view.update(size: glassBackgroundFrame.size, cornerRadius: 23.0, isDark: true, tintColor: glassTintColor, transition: ComponentTransition(transition))
|
||||
|
||||
transition.updateFrame(view: self.backgroundTintView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentBounds.width, height: contentBounds.height)).insetBy(dx: -10.0, dy: -10.0))
|
||||
|
|
@ -283,6 +285,13 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||
self.backgroundClippingLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.01, delay: mainCircleDelay)
|
||||
self.backgroundClippingLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay)
|
||||
self.backgroundShadowLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay)
|
||||
|
||||
if let glassBackgroundView = self.glassBackgroundView {
|
||||
glassBackgroundView.container.alpha = 0.0
|
||||
|
||||
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||
transition.setAlpha(view: glassBackgroundView.container, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
func animateInFromAnchorRect(size: CGSize, sourceBackgroundFrame: CGRect) {
|
||||
|
|
@ -304,6 +313,13 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||
self.backgroundClippingLayer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: visualSourceBackgroundFrame.size)), to: NSValue(cgRect: self.backgroundClippingLayer.bounds), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
||||
self.backgroundShadowLayer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceShadowFrame.midX - size.width / 2.0, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||
self.backgroundShadowLayer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceShadowFrame.size)), to: NSValue(cgRect: self.backgroundShadowLayer.bounds), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
||||
|
||||
if let glassBackgroundView = self.glassBackgroundView {
|
||||
glassBackgroundView.container.alpha = 0.0
|
||||
|
||||
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||
transition.setAlpha(view: glassBackgroundView.container, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut() {
|
||||
|
|
@ -314,5 +330,10 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||
self.largeCircleShadowLayer.animateAlpha(from: CGFloat(self.largeCircleShadowLayer.opacity), to: 0.0, duration: 0.1, removeOnCompletion: false)
|
||||
//self.smallCircleLayer.animateAlpha(from: CGFloat(self.smallCircleLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.smallCircleShadowLayer.animateAlpha(from: CGFloat(self.smallCircleShadowLayer.opacity), to: 0.0, duration: 0.1, removeOnCompletion: false)
|
||||
|
||||
if let glassBackgroundView = self.glassBackgroundView {
|
||||
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||
transition.setAlpha(view: glassBackgroundView.container, alpha: 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -510,7 +510,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
self.backgroundMaskNode = ASDisplayNode()
|
||||
var backgroundGlassParams: ReactionContextBackgroundNode.GlassParams?
|
||||
if case let .glass(isTinted) = style {
|
||||
backgroundGlassParams = ReactionContextBackgroundNode.GlassParams(isTinted: isTinted)
|
||||
backgroundGlassParams = ReactionContextBackgroundNode.GlassParams(isDark: presentationData.theme.overallDarkAppearance, isTinted: isTinted)
|
||||
}
|
||||
self.backgroundNode = ReactionContextBackgroundNode(glass: backgroundGlassParams, largeCircleSize: largeCircleSize, smallCircleSize: smallCircleSize, maskNode: self.backgroundMaskNode)
|
||||
self.leftBackgroundMaskNode = ASDisplayNode()
|
||||
|
|
|
|||
|
|
@ -674,6 +674,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||
context: reactionItems.context,
|
||||
animationCache: reactionItems.animationCache,
|
||||
presentationData: presentationData,
|
||||
//style: .glass(isTinted: false),
|
||||
items: reactionItems.reactionItems,
|
||||
selectedItems: reactionItems.selectedReactionItems,
|
||||
title: reactionItems.reactionsTitle,
|
||||
|
|
@ -1058,12 +1059,17 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||
|
||||
transition.updateFrame(node: self.contentRectDebugNode, frame: contentRect, beginWithCurrentState: true)
|
||||
|
||||
var totalActionsHeight: CGFloat = actionsSize.height
|
||||
if additionalActionsSize.height != 0.0 {
|
||||
totalActionsHeight += 10.0 + additionalActionsSize.height
|
||||
}
|
||||
|
||||
var actionsFrame: CGRect
|
||||
if let contextExtractableContainer {
|
||||
let _ = contextExtractableContainer
|
||||
actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.minY), size: actionsSize)
|
||||
} else if case let .reference(source) = self.source, let actionsPosition = source.transitionInfo()?.actionsPosition, case .top = actionsPosition {
|
||||
actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.minY - contentActionsSpacing - actionsSize.height), size: actionsSize)
|
||||
actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.minY - contentActionsSpacing - totalActionsHeight), size: actionsSize)
|
||||
} else {
|
||||
actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.maxY + contentActionsSpacing), size: actionsSize)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -591,11 +591,11 @@ public final class VariableBlurEffect {
|
|||
}
|
||||
|
||||
public let position: Position
|
||||
public let extendsInwards: Bool
|
||||
public let inwardsExtension: CGFloat?
|
||||
|
||||
public init(position: Position, extendsInwards: Bool) {
|
||||
public init(position: Position, inwardsExtension: CGFloat?) {
|
||||
self.position = position
|
||||
self.extendsInwards = extendsInwards
|
||||
self.inwardsExtension = inwardsExtension
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -664,7 +664,7 @@ public final class VariableBlurEffect {
|
|||
let mainEffectFrame: CGRect
|
||||
let additionalEffectFrame: CGRect
|
||||
|
||||
if params.placement.extendsInwards {
|
||||
if params.placement.inwardsExtension != nil {
|
||||
mainEffectFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))
|
||||
additionalEffectFrame = CGRect()
|
||||
} else if params.placement.position == .bottom {
|
||||
|
|
@ -698,8 +698,8 @@ public final class VariableBlurEffect {
|
|||
let isHeightUpdated = gradient.height != self.params?.gradient.height || size.height != self.params?.size.height
|
||||
|
||||
if isGradientUpdated {
|
||||
if params.placement.extendsInwards {
|
||||
let baseHeight = max(1.0, params.gradient.height)
|
||||
if let inwardsExtension = params.placement.inwardsExtension {
|
||||
let baseHeight = max(1.0, params.gradient.height + inwardsExtension)
|
||||
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)
|
||||
|
|
@ -712,13 +712,17 @@ public final class VariableBlurEffect {
|
|||
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())
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: max(0.0, size.height - inwardsExtension)), end: CGPoint(x: 0.0, y: 0.0), options: CGGradientDrawingOptions())
|
||||
if inwardsExtension > 0.0 {
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.height - inwardsExtension), size: CGSize(width: size.width, height: inwardsExtension)))
|
||||
}
|
||||
} 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.gradientImage = EdgeEffectView.generateEdgeGradient(baseHeight: max(1.0, params.gradient.height), isInverted: params.placement.position == .bottom, extendsInwards: params.placement.inwardsExtension != nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -775,6 +779,62 @@ public final class VariableBlurView: UIView {
|
|||
}
|
||||
|
||||
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)
|
||||
self.effect?.update(size: size, constantHeight: constantHeight, placement: VariableBlurEffect.Placement(position: isInverted ? .bottom : .top, inwardsExtension: nil), gradient: gradient, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public final class EdgeMaskView: UIView {
|
||||
private struct MaskParams: Equatable {
|
||||
let gradientHeight: CGFloat
|
||||
let extensionHeight: CGFloat
|
||||
|
||||
init(gradientHeight: CGFloat, extensionHeight: CGFloat) {
|
||||
self.gradientHeight = gradientHeight
|
||||
self.extensionHeight = extensionHeight
|
||||
}
|
||||
}
|
||||
|
||||
private let imageView: UIImageView
|
||||
|
||||
private var maskParams: MaskParams?
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
self.imageView = UIImageView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.imageView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func update(size: CGSize, color: UIColor, gradientHeight: CGFloat, extensionHeight: CGFloat, transition: ComponentTransition) {
|
||||
let maskParams = MaskParams(gradientHeight: gradientHeight, extensionHeight: extensionHeight)
|
||||
if maskParams != self.maskParams {
|
||||
self.maskParams = maskParams
|
||||
|
||||
let baseHeight = max(1.0, maskParams.gradientHeight + maskParams.extensionHeight)
|
||||
let resizingInverted = !"".isEmpty
|
||||
self.imageView.image = 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 "".isEmpty {
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height - maskParams.extensionHeight), 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).withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
self.imageView.tintColor = color
|
||||
transition.setFrame(view: self.imageView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,38 @@ import MultilineTextComponent
|
|||
|
||||
public final class GlassControlGroupComponent: Component {
|
||||
public final class Item: Equatable {
|
||||
public enum Content: Hashable {
|
||||
public enum Content: Equatable {
|
||||
case icon(String)
|
||||
case text(String)
|
||||
case customIcon(id: AnyHashable, component: AnyComponent<Empty>)
|
||||
|
||||
enum Id: Hashable {
|
||||
case icon(String)
|
||||
case text(String)
|
||||
case customIcon(AnyHashable)
|
||||
}
|
||||
|
||||
var id: Id {
|
||||
switch self {
|
||||
case let .icon(icon):
|
||||
return .icon(icon)
|
||||
case let .text(text):
|
||||
return .text(text)
|
||||
case let .customIcon(id, _):
|
||||
return .customIcon(id)
|
||||
}
|
||||
}
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
case let .icon(icon):
|
||||
icon.hash(into: &hasher)
|
||||
case let .text(text):
|
||||
text.hash(into: &hasher)
|
||||
case let .customIcon(id, _):
|
||||
id.hash(into: &hasher)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public let id: AnyHashable
|
||||
|
|
@ -125,7 +154,7 @@ public final class GlassControlGroupComponent: Component {
|
|||
var validIds: [AnyHashable] = []
|
||||
var isInteractive = false
|
||||
for item in component.items {
|
||||
let itemId = ItemId(id: item.id, contentId: item.content)
|
||||
let itemId = ItemId(id: item.id, contentId: item.content.id)
|
||||
|
||||
validIds.append(itemId)
|
||||
|
||||
|
|
@ -157,6 +186,8 @@ public final class GlassControlGroupComponent: Component {
|
|||
))
|
||||
itemInsets.left = 10.0
|
||||
itemInsets.right = itemInsets.left
|
||||
case let .customIcon(_, customIcon):
|
||||
content = customIcon
|
||||
}
|
||||
|
||||
var minItemWidth: CGFloat = availableSize.height
|
||||
|
|
|
|||
|
|
@ -142,19 +142,21 @@ public final class VideoPlaybackControlsComponent: Component {
|
|||
|
||||
self.isUserInteractionEnabled = component.isVisible
|
||||
|
||||
let containerInset: CGFloat = 32.0
|
||||
|
||||
let size = CGSize(width: component.layoutParams.sideButtonSize * 2.0 + component.layoutParams.centerButtonSize + component.layoutParams.spacing * 2.0, height: component.layoutParams.centerButtonSize)
|
||||
|
||||
let leftButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - component.layoutParams.sideButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.sideButtonSize, height: component.layoutParams.sideButtonSize))
|
||||
let centerButtonFrame = CGRect(origin: CGPoint(x: component.layoutParams.sideButtonSize + component.layoutParams.spacing, y: floorToScreenPixels((size.height - component.layoutParams.centerButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.centerButtonSize, height: component.layoutParams.centerButtonSize))
|
||||
let rightButtonFrame = CGRect(origin: CGPoint(x: size.width - component.layoutParams.sideButtonSize, y: floorToScreenPixels((size.height - component.layoutParams.sideButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.sideButtonSize, height: component.layoutParams.sideButtonSize))
|
||||
let leftButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - component.layoutParams.sideButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.sideButtonSize, height: component.layoutParams.sideButtonSize)).offsetBy(dx: containerInset, dy: containerInset)
|
||||
let centerButtonFrame = CGRect(origin: CGPoint(x: component.layoutParams.sideButtonSize + component.layoutParams.spacing, y: floorToScreenPixels((size.height - component.layoutParams.centerButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.centerButtonSize, height: component.layoutParams.centerButtonSize)).offsetBy(dx: containerInset, dy: containerInset)
|
||||
let rightButtonFrame = CGRect(origin: CGPoint(x: size.width - component.layoutParams.sideButtonSize, y: floorToScreenPixels((size.height - component.layoutParams.sideButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.sideButtonSize, height: component.layoutParams.sideButtonSize)).offsetBy(dx: containerInset, dy: containerInset)
|
||||
|
||||
if isVisibleChanged && !transition.animation.isImmediate {
|
||||
self.backgroundContainer.isHidden = true
|
||||
self.backgroundContainer.isHidden = false
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.backgroundContainer.update(size: size, isDark: true, transition: transition)
|
||||
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -containerInset, dy: -containerInset))
|
||||
self.backgroundContainer.update(size: CGSize(width: size.width + containerInset * 2.0, height: size.height + containerInset * 2.0), isDark: true, transition: transition)
|
||||
|
||||
let areSideButtonsVisible = component.isVisible && component.displaySeekControls
|
||||
let buttonsTintColor: GlassBackgroundView.TintColor = .init(kind: .custom(style: .clear, color: UIColor(white: 0.0, alpha: 0.2)))
|
||||
|
|
|
|||
|
|
@ -2256,6 +2256,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
if let value = self.backgroundNode.makeEdgeEffectNode() {
|
||||
bottomBackgroundEdgeEffectNode = value
|
||||
self.bottomBackgroundEdgeEffectNode = value
|
||||
value.isUserInteractionEnabled = false
|
||||
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.historyNodeContainer.view)
|
||||
}
|
||||
}
|
||||
|
|
@ -2466,6 +2467,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
if let value = self.backgroundNode.makeEdgeEffectNode() {
|
||||
topBackgroundEdgeEffectNode = value
|
||||
self.topBackgroundEdgeEffectNode = value
|
||||
value.isUserInteractionEnabled = false
|
||||
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.historyNodeContainer.view)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEffectNode
|
|||
return
|
||||
}
|
||||
self.contentNode.contents = parentNode.contentNode.contents
|
||||
self.contentNode.contentMode = parentNode.contentNode.contentMode
|
||||
self.contentNode.backgroundColor = parentNode.contentNode.backgroundColor
|
||||
self.contentNode.alpha = parentNode.contentNode.alpha
|
||||
self.contentNode.isHidden = parentNode.contentNode.isHidden
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue