Update Ghostgram features

This commit is contained in:
ichmagmaus 812
2026-03-07 18:15:32 +01:00
parent 32b0dddcd9
commit 13a43b9068
902 changed files with 148302 additions and 62355 deletions
@@ -124,6 +124,11 @@ class CaptionScrollWrapperNode: ASDisplayNode {
}
final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScrollViewDelegate {
struct SettingsButtonState: Equatable {
let speed: String?
let quality: String?
}
private let context: AccountContext
private var presentationData: PresentationData
private var theme: PresentationTheme
@@ -1147,6 +1152,14 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
}
}
func setMessage(_ message: Message, displayInfo: Bool, translateToLanguage: String? = nil, peerIsCopyProtected: Bool, displayPictureInPictureButton: Bool, settingsButtonState: SettingsButtonState?, displayStickersButton: Bool) {
self.setMessage(message, displayInfo: displayInfo, translateToLanguage: translateToLanguage, peerIsCopyProtected: peerIsCopyProtected)
}
func setMessage(_ message: Message, displayInfo: Bool, translateToLanguage: String? = nil, peerIsCopyProtected: Bool, displayPictureInPictureButton: Bool, settingsButtonState: SettingsButtonState?, displayStickersButton: Bool, animated: Bool) {
self.setMessage(message, displayInfo: displayInfo, translateToLanguage: translateToLanguage, peerIsCopyProtected: peerIsCopyProtected, displayPictureInPictureButton: displayPictureInPictureButton, settingsButtonState: settingsButtonState, displayStickersButton: displayStickersButton)
}
func setWebPage(_ webPage: TelegramMediaWebpage, media: Media) {
self.currentWebPageAndMedia = (webPage, media)
}
@@ -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?
@@ -535,6 +535,10 @@ public struct GalleryConfiguration {
}
static func with(appConfiguration: AppConfiguration) -> GalleryConfiguration {
// MARK: Swiftgram
if appConfiguration.sgWebSettings.global.ytPip {
return GalleryConfiguration(youtubePictureInPictureEnabled: true)
}
if let data = appConfiguration.data, let value = data["youtube_pip"] as? String {
return GalleryConfiguration(youtubePictureInPictureEnabled: value != "disabled")
} else {
@@ -576,8 +580,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: .clear, style: .glass)
private var galleryNode: GalleryControllerNode {
return self.displayNode as! GalleryControllerNode
@@ -588,6 +591,8 @@ public class GalleryController: ViewController, StandalonePresentableController,
private let source: GalleryControllerItemSource
private let invertItemOrder: Bool
private let titleView: GalleryTitleView
private let streamVideos: Bool
private let _ready = Promise<Bool>()
@@ -618,7 +623,7 @@ public class GalleryController: ViewController, StandalonePresentableController,
private var configuration: GalleryConfiguration?
private let centralItemTitle = Promise<String>()
private let centralItemTitleView = Promise<UIView?>()
private let centralItemTitleContent = Promise<GalleryTitleView.Content?>()
private let centralItemRightBarButtonItem = Promise<UIBarButtonItem?>()
private let centralItemRightBarButtonItems = Promise<[UIBarButtonItem]?>(nil)
private let centralItemNavigationStyle = Promise<GalleryItemNodeNavigationStyle>()
@@ -646,6 +651,8 @@ public class GalleryController: ViewController, StandalonePresentableController,
public var onDidAppear: (() -> Void)?
public var useSimpleAnimation: Bool = false
public var navigateToMessageContext: ((EngineMessage) -> Void)?
private var initialOrientation: UIInterfaceOrientation?
public init(context: AccountContext, source: GalleryControllerItemSource, invertItemOrder: Bool = false, streamSingleVideo: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, playbackRate: Double? = nil, synchronousLoad: Bool = false, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, baseNavigationController: NavigationController?, actionInteraction: GalleryControllerActionInteraction? = nil, generateStoreAfterDownload: ((Message, TelegramMediaFile) -> (() -> Void)?)? = nil) {
@@ -681,6 +688,8 @@ public class GalleryController: ViewController, StandalonePresentableController,
openActionOptionsImpl?(action, message)
}
self.titleView = GalleryTitleView(context: context, presentationData: self.presentationData)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: GalleryController.darkNavigationTheme, strings: NavigationBarStrings(presentationStrings: self.presentationData.strings)))
let backItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Back, target: self, action: #selector(self.donePressed))
@@ -916,8 +925,8 @@ public class GalleryController: ViewController, StandalonePresentableController,
self?.navigationItem.title = title
}))
self.centralItemAttributesDisposable.add(self.centralItemTitleView.get().start(next: { [weak self] titleView in
self?.navigationItem.titleView = titleView
self.centralItemAttributesDisposable.add(self.centralItemTitleContent.get().start(next: { [weak self] titleContent in
self?.titleView.setContent(content: titleContent)
}))
self.centralItemAttributesDisposable.add(combineLatest(self.centralItemRightBarButtonItem.get(), self.centralItemRightBarButtonItems.get()).start(next: { [weak self] rightBarButtonItem, rightBarButtonItems in
@@ -934,7 +943,7 @@ public class GalleryController: ViewController, StandalonePresentableController,
self.centralItemAttributesDisposable.add(self.centralItemFooterContentNode.get().start(next: { [weak self] footerContentNode, overlayContentNode in
self?.galleryNode.updatePresentationState({
$0.withUpdatedFooterContentNode(footerContentNode).withUpdatedOverlayContentNode(overlayContentNode)
}, transition: .immediate)
}, transition: .animated(duration: 0.4, curve: .spring))
}))
self.centralItemAttributesDisposable.add(self.centralItemNavigationStyle.get().start(next: { [weak self] style in
@@ -1298,6 +1307,10 @@ public class GalleryController: ViewController, StandalonePresentableController,
var animatedOutNode = true
var animatedOutInterface = false
if forceAway {
self._hiddenMedia.set(.single(nil))
}
let completion = { [weak self] in
if animatedOutNode && animatedOutInterface {
self?.actionInteraction?.updateCanReadHistory(true)
@@ -1371,10 +1384,12 @@ public class GalleryController: ViewController, StandalonePresentableController,
}
}, controller: { [weak self] in
return self
}, currentItemNode: { [weak self] in
return self?.galleryNode.pager.centralItemNode()
})
let disableTapNavigation = !(self.context.sharedContext.currentMediaDisplaySettings.with { $0 }.showNextMediaOnTap)
self.displayNode = GalleryControllerNode(context: self.context, controllerInteraction: controllerInteraction, disableTapNavigation: disableTapNavigation)
self.displayNode = GalleryControllerNode(context: self.context, controllerInteraction: controllerInteraction, titleView: titleView, disableTapNavigation: disableTapNavigation)
self.displayNodeDidLoad()
self.galleryNode.statusBar = self.statusBar
@@ -1528,7 +1543,7 @@ public class GalleryController: ViewController, StandalonePresentableController,
if let node = strongSelf.galleryNode.pager.centralItemNode() {
strongSelf.centralItemTitle.set(node.title())
strongSelf.centralItemTitleView.set(node.titleView())
strongSelf.centralItemTitleContent.set(node.titleContent())
strongSelf.centralItemRightBarButtonItem.set(node.rightBarButtonItem())
strongSelf.centralItemRightBarButtonItems.set(node.rightBarButtonItems())
strongSelf.centralItemNavigationStyle.set(node.navigationStyle())
@@ -1714,7 +1729,7 @@ public class GalleryController: ViewController, StandalonePresentableController,
let entry = self.entries[centralItemNode.index]
self.centralItemTitle.set(centralItemNode.title())
self.centralItemTitleView.set(centralItemNode.titleView())
self.centralItemTitleContent.set(centralItemNode.titleContent())
self.centralItemRightBarButtonItem.set(centralItemNode.rightBarButtonItem())
self.centralItemRightBarButtonItems.set(centralItemNode.rightBarButtonItems())
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())
@@ -1767,7 +1782,7 @@ public class GalleryController: ViewController, StandalonePresentableController,
if let centralItemNode = self.galleryNode.pager.centralItemNode() {
let message = self.entries[centralItemNode.index].entry.message
self.centralItemTitle.set(centralItemNode.title())
self.centralItemTitleView.set(centralItemNode.titleView())
self.centralItemTitleContent.set(centralItemNode.titleContent())
self.centralItemRightBarButtonItem.set(centralItemNode.rightBarButtonItem())
self.centralItemRightBarButtonItems.set(centralItemNode.rightBarButtonItems())
self.centralItemNavigationStyle.set(centralItemNode.navigationStyle())
@@ -1912,4 +1927,11 @@ public class GalleryController: ViewController, StandalonePresentableController,
return true
}
func dismissAndNavigateToMessageContext(message: Message) {
if let navigateToMessageContext = self.navigateToMessageContext {
navigateToMessageContext(EngineMessage(message))
}
self.dismiss(forceAway: true)
}
}
@@ -57,7 +57,7 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
}
}
public init(context: AccountContext, controllerInteraction: GalleryControllerInteraction, pageGap: CGFloat = 20.0, disableTapNavigation: Bool = false) {
public init(context: AccountContext, controllerInteraction: GalleryControllerInteraction, titleView: UIView? = nil, pageGap: CGFloat = 20.0, disableTapNavigation: Bool = false) {
self.context = context
self.backgroundNode = ASDisplayNode()
self.backgroundNode.backgroundColor = UIColor.black
@@ -12,14 +12,16 @@ public final class GalleryControllerInteraction {
public let replaceRootController: (ViewController, Promise<Bool>?) -> Void
public let editMedia: (MessageId) -> Void
public let controller: () -> ViewController?
public let currentItemNode: (() -> GalleryItemNode?)?
public init(presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping (ViewController) -> Void, dismissController: @escaping () -> Void, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, editMedia: @escaping (MessageId) -> Void, controller: @escaping () -> ViewController?) {
public init(presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping (ViewController) -> Void, dismissController: @escaping () -> Void, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, editMedia: @escaping (MessageId) -> Void, controller: @escaping () -> ViewController?, currentItemNode: (() -> GalleryItemNode?)? = nil) {
self.presentController = presentController
self.pushController = pushController
self.dismissController = dismissController
self.replaceRootController = replaceRootController
self.editMedia = editMedia
self.controller = controller
self.currentItemNode = currentItemNode
}
}
@@ -56,6 +56,10 @@ open class GalleryItemNode: ASDisplayNode {
return .single(nil)
}
open func titleContent() -> Signal<GalleryTitleView.Content?, NoError> {
return .single(nil)
}
open func rightBarButtonItem() -> Signal<UIBarButtonItem?, NoError> {
return .single(nil)
}
@@ -6,15 +6,32 @@ import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramStringFormatting
import AccountContext
private let titleFont = Font.medium(15.0)
private let dateFont = Font.regular(14.0)
final class GalleryTitleView: UIView, NavigationBarTitleView {
public final class GalleryTitleView: UIView, NavigationBarTitleView {
public struct Content {
let message: EngineMessage?
let title: String?
let action: (() -> Void)?
init(message: EngineMessage, title: String?, action: (() -> Void)?) {
self.message = message
self.title = title
self.action = action
}
}
private let authorNameNode: ASTextNode
private let dateNode: ASTextNode
private var context: AccountContext?
private var presentationDataValue: PresentationData?
private var contentAction: (() -> Void)?
private var tapRecognizer: UITapGestureRecognizer?
var requestUpdate: ((ContainedViewLayoutTransition) -> Void)?
public var requestUpdate: ((ContainedViewLayoutTransition) -> Void)?
override init(frame: CGRect) {
self.authorNameNode = ASTextNode()
@@ -31,6 +48,12 @@ final class GalleryTitleView: UIView, NavigationBarTitleView {
self.addSubnode(self.dateNode)
}
convenience init(context: AccountContext, presentationData: PresentationData) {
self.init(frame: CGRect())
self.context = context
self.presentationDataValue = presentationData
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@@ -43,7 +66,37 @@ final class GalleryTitleView: UIView, NavigationBarTitleView {
self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: .white)
}
func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
func setContent(content: Content?) {
self.contentAction = content?.action
if self.tapRecognizer == nil {
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.contentTapped))
self.addGestureRecognizer(tapRecognizer)
self.tapRecognizer = tapRecognizer
}
self.tapRecognizer?.isEnabled = content?.action != nil
if let content, let message = content.message, let context = self.context, let presentationData = self.presentationDataValue {
self.setMessage(message._asMessage(), presentationData: presentationData, accountPeerId: context.account.peerId)
if let title = content.title {
self.authorNameNode.attributedText = NSAttributedString(string: title, font: titleFont, textColor: .white)
}
} else if let title = content?.title {
self.authorNameNode.attributedText = NSAttributedString(string: title, font: titleFont, textColor: .white)
self.dateNode.attributedText = nil
} else {
self.authorNameNode.attributedText = nil
self.dateNode.attributedText = nil
}
self.requestUpdate?(.immediate)
}
@objc private func contentTapped() {
self.contentAction?()
}
public func updateLayout(availableSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
let size = availableSize
let leftInset: CGFloat = 0.0
@@ -63,7 +116,7 @@ final class GalleryTitleView: UIView, NavigationBarTitleView {
return availableSize
}
func animateLayoutTransition() {
public func animateLayoutTransition() {
}
}
@@ -756,7 +756,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
guard let controller = self.baseNavigationController()?.topViewController as? ViewController else {
return
}
let contextController = ContextController(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)
let contextController = ContextController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceNode.view, actionsOnTop: false)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
controller.presentInGlobalOverlay(contextController)
}
@@ -1,3 +1,4 @@
import SGSimpleSettings
import Foundation
import UIKit
import AsyncDisplayKit
@@ -36,6 +37,7 @@ import ComponentDisplayAdapters
import ToastComponent
import MultilineTextComponent
import BundleIconComponent
import VideoPlaybackControlsComponent
public enum UniversalVideoGalleryItemContentInfo {
case message(Message, Int?)
@@ -100,32 +102,14 @@ public class UniversalVideoGalleryItem: GalleryItem {
public func node(synchronous: Bool) -> GalleryItemNode {
let node = UniversalVideoGalleryItemNode(context: self.context, presentationData: self.presentationData, performAction: self.performAction, openActionOptions: self.openActionOptions, present: self.present)
if let indexData = self.indexData {
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").string))
} else if case let .message(message, _) = self.contentInfo, let _ = message.adAttribute {
node._title.set(.single(self.presentationData.strings.Gallery_Ad))
}
node.setupItem(self)
if self.displayInfoOnTop, case let .message(message, _) = self.contentInfo {
node.titleContentView?.setMessage(message, presentationData: self.presentationData, accountPeerId: self.context.account.peerId)
}
return node
}
public func updateNode(node: GalleryItemNode, synchronous: Bool) {
if let node = node as? UniversalVideoGalleryItemNode {
if let indexData = self.indexData {
node._title.set(.single(self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").string))
}
node.setupItem(self)
if self.displayInfoOnTop, case let .message(message, _) = self.contentInfo {
node.titleContentView?.setMessage(message, presentationData: self.presentationData, accountPeerId: self.context.account.peerId)
}
}
}
@@ -237,7 +221,7 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
var performAction: ((GalleryControllerInteractionTapAction) -> Void)?
var presentPremiumDemo: (() -> Void)?
var openMoreMenu: ((ContextReferenceContentNode, Message) -> Void)?
var openMoreMenu: ((UIView, Message) -> Void)?
private var validLayout: (size: CGSize, metrics: LayoutMetrics, insets: UIEdgeInsets)?
@@ -373,7 +357,7 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
},
moreAction: { [weak self] sourceNode in
if let self {
self.openMoreMenu?(sourceNode, adMessage)
self.openMoreMenu?(sourceNode.view, adMessage)
}
}
)
@@ -434,14 +418,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 +479,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 +514,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 +537,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 +547,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 +554,6 @@ final class MoreHeaderButton: HighlightableButtonNode {
self.iconNode.image = image
self.iconNode.isHidden = false
self.animationNode?.isHidden = false
}
} else {
self.content = content
@@ -595,7 +565,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 +572,6 @@ final class MoreHeaderButton: HighlightableButtonNode {
self.iconNode.image = image
self.iconNode.isHidden = false
self.animationNode?.isHidden = false
}
}
}
@@ -614,236 +582,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()
}
}
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: 26.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)
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: wide ? 32.0 : 22.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)
}
}
}
}
@@ -1117,7 +862,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
fileprivate let _ready = Promise<Void>()
fileprivate let _title = Promise<String>()
fileprivate let _titleView = Promise<UIView?>()
fileprivate let _titleContent = Promise<GalleryTitleView.Content?>(nil)
fileprivate let _rightBarButtonItems = Promise<[UIBarButtonItem]?>()
fileprivate var titleContentView: GalleryTitleView?
@@ -1129,8 +874,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?
@@ -1140,6 +883,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
private let statusNode: RadialStatusNode
private var statusNodeShouldBeHidden = true
private let playbackControls = ComponentView<Empty>()
private var isCentral: Bool?
private var _isVisible: Bool?
private var initiallyActivated = false
@@ -1153,6 +898,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
private var dismissOnOrientationChange = false
private var keepSoundOnDismiss = false
private var hasPictureInPicture = false
private var displayStickersButton: Bool = false
private var settingsButtonState: ChatItemGalleryFooterContentNode.SettingsButtonState?
private var pictureInPictureButton: UIBarButtonItem?
@@ -1168,7 +915,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()
@@ -1182,6 +928,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
private var isPlaying = false
private let isPlayingPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
private let isInteractingPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
private var areControlsVisible: Bool = true
private let controlsVisiblePromise = ValuePromise<Bool>(true, ignoreRepeated: true)
private let isShowingContextMenuPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
private let isShowingSettingsMenuPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
@@ -1227,9 +974,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
@@ -1259,7 +1003,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)
@@ -1268,8 +1011,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 {
@@ -1373,12 +1114,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if case let .message(message, _) = self.item?.contentInfo, let _ = message.adAttribute {
adMessage = message
}
self.openMoreMenu(sourceNode: self.moreBarButton.referenceNode, gesture: gesture, adMessage: adMessage, isSettings: false)
self.openMoreMenu(sourceView: self.moreBarButton.referenceNode.view, gesture: gesture, adMessage: adMessage, isSettings: false)
}
self.titleContentView = GalleryTitleView(frame: CGRect())
self._titleView.set(.single(self.titleContentView))
let shouldHideControlsSignal: Signal<Void, NoError> = combineLatest(self.isPlayingPromise.get(), self.isInteractingPromise.get(), self.controlsVisiblePromise.get(), self.isShowingContextMenuPromise.get(), self.isShowingSettingsMenuPromise.get(), self.isShowingAdMenuPromise.get(), self.hasExpandedCaptionPromise.get())
|> mapToSignal { isPlaying, isInteracting, controlsVisible, isShowingContextMenu, isShowingSettingsMenu, isShowingAdMenu, hasExpandedCaptionPromise -> Signal<Void, NoError> in
if isShowingContextMenu || isShowingSettingsMenu || isShowingAdMenu || hasExpandedCaptionPromise {
@@ -1403,7 +1141,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
deinit {
self.statusDisposable.dispose()
self.moreButtonStateDisposable.dispose()
self.settingsButtonStateDisposable.dispose()
self.mediaPlaybackStateDisposable.dispose()
self.scrubbingFrameDisposable?.dispose()
self.hideControlsDisposable?.dispose()
@@ -1451,6 +1188,60 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
transition.updateFrame(node: self.statusButtonNode, frame: statusFrame)
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(), size: statusFrame.size))
let hideControls = self.item?.hideControls ?? true
var playbackControlsIsPlaying: Bool = false
var playbackControlsIsSeekable: Bool = false
var playbackControlsIsVisible: Bool = false
if case let .playback(paused, seekable) = self.footerContentNode.content {
playbackControlsIsVisible = true
playbackControlsIsSeekable = seekable
playbackControlsIsPlaying = !paused
}
if !self.areControlsVisible || hideControls {
playbackControlsIsVisible = false
}
let playbackControlsSize = self.playbackControls.update(
transition: ComponentTransition(transition),
component: AnyComponent(VideoPlaybackControlsComponent(
layoutParams: VideoPlaybackControlsComponent.LayoutParams(
sideButtonSize: 64.0,
centerButtonSize: 92.0,
spacing: 30.0
),
isVisible: playbackControlsIsVisible,
isPlaying: playbackControlsIsPlaying,
displaySeekControls: playbackControlsIsSeekable,
togglePlayback: { [weak self] in
guard let self else {
return
}
self.footerContentNode.playbackControlPressed()
},
seek: { [weak self] isForward in
guard let self else {
return
}
if isForward {
self.footerContentNode.seekForward?(15.0)
} else {
self.footerContentNode.seekBackward?(15.0)
}
}
)),
environment: {},
containerSize: CGSize(width: 1000.0, height: 1000.0)
)
let playbackControlsFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - playbackControlsSize.width) / 2.0), y: floor((layout.size.height - playbackControlsSize.height) / 2.0)), size: playbackControlsSize)
if let playbackControlsView = self.playbackControls.view {
if playbackControlsView.superview == nil {
self.view.addSubview(playbackControlsView)
}
let transition = ComponentTransition(transition)
transition.setFrame(view: playbackControlsView, frame: playbackControlsFrame)
}
if let pictureInPictureNode = self.pictureInPictureNode {
if let item = self.item {
var placeholderSize = item.content.dimensions.fitted(layout.size)
@@ -1822,15 +1613,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)
@@ -1894,11 +1697,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)
}
}
}
}
@@ -1953,44 +1758,56 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
strongSelf.statusButtonNode.isHidden = strongSelf.hideStatusNodeUntilCentrality || strongSelf.statusNodeShouldBeHidden
}
let footerContent: ChatItemGalleryFooterContent
if isAnimated || disablePlayerControls {
strongSelf.footerContentNode.content = .info
footerContent = .info
} else if isPaused && !strongSelf.ignorePauseStatus && strongSelf.isCentral == true {
if hasStarted || strongSelf.didPause {
strongSelf.footerContentNode.content = .playback(paused: true, seekable: seekable)
footerContent = .playback(paused: true, seekable: seekable)
} else if let fetchStatus = fetchStatus, !strongSelf.requiresDownload {
if item.content is HLSVideoContent {
strongSelf.footerContentNode.content = .playback(paused: true, seekable: seekable)
footerContent = .playback(paused: true, seekable: seekable)
} else {
strongSelf.footerContentNode.content = .fetch(status: fetchStatus, seekable: seekable)
footerContent = .fetch(status: fetchStatus, seekable: seekable)
}
} else {
footerContent = .info
}
} else {
strongSelf.footerContentNode.content = .playback(paused: false, seekable: seekable)
footerContent = .playback(paused: false, seekable: seekable)
}
if strongSelf.footerContentNode.content != footerContent {
strongSelf.footerContentNode.content = footerContent
if let validLayout = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(validLayout.layout, navigationBarHeight: validLayout.navigationBarHeight, transition: .animated(duration: 0.2, curve: .easeInOut))
}
}
}
}))
self.zoomableContent = (videoSize, videoNode)
var hasSettingsButton = false
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) {
let rightBarButtonItem = UIBarButtonItem(image: pictureInPictureButtonImage, style: .plain, target: self, action: #selector(self.pictureInPictureButtonPressed))
/*let rightBarButtonItem = UIBarButtonItem(image: pictureInPictureButtonImage, style: .plain, target: self, action: #selector(self.pictureInPictureButtonPressed))
rightBarButtonItem.accessibilityLabel = self.presentationData.strings.Gallery_VoiceOver_PictureInPicture
self.pictureInPictureButton = rightBarButtonItem
barButtonItems.append(rightBarButtonItem)
barButtonItems.append(rightBarButtonItem)*/
self.hasPictureInPicture = true
} else {
self.hasPictureInPicture = false
}
if let contentInfo = item.contentInfo, case let .message(message, mediaIndex) = contentInfo {
var hasMoreButton = false
var file: TelegramMediaFile?
for m in message.media {
if let m = m as? TelegramMediaFile, m.isVideo {
@@ -2009,7 +1826,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
var hasMoreButton = false
if isEnhancedWebPlayer {
hasMoreButton = true
} else if let file = file, !file.isAnimated {
@@ -2025,9 +1841,7 @@ 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
}
if hasMoreButton {
@@ -2037,6 +1851,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
if hasSettingsButton {
self.settingsButtonState = ChatItemGalleryFooterContentNode.SettingsButtonState(
speed: nil,
quality: nil
)
}
self._rightBarButtonItems.set(.single(barButtonItems))
videoNode.playbackCompleted = { [weak self, weak videoNode] in
@@ -2091,7 +1911,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)
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)
}
@@ -2115,18 +1935,42 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
self.overlayContentNode.presentPremiumDemo = { [weak self] in
self?.presentPremiumDemo()
}
self.overlayContentNode.openMoreMenu = { [weak self] sourceNode, adMessage in
self?.openMoreMenu(sourceNode: sourceNode, gesture: nil, adMessage: adMessage, isSettings: false, actionsOnTop: true)
self.overlayContentNode.openMoreMenu = { [weak self] sourceView, adMessage in
self?.openMoreMenu(sourceView: sourceView, gesture: nil, adMessage: adMessage, isSettings: false, actionsOnTop: true)
}
self.overlayContentNode.setMessage(context: item.context, message: message)
}
var title: String?
if let indexData = item.indexData {
title = self.presentationData.strings.Items_NOfM("\(indexData.position + 1)", "\(indexData.totalCount)").string
} else if case let .message(message, _) = item.contentInfo, let _ = message.adAttribute {
title = self.presentationData.strings.Gallery_Ad
}
if case let .message(message, _) = item.contentInfo {
self._titleContent.set(.single(GalleryTitleView.Content(message: EngineMessage(message), title: title, action: message.adAttribute == nil ? { [weak self] in
guard let self else {
return
}
guard let controller = self.galleryController() as? GalleryController else {
return
}
controller.dismissAndNavigateToMessageContext(message: message)
} : nil)))
}
}
override func controlsVisibilityUpdated(isVisible: Bool) {
self.areControlsVisible = isVisible
self.controlsVisiblePromise.set(isVisible)
self.videoNode?.isUserInteractionEnabled = isVisible ? self.videoNodeUserInteractionEnabled : false
self.videoNode?.notifyPlaybackControlsHidden(!isVisible)
if let validLayout = self.validLayout {
self.containerLayoutUpdated(validLayout.layout, navigationBarHeight: validLayout.navigationBarHeight, transition: .animated(duration: 0.2, curve: .easeInOut))
}
}
private func updateDisplayPlaceholder() {
@@ -2546,6 +2390,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
self.statusButtonNode.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: self.statusButtonNode.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
self.statusButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
self.statusButtonNode.layer.animateScale(from: 0.5, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
if let playbackControlsView = self.playbackControls.view {
playbackControlsView.layer.animatePosition(from: CGPoint(x: transformedSuperFrame.midX, y: transformedSuperFrame.midY), to: playbackControlsView.layer.position, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
playbackControlsView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
playbackControlsView.layer.animateScale(from: 0.2, to: 1.0, duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring)
}
}
}
@@ -2637,6 +2487,13 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
self.statusButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
self.statusButtonNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false)
if let playbackControlsView = self.playbackControls.view {
playbackControlsView.layer.animatePosition(from: playbackControlsView.layer.position, to: CGPoint(x: transformedSelfFrame.midX, y: transformedSelfFrame.midY), duration: 0.25, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in
})
playbackControlsView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false)
playbackControlsView.layer.animateScale(from: 1.0, to: 0.2, duration: 0.25, removeOnCompletion: false)
}
let fromTransform: CATransform3D
let toTransform: CATransform3D
@@ -2862,6 +2719,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
override func maybePerformActionForSwipeDismiss() -> Bool {
if #available(iOS 15.0, *) {
if SGSimpleSettings.shared.videoPIPSwipeDirection != SGSimpleSettings.VideoPIPSwipeDirection.up.rawValue {
return false
}
}
if let data = self.context.currentAppConfiguration.with({ $0 }).data {
if let _ = data["ios_killswitch_disable_swipe_pip"] {
return false
@@ -2899,8 +2762,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
return self._title.get()
}
override func titleView() -> Signal<UIView?, NoError> {
return self._titleView.get()
override func titleContent() -> Signal<GalleryTitleView.Content?, NoError> {
return self._titleContent.get()
}
override func rightBarButtonItems() -> Signal<[UIBarButtonItem]?, NoError> {
@@ -3036,7 +2899,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)
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)
}
@@ -3237,7 +3100,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
private var playOnDismiss = false
private func openMoreMenu(sourceNode: ContextReferenceContentNode, gesture: ContextGesture?, adMessage: Message?, isSettings: Bool, actionsOnTop: Bool = false) {
private func openMoreMenu(sourceView: UIView, gesture: ContextGesture?, adMessage: Message?, isSettings: Bool, actionsOnTop: Bool = false) {
guard let controller = self.baseNavigationController()?.topViewController as? ViewController else {
return
}
@@ -3254,7 +3117,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
})
}
let contextController = ContextController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: sourceNode, actionsOnTop: actionsOnTop)), items: items |> map { items in
let sourceView = sourceView
_ = isSettings
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 {
@@ -3761,16 +3627,16 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
f(.default)
})))
}
// if #available(iOS 11.0, *) {
// items.append(.action(ContextMenuActionItem(text: "AirPlay", textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/AirPlay"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
// f(.default)
// guard let strongSelf = self else {
// return
// }
// strongSelf.beginAirPlaySetup()
// })))
// }
// MARK: Swiftgram
if #available(iOS 11.0, *) {
items.append(.action(ContextMenuActionItem(text: "AirPlay", textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/AirPlay"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
f(.default)
guard let strongSelf = self else {
return
}
strongSelf.beginAirPlaySetup()
})))
}
if let (message, _, _) = strongSelf.contentInfo() {
for media in message.media {
@@ -3912,8 +3778,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
})
}
@objc private func settingsButtonPressed() {
self.openMoreMenu(sourceNode: self.settingsBarButton.referenceNode, gesture: nil, adMessage: nil, isSettings: true)
func settingsButtonPressed(sourceView: UIView) {
self.openMoreMenu(sourceView: sourceView, gesture: nil, adMessage: nil, isSettings: true, actionsOnTop: true)
}
override func adjustForPreviewing() {
@@ -4087,17 +3953,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)
}
}