mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Modernize video playback UI
This commit is contained in:
parent
fed038104e
commit
7161c34527
@ -18,10 +18,10 @@ public final class GalleryControllerActionInteraction {
|
||||
public let openHashtag: (String?, String) -> Void
|
||||
public let openBotCommand: (String) -> Void
|
||||
public let addContact: (String) -> Void
|
||||
public let storeMediaPlaybackState: (MessageId, Double?) -> Void
|
||||
public let storeMediaPlaybackState: (MessageId, Double?, Double) -> Void
|
||||
public let editMedia: (MessageId, [UIView], @escaping () -> Void) -> Void
|
||||
|
||||
public init(openUrl: @escaping (String, Bool) -> Void, openUrlIn: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void, openPeer: @escaping (PeerId) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, addContact: @escaping (String) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void, editMedia: @escaping (MessageId, [UIView], @escaping () -> Void) -> Void) {
|
||||
public init(openUrl: @escaping (String, Bool) -> Void, openUrlIn: @escaping (String) -> Void, openPeerMention: @escaping (String) -> Void, openPeer: @escaping (PeerId) -> Void, openHashtag: @escaping (String?, String) -> Void, openBotCommand: @escaping (String) -> Void, addContact: @escaping (String) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?, Double) -> Void, editMedia: @escaping (MessageId, [UIView], @escaping () -> Void) -> Void) {
|
||||
self.openUrl = openUrl
|
||||
self.openUrlIn = openUrlIn
|
||||
self.openPeerMention = openPeerMention
|
||||
|
@ -239,7 +239,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
|
||||
var presentationData: NavigationBarPresentationData
|
||||
|
||||
private var validLayout: (size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool)?
|
||||
private var validLayout: (size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool)?
|
||||
private var requestedLayout: Bool = false
|
||||
var requestContainerLayout: (ContainedViewLayoutTransition) -> Void = { _ in }
|
||||
|
||||
@ -940,16 +940,16 @@ open class NavigationBar: ASDisplayNode {
|
||||
|
||||
if let validLayout = self.validLayout, self.requestedLayout {
|
||||
self.requestedLayout = false
|
||||
self.updateLayout(size: validLayout.size, defaultHeight: validLayout.defaultHeight, additionalTopHeight: validLayout.additionalTopHeight, additionalContentHeight: validLayout.additionalContentHeight, additionalBackgroundHeight: validLayout.additionalBackgroundHeight, leftInset: validLayout.leftInset, rightInset: validLayout.rightInset, appearsHidden: validLayout.appearsHidden, transition: .immediate)
|
||||
self.updateLayout(size: validLayout.size, defaultHeight: validLayout.defaultHeight, additionalTopHeight: validLayout.additionalTopHeight, additionalContentHeight: validLayout.additionalContentHeight, additionalBackgroundHeight: validLayout.additionalBackgroundHeight, leftInset: validLayout.leftInset, rightInset: validLayout.rightInset, appearsHidden: validLayout.appearsHidden, isLandscape: validLayout.isLandscape, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, transition: ContainedViewLayoutTransition) {
|
||||
func updateLayout(size: CGSize, defaultHeight: CGFloat, additionalTopHeight: CGFloat, additionalContentHeight: CGFloat, additionalBackgroundHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, appearsHidden: Bool, isLandscape: Bool, transition: ContainedViewLayoutTransition) {
|
||||
if self.layoutSuspended {
|
||||
return
|
||||
}
|
||||
|
||||
self.validLayout = (size, defaultHeight, additionalTopHeight, additionalContentHeight, additionalBackgroundHeight, leftInset, rightInset, appearsHidden)
|
||||
self.validLayout = (size, defaultHeight, additionalTopHeight, additionalContentHeight, additionalBackgroundHeight, leftInset, rightInset, appearsHidden, isLandscape)
|
||||
|
||||
let backgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height + additionalBackgroundHeight))
|
||||
if self.backgroundNode.frame != backgroundFrame {
|
||||
@ -995,7 +995,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
var leftTitleInset: CGFloat = leftInset + 1.0
|
||||
var rightTitleInset: CGFloat = rightInset + 1.0
|
||||
if self.backButtonNode.supernode != nil {
|
||||
let backButtonSize = self.backButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight))
|
||||
let backButtonSize = self.backButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape)
|
||||
leftTitleInset += backButtonSize.width + backButtonInset + 1.0
|
||||
|
||||
let topHitTestSlop = (nominalHeight - backButtonSize.height) * 0.5
|
||||
@ -1047,7 +1047,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
self.badgeNode.alpha = 1.0
|
||||
}
|
||||
} else if self.leftButtonNode.supernode != nil {
|
||||
let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight))
|
||||
let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape)
|
||||
leftTitleInset += leftButtonSize.width + leftButtonInset + 1.0
|
||||
|
||||
self.leftButtonNode.alpha = 1.0
|
||||
@ -1059,7 +1059,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
transition.updateFrame(node: self.badgeNode, frame: CGRect(origin: backButtonArrowFrame.origin.offsetBy(dx: 7.0, dy: -9.0), size: badgeSize))
|
||||
|
||||
if self.rightButtonNode.supernode != nil {
|
||||
let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)))
|
||||
let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight)), isLandscape: isLandscape)
|
||||
rightTitleInset += rightButtonSize.width + leftButtonInset + 1.0
|
||||
self.rightButtonNode.alpha = 1.0
|
||||
transition.updateFrame(node: self.rightButtonNode, frame: CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize))
|
||||
@ -1073,7 +1073,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
break
|
||||
case .bottom:
|
||||
if let transitionBackButtonNode = self.transitionBackButtonNode {
|
||||
let transitionBackButtonSize = transitionBackButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight))
|
||||
let transitionBackButtonSize = transitionBackButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight), isLandscape: isLandscape)
|
||||
let initialX: CGFloat = backButtonInset + size.width * 0.3
|
||||
let finalX: CGFloat = floor((size.width - transitionBackButtonSize.width) / 2.0)
|
||||
|
||||
@ -1204,7 +1204,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
node.updateManualText(self.backButtonNode.manualText)
|
||||
node.color = accentColor
|
||||
if let validLayout = self.validLayout {
|
||||
let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight))
|
||||
let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape)
|
||||
node.frame = self.backButtonNode.frame
|
||||
}
|
||||
return node
|
||||
@ -1227,7 +1227,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
node.updateItems(items)
|
||||
node.color = accentColor
|
||||
if let validLayout = self.validLayout {
|
||||
let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight))
|
||||
let _ = node.updateLayout(constrainedSize: CGSize(width: validLayout.size.width, height: validLayout.defaultHeight), isLandscape: validLayout.isLandscape)
|
||||
node.frame = self.backButtonNode.frame
|
||||
}
|
||||
return node
|
||||
|
@ -255,8 +255,6 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
|
||||
|
||||
public override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
//self.updateHighlightedState(self.touchInsideApparentBounds(touches.first!), animated: true)
|
||||
}
|
||||
|
||||
public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
@ -274,6 +272,15 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
|
||||
self.pressed()
|
||||
}
|
||||
}
|
||||
|
||||
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let node = self.node as? HighlightableButtonNode {
|
||||
let result = node.view.hitTest(self.view.convert(point, to: node.view), with: event)
|
||||
return result
|
||||
} else {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
}
|
||||
|
||||
public override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
|
||||
super.touchesCancelled(touches, with: event)
|
||||
@ -453,7 +460,7 @@ public final class NavigationButtonNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(constrainedSize: CGSize) -> CGSize {
|
||||
public func updateLayout(constrainedSize: CGSize, isLandscape: Bool) -> CGSize {
|
||||
var nodeOrigin = CGPoint()
|
||||
var totalSize = CGSize()
|
||||
for node in self.nodes {
|
||||
@ -468,6 +475,9 @@ public final class NavigationButtonNode: ASDisplayNode {
|
||||
totalSize.height = max(totalSize.height, nodeSize.height)
|
||||
node.frame = CGRect(origin: CGPoint(x: nodeOrigin.x, y: floor((totalSize.height - nodeSize.height) / 2.0)), size: nodeSize)
|
||||
nodeOrigin.x += node.bounds.width
|
||||
if isLandscape {
|
||||
nodeOrigin.x += 16.0
|
||||
}
|
||||
}
|
||||
return totalSize
|
||||
}
|
||||
|
@ -381,6 +381,8 @@ public enum TabBarItemContextActionType {
|
||||
}
|
||||
|
||||
self.navigationBarOrigin = navigationBarFrame.origin.y
|
||||
|
||||
let isLandscape = layout.size.width > layout.size.height
|
||||
|
||||
if let navigationBar = self.navigationBar {
|
||||
if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode, !self.displayNavigationBar {
|
||||
@ -392,7 +394,7 @@ public enum TabBarItemContextActionType {
|
||||
navigationBarFrame.size.height += NavigationBar.defaultSecondaryContentHeight
|
||||
//navigationBarFrame.origin.y += NavigationBar.defaultSecondaryContentHeight
|
||||
}
|
||||
navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: navigationLayout.defaultContentHeight, additionalTopHeight: statusBarHeight, additionalContentHeight: self.additionalNavigationBarHeight, additionalBackgroundHeight: additionalBackgroundHeight, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, appearsHidden: !self.displayNavigationBar, transition: transition)
|
||||
navigationBar.updateLayout(size: navigationBarFrame.size, defaultHeight: navigationLayout.defaultContentHeight, additionalTopHeight: statusBarHeight, additionalContentHeight: self.additionalNavigationBarHeight, additionalBackgroundHeight: additionalBackgroundHeight, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, appearsHidden: !self.displayNavigationBar, isLandscape: isLandscape, transition: transition)
|
||||
if !transition.isAnimated {
|
||||
navigationBar.layer.cancelAnimationsRecursive(key: "bounds")
|
||||
navigationBar.layer.cancelAnimationsRecursive(key: "position")
|
||||
|
@ -230,20 +230,20 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati
|
||||
let gallery = SecretMediaPreviewController(context: context, messageId: message.id)
|
||||
return .secretGallery(gallery)
|
||||
} else {
|
||||
let startTimecode: Signal<Double?, NoError>
|
||||
let startState: Signal<(timecode: Double?, rate: Double), NoError>
|
||||
if let timecode = timecode {
|
||||
startTimecode = .single(timecode)
|
||||
startState = .single((timecode: timecode, rate: 1.0))
|
||||
} else {
|
||||
startTimecode = mediaPlaybackStoredState(postbox: context.account.postbox, messageId: message.id)
|
||||
startState = mediaPlaybackStoredState(postbox: context.account.postbox, messageId: message.id)
|
||||
|> map { state in
|
||||
return state?.timestamp
|
||||
return (state?.timestamp, state?.playbackRate.doubleValue ?? 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
return .gallery(startTimecode
|
||||
return .gallery(startState
|
||||
|> deliverOnMainQueue
|
||||
|> map { timecode in
|
||||
let gallery = GalleryController(context: context, source: source ?? (standalone ? .standaloneMessage(message) : .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil))), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: timecode, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
|
||||
|> map { startState in
|
||||
let gallery = GalleryController(context: context, source: source ?? (standalone ? .standaloneMessage(message) : .peerMessagesAtId(messageId: message.id, chatLocation: chatLocation ?? .peer(message.id.peerId), chatLocationContextHolder: chatLocationContextHolder ?? Atomic<ChatLocationContextHolder?>(value: nil))), invertItemOrder: reverseMessageGalleryOrder, streamSingleVideo: stream, fromPlayingVideo: autoplayingVideo, landscape: landscape, timecode: startState.timecode, playbackRate: startState.rate, synchronousLoad: synchronousLoad, replaceRootController: { [weak navigationController] controller, ready in
|
||||
navigationController?.replaceTopController(controller, animated: false, ready: ready)
|
||||
}, baseNavigationController: navigationController, actionInteraction: actionInteraction)
|
||||
gallery.temporaryDoNotWaitForReady = autoplayingVideo
|
||||
|
@ -28,7 +28,10 @@ swift_library(
|
||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/UrlEscaping:UrlEscaping",
|
||||
"//submodules/ManagedAnimationNode:ManagedAnimationNode"
|
||||
"//submodules/ManagedAnimationNode:ManagedAnimationNode",
|
||||
"//submodules/ContextUI:ContextUI",
|
||||
"//submodules/SaveToCameraRoll:SaveToCameraRoll",
|
||||
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -31,6 +31,9 @@ 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)
|
||||
@ -119,6 +122,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
private let contentNode: ASDisplayNode
|
||||
private let deleteButton: UIButton
|
||||
private let fullscreenButton: UIButton
|
||||
private let actionButton: UIButton
|
||||
private let editButton: UIButton
|
||||
private let maskNode: ASDisplayNode
|
||||
@ -152,6 +156,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
var seekBackward: ((Double) -> Void)?
|
||||
var seekForward: ((Double) -> Void)?
|
||||
var setPlayRate: ((Double) -> Void)?
|
||||
var toggleFullscreen: (() -> Void)?
|
||||
var fetchControl: (() -> Void)?
|
||||
|
||||
var interacting: ((Bool) -> Void)?
|
||||
@ -286,6 +291,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.contentNode = ASDisplayNode()
|
||||
|
||||
self.deleteButton = UIButton()
|
||||
self.fullscreenButton = UIButton()
|
||||
self.actionButton = UIButton()
|
||||
self.editButton = UIButton()
|
||||
|
||||
@ -363,6 +369,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
}
|
||||
|
||||
self.contentNode.view.addSubview(self.deleteButton)
|
||||
self.contentNode.view.addSubview(self.fullscreenButton)
|
||||
self.contentNode.view.addSubview(self.actionButton)
|
||||
self.contentNode.view.addSubview(self.editButton)
|
||||
self.contentNode.addSubnode(self.scrollWrapperNode)
|
||||
@ -381,6 +388,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.contentNode.addSubnode(self.statusButtonNode)
|
||||
|
||||
self.deleteButton.addTarget(self, action: #selector(self.deleteButtonPressed), for: [.touchUpInside])
|
||||
self.fullscreenButton.addTarget(self, action: #selector(self.fullscreenButtonPressed), for: [.touchUpInside])
|
||||
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), for: [.touchUpInside])
|
||||
self.editButton.addTarget(self, action: #selector(self.editButtonPressed), for: [.touchUpInside])
|
||||
|
||||
@ -559,6 +567,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
if origin == nil {
|
||||
self.editButton.isHidden = true
|
||||
self.deleteButton.isHidden = true
|
||||
self.fullscreenButton.isHidden = true
|
||||
self.editButton.isHidden = true
|
||||
}
|
||||
}
|
||||
@ -568,12 +577,22 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
let canDelete: Bool
|
||||
var canShare = !message.containsSecretMedia
|
||||
|
||||
var canFullscreen = false
|
||||
|
||||
var canEdit = false
|
||||
for media in message.media {
|
||||
if media is TelegramMediaImage {
|
||||
canEdit = true
|
||||
break
|
||||
} else if let media = media as? TelegramMediaFile, !media.isAnimated {
|
||||
for attribute in media.attributes {
|
||||
switch attribute {
|
||||
case .Video:
|
||||
canFullscreen = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -637,7 +656,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
messageText = galleryCaptionStringWithAppliedEntities(message.text, entities: entities)
|
||||
}
|
||||
|
||||
if self.currentMessageText != messageText || canDelete != !self.deleteButton.isHidden || canShare != !self.actionButton.isHidden || canEdit != !self.editButton.isHidden || self.currentAuthorNameText != authorNameText || self.currentDateText != dateText {
|
||||
if self.currentMessageText != messageText || canDelete != !self.deleteButton.isHidden || canFullscreen != !self.fullscreenButton.isHidden || canShare != !self.actionButton.isHidden || canEdit != !self.editButton.isHidden || self.currentAuthorNameText != authorNameText || self.currentDateText != dateText {
|
||||
self.currentMessageText = messageText
|
||||
|
||||
if messageText.length == 0 {
|
||||
@ -654,8 +673,15 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.authorNameNode.attributedText = nil
|
||||
}
|
||||
self.dateNode.attributedText = NSAttributedString(string: dateText, font: dateFont, textColor: .white)
|
||||
|
||||
self.deleteButton.isHidden = !canDelete
|
||||
|
||||
if canFullscreen {
|
||||
self.fullscreenButton.isHidden = false
|
||||
self.deleteButton.isHidden = true
|
||||
} else {
|
||||
self.deleteButton.isHidden = !canDelete
|
||||
self.fullscreenButton.isHidden = true
|
||||
}
|
||||
|
||||
self.actionButton.isHidden = !canShare
|
||||
self.editButton.isHidden = !canEdit
|
||||
|
||||
@ -683,6 +709,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
panelHeight += contentInset
|
||||
|
||||
let isLandscape = size.width > size.height
|
||||
|
||||
self.fullscreenButton.setImage(isLandscape ? fullscreenOffImage : fullscreenOnImage, for: [.normal])
|
||||
|
||||
let displayCaption: Bool
|
||||
if case .compact = metrics.widthClass {
|
||||
displayCaption = !self.textNode.isHidden && !isLandscape
|
||||
@ -776,10 +805,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
|
||||
let deleteFrame = CGRect(origin: CGPoint(x: width - 44.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
var editFrame = CGRect(origin: CGPoint(x: width - 44.0 - 50.0 - rightInset, y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
if self.deleteButton.isHidden {
|
||||
if self.deleteButton.isHidden && self.fullscreenButton.isHidden {
|
||||
editFrame = deleteFrame
|
||||
}
|
||||
self.deleteButton.frame = deleteFrame
|
||||
self.fullscreenButton.frame = deleteFrame
|
||||
self.editButton.frame = editFrame
|
||||
|
||||
if let image = self.backwardButton.backgroundIconNode.image {
|
||||
@ -789,7 +819,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.forwardButton.frame = CGRect(origin: CGPoint(x: floor((width - image.size.width) / 2.0) + 66.0, y: panelHeight - bottomInset - 44.0 + 7.0), size: image.size)
|
||||
}
|
||||
|
||||
self.playbackControlButton.frame = CGRect(origin: CGPoint(x: floor((width - 44.0) / 2.0), y: panelHeight - bottomInset - 44.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.playbackControlButton.frame = CGRect(origin: CGPoint(x: floor((width - 44.0) / 2.0), y: panelHeight - bottomInset - 44.0 - 2.0), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.playPauseIconNode.frame = self.playbackControlButton.bounds.offsetBy(dx: 2.0, dy: 2.0)
|
||||
|
||||
let statusSize = CGSize(width: 28.0, height: 28.0)
|
||||
@ -855,6 +885,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.dateNode.alpha = 1.0
|
||||
self.authorNameNode.alpha = 1.0
|
||||
self.deleteButton.alpha = 1.0
|
||||
self.fullscreenButton.alpha = 1.0
|
||||
self.actionButton.alpha = 1.0
|
||||
self.editButton.alpha = 1.0
|
||||
self.backwardButton.alpha = 1.0
|
||||
@ -878,6 +909,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
self.dateNode.alpha = 0.0
|
||||
self.authorNameNode.alpha = 0.0
|
||||
self.deleteButton.alpha = 0.0
|
||||
self.fullscreenButton.alpha = 0.0
|
||||
self.actionButton.alpha = 0.0
|
||||
self.editButton.alpha = 0.0
|
||||
self.backwardButton.alpha = 0.0
|
||||
@ -888,6 +920,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
@objc func fullscreenButtonPressed() {
|
||||
self.toggleFullscreen?()
|
||||
}
|
||||
|
||||
@objc func deleteButtonPressed() {
|
||||
if let currentMessage = self.currentMessage {
|
||||
|
@ -145,7 +145,7 @@ private func galleryMessageCaptionText(_ message: Message) -> String {
|
||||
return message.text
|
||||
}
|
||||
|
||||
public func galleryItemForEntry(context: AccountContext, presentationData: PresentationData, entry: MessageHistoryEntry, isCentral: Bool = false, streamVideos: Bool, loopVideos: Bool = false, hideControls: Bool = false, fromPlayingVideo: Bool = false, isSecret: Bool = false, landscape: Bool = false, timecode: Double? = nil, displayInfoOnTop: Bool = false, configuration: GalleryConfiguration? = nil, tempFilePath: String? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void = { _, _ in }, present: @escaping (ViewController, Any?) -> Void) -> GalleryItem? {
|
||||
public func galleryItemForEntry(context: AccountContext, presentationData: PresentationData, entry: MessageHistoryEntry, isCentral: Bool = false, streamVideos: Bool, loopVideos: Bool = false, hideControls: Bool = false, fromPlayingVideo: Bool = false, isSecret: Bool = false, landscape: Bool = false, timecode: Double? = nil, playbackRate: Double = 1.0, displayInfoOnTop: Bool = false, configuration: GalleryConfiguration? = nil, tempFilePath: String? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void = { _ in }, storeMediaPlaybackState: @escaping (MessageId, Double?, Double) -> Void = { _, _, _ in }, present: @escaping (ViewController, Any?) -> Void) -> GalleryItem? {
|
||||
let message = entry.message
|
||||
let location = entry.location
|
||||
if let (media, mediaImage) = mediaForMessage(message: message) {
|
||||
@ -178,7 +178,7 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese
|
||||
}
|
||||
|
||||
let caption = galleryCaptionStringWithAppliedEntities(text, entities: entities)
|
||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: caption, displayInfoOnTop: displayInfoOnTop, hideControls: hideControls, fromPlayingVideo: fromPlayingVideo, isSecret: isSecret, landscape: landscape, timecode: timecode, configuration: configuration, playbackCompleted: playbackCompleted, performAction: performAction, openActionOptions: openActionOptions, storeMediaPlaybackState: storeMediaPlaybackState, present: present)
|
||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: caption, displayInfoOnTop: displayInfoOnTop, hideControls: hideControls, fromPlayingVideo: fromPlayingVideo, isSecret: isSecret, landscape: landscape, timecode: timecode, playbackRate: playbackRate, configuration: configuration, playbackCompleted: playbackCompleted, performAction: performAction, openActionOptions: openActionOptions, storeMediaPlaybackState: storeMediaPlaybackState, present: present)
|
||||
} else {
|
||||
if let fileName = file.fileName, (fileName as NSString).pathExtension.lowercased() == "json" {
|
||||
return ChatAnimationGalleryItem(context: context, presentationData: presentationData, message: message, location: location)
|
||||
@ -188,7 +188,7 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese
|
||||
if let dimensions = file.dimensions {
|
||||
pixelsCount = Int(dimensions.width) * Int(dimensions.height)
|
||||
}
|
||||
if (file.size == nil || file.size! < 4 * 1024 * 1024) && pixelsCount < 4096 * 4096 {
|
||||
if pixelsCount < 10000 * 10000 {
|
||||
return ChatImageGalleryItem(context: context, presentationData: presentationData, message: message, location: location, displayInfoOnTop: displayInfoOnTop, performAction: performAction, openActionOptions: openActionOptions, present: present)
|
||||
} else {
|
||||
return ChatDocumentGalleryItem(context: context, presentationData: presentationData, message: message, location: location)
|
||||
@ -219,7 +219,7 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese
|
||||
}
|
||||
}
|
||||
if let content = content {
|
||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), displayInfoOnTop: displayInfoOnTop, fromPlayingVideo: fromPlayingVideo, isSecret: isSecret, landscape: landscape, timecode: timecode, configuration: configuration, performAction: performAction, openActionOptions: openActionOptions, storeMediaPlaybackState: storeMediaPlaybackState, present: present)
|
||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), timestamp: message.timestamp), indexData: location.flatMap { GalleryItemIndexData(position: Int32($0.index), totalCount: Int32($0.count)) }, contentInfo: .message(message), caption: NSAttributedString(string: ""), displayInfoOnTop: displayInfoOnTop, fromPlayingVideo: fromPlayingVideo, isSecret: isSecret, landscape: landscape, timecode: timecode, playbackRate: playbackRate, configuration: configuration, performAction: performAction, openActionOptions: openActionOptions, storeMediaPlaybackState: storeMediaPlaybackState, present: present)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -349,6 +349,7 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
private let fromPlayingVideo: Bool
|
||||
private let landscape: Bool
|
||||
private let timecode: Double?
|
||||
private let playbackRate: Double
|
||||
|
||||
private let accountInUseDisposable = MetaDisposable()
|
||||
private let disposable = MetaDisposable()
|
||||
@ -388,7 +389,7 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
|
||||
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, synchronousLoad: Bool = false, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, baseNavigationController: NavigationController?, actionInteraction: GalleryControllerActionInteraction? = nil) {
|
||||
public init(context: AccountContext, source: GalleryControllerItemSource, invertItemOrder: Bool = false, streamSingleVideo: Bool = false, fromPlayingVideo: Bool = false, landscape: Bool = false, timecode: Double? = nil, playbackRate: Double = 1.0, synchronousLoad: Bool = false, replaceRootController: @escaping (ViewController, Promise<Bool>?) -> Void, baseNavigationController: NavigationController?, actionInteraction: GalleryControllerActionInteraction? = nil) {
|
||||
self.context = context
|
||||
self.source = source
|
||||
self.invertItemOrder = invertItemOrder
|
||||
@ -399,6 +400,7 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
self.fromPlayingVideo = fromPlayingVideo
|
||||
self.landscape = landscape
|
||||
self.timecode = timecode
|
||||
self.playbackRate = playbackRate
|
||||
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
@ -537,7 +539,7 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
if entry.message.stableId == strongSelf.centralEntryStableId {
|
||||
isCentral = true
|
||||
}
|
||||
if let item = galleryItemForEntry(context: context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: streamSingleVideo, fromPlayingVideo: isCentral && fromPlayingVideo, landscape: isCentral && landscape, timecode: isCentral ? timecode : nil, displayInfoOnTop: displayInfoOnTop, configuration: configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
|
||||
if let item = galleryItemForEntry(context: context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: streamSingleVideo, fromPlayingVideo: isCentral && fromPlayingVideo, landscape: isCentral && landscape, timecode: isCentral ? timecode : nil, playbackRate: isCentral ? playbackRate : 1.0, displayInfoOnTop: displayInfoOnTop, configuration: configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _, _ in }, present: { [weak self] c, a in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentInGlobalOverlay(c, with: a)
|
||||
}
|
||||
@ -1040,6 +1042,9 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
self.galleryNode.baseNavigationController = { [weak baseNavigationController] in
|
||||
return baseNavigationController
|
||||
}
|
||||
self.galleryNode.galleryController = { [weak self] in
|
||||
return self
|
||||
}
|
||||
|
||||
var displayInfoOnTop = false
|
||||
if case .custom = source {
|
||||
@ -1053,7 +1058,7 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
if entry.message.stableId == self.centralEntryStableId {
|
||||
isCentral = true
|
||||
}
|
||||
if let item = galleryItemForEntry(context: self.context, presentationData: self.presentationData, entry: entry, streamVideos: self.streamVideos, fromPlayingVideo: isCentral && self.fromPlayingVideo, landscape: isCentral && self.landscape, timecode: isCentral ? self.timecode : nil, displayInfoOnTop: displayInfoOnTop, configuration: self.configuration, performAction: self.performAction, openActionOptions: self.openActionOptions, storeMediaPlaybackState: self.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
|
||||
if let item = galleryItemForEntry(context: self.context, presentationData: self.presentationData, entry: entry, streamVideos: self.streamVideos, fromPlayingVideo: isCentral && self.fromPlayingVideo, landscape: isCentral && self.landscape, timecode: isCentral ? self.timecode : nil, playbackRate: isCentral ? self.playbackRate : 1.0, displayInfoOnTop: displayInfoOnTop, configuration: self.configuration, performAction: self.performAction, openActionOptions: self.openActionOptions, storeMediaPlaybackState: self.actionInteraction?.storeMediaPlaybackState ?? { _, _, _ in }, present: { [weak self] c, a in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentInGlobalOverlay(c, with: a)
|
||||
}
|
||||
@ -1133,7 +1138,7 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
if entry.message.stableId == strongSelf.centralEntryStableId {
|
||||
isCentral = true
|
||||
}
|
||||
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, displayInfoOnTop: displayInfoOnTop, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
|
||||
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, displayInfoOnTop: displayInfoOnTop, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _, _ in }, present: { [weak self] c, a in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentInGlobalOverlay(c, with: a)
|
||||
}
|
||||
@ -1185,7 +1190,7 @@ public class GalleryController: ViewController, StandalonePresentableController
|
||||
if entry.message.stableId == strongSelf.centralEntryStableId {
|
||||
isCentral = true
|
||||
}
|
||||
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, displayInfoOnTop: displayInfoOnTop, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _ in }, present: { [weak self] c, a in
|
||||
if let item = galleryItemForEntry(context: strongSelf.context, presentationData: strongSelf.presentationData, entry: entry, isCentral: isCentral, streamVideos: false, fromPlayingVideo: isCentral && strongSelf.fromPlayingVideo, landscape: isCentral && strongSelf.landscape, timecode: isCentral ? strongSelf.timecode : nil, displayInfoOnTop: displayInfoOnTop, configuration: strongSelf.configuration, performAction: strongSelf.performAction, openActionOptions: strongSelf.openActionOptions, storeMediaPlaybackState: strongSelf.actionInteraction?.storeMediaPlaybackState ?? { _, _, _ in }, present: { [weak self] c, a in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentInGlobalOverlay(c, with: a)
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
|
||||
public var beginCustomDismiss: () -> Void = { }
|
||||
public var completeCustomDismiss: () -> Void = { }
|
||||
public var baseNavigationController: () -> NavigationController? = { return nil }
|
||||
public var galleryController: () -> ViewController? = { return nil }
|
||||
|
||||
private var presentationState = GalleryControllerPresentationState()
|
||||
|
||||
@ -120,6 +121,9 @@ open class GalleryControllerNode: ASDisplayNode, UIScrollViewDelegate, UIGesture
|
||||
self.pager.baseNavigationController = { [weak self] in
|
||||
return self?.baseNavigationController()
|
||||
}
|
||||
self.pager.galleryController = { [weak self] in
|
||||
return self?.galleryController()
|
||||
}
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
|
||||
|
@ -27,6 +27,7 @@ open class GalleryItemNode: ASDisplayNode {
|
||||
public var beginCustomDismiss: () -> Void = { }
|
||||
public var completeCustomDismiss: () -> Void = { }
|
||||
public var baseNavigationController: () -> NavigationController? = { return nil }
|
||||
public var galleryController: () -> ViewController? = { return nil }
|
||||
public var alternativeDismiss: () -> Bool = { return false }
|
||||
|
||||
override public init() {
|
||||
|
@ -112,6 +112,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
|
||||
public var beginCustomDismiss: () -> Void = { }
|
||||
public var completeCustomDismiss: () -> Void = { }
|
||||
public var baseNavigationController: () -> NavigationController? = { return nil }
|
||||
public var galleryController: () -> ViewController? = { return nil }
|
||||
|
||||
public init(pageGap: CGFloat, disableTapNavigation: Bool) {
|
||||
self.pageGap = pageGap
|
||||
@ -480,6 +481,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
|
||||
node.beginCustomDismiss = self.beginCustomDismiss
|
||||
node.completeCustomDismiss = self.completeCustomDismiss
|
||||
node.baseNavigationController = self.baseNavigationController
|
||||
node.galleryController = self.galleryController
|
||||
node.index = index
|
||||
return node
|
||||
}
|
||||
|
@ -15,6 +15,11 @@ import PresentationDataUtils
|
||||
import OverlayStatusController
|
||||
import StickerPackPreviewUI
|
||||
import AppBundle
|
||||
import AnimationUI
|
||||
import ContextUI
|
||||
import SaveToCameraRoll
|
||||
import UndoUI
|
||||
import TelegramUIPreferences
|
||||
|
||||
public enum UniversalVideoGalleryItemContentInfo {
|
||||
case message(Message)
|
||||
@ -40,14 +45,15 @@ public class UniversalVideoGalleryItem: GalleryItem {
|
||||
let isSecret: Bool
|
||||
let landscape: Bool
|
||||
let timecode: Double?
|
||||
let playbackRate: Double
|
||||
let configuration: GalleryConfiguration?
|
||||
let playbackCompleted: () -> Void
|
||||
let performAction: (GalleryControllerInteractionTapAction) -> Void
|
||||
let openActionOptions: (GalleryControllerInteractionTapAction) -> Void
|
||||
let storeMediaPlaybackState: (MessageId, Double?) -> Void
|
||||
let storeMediaPlaybackState: (MessageId, Double?, Double) -> Void
|
||||
let present: (ViewController, Any?) -> Void
|
||||
|
||||
public init(context: AccountContext, presentationData: PresentationData, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, contentInfo: UniversalVideoGalleryItemContentInfo?, caption: NSAttributedString, credit: NSAttributedString? = nil, displayInfoOnTop: Bool = false, hideControls: Bool = false, fromPlayingVideo: Bool = false, isSecret: Bool = false, landscape: Bool = false, timecode: Double? = nil, configuration: GalleryConfiguration? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||
public init(context: AccountContext, presentationData: PresentationData, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, contentInfo: UniversalVideoGalleryItemContentInfo?, caption: NSAttributedString, credit: NSAttributedString? = nil, displayInfoOnTop: Bool = false, hideControls: Bool = false, fromPlayingVideo: Bool = false, isSecret: Bool = false, landscape: Bool = false, timecode: Double? = nil, playbackRate: Double = 1.0, configuration: GalleryConfiguration? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?, Double) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.content = content
|
||||
@ -62,6 +68,7 @@ public class UniversalVideoGalleryItem: GalleryItem {
|
||||
self.isSecret = isSecret
|
||||
self.landscape = landscape
|
||||
self.timecode = timecode
|
||||
self.playbackRate = playbackRate
|
||||
self.configuration = configuration
|
||||
self.playbackCompleted = playbackCompleted
|
||||
self.performAction = performAction
|
||||
@ -131,6 +138,8 @@ public class UniversalVideoGalleryItem: GalleryItem {
|
||||
|
||||
private let pictureInPictureImage = UIImage(bundleImageName: "Media Gallery/PictureInPictureIcon")?.precomposed()
|
||||
private let pictureInPictureButtonImage = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/PictureInPictureButton"), color: .white)
|
||||
private let moreButtonImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/More"), color: .white)
|
||||
|
||||
private let placeholderFont = Font.regular(16.0)
|
||||
|
||||
private final class UniversalVideoGalleryItemPictureInPictureNode: ASDisplayNode {
|
||||
@ -253,6 +262,196 @@ private struct FetchControls {
|
||||
let cancel: () -> Void
|
||||
}
|
||||
|
||||
func optionsBackgroundImage(dark: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: 28.0, height: 28.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(UIColor(rgb: dark ? 0x1c1c1e : 0x2c2c2e).cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
})?.stretchableImage(withLeftCapWidth: 14, topCapHeight: 14)
|
||||
}
|
||||
|
||||
private func optionsCircleImage(dark: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: 22.0, height: 22.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))
|
||||
})
|
||||
}
|
||||
|
||||
private func optionsRateImage(rate: String, isLarge: Bool, color: UIColor = .white) -> UIImage? {
|
||||
return generateImage(isLarge ? CGSize(width: 30.0, height: 30.0) : CGSize(width: 24.0, height: 24.0), rotatedContext: { size, context in
|
||||
UIGraphicsPushContext(context)
|
||||
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: isLarge ? "Chat/Context Menu/Playspeed30" : "Chat/Context Menu/Playspeed24"), color: .white) {
|
||||
image.draw(at: CGPoint(x: 0.0, y: 0.0))
|
||||
}
|
||||
|
||||
let string = NSMutableAttributedString(string: rate, font: Font.with(size: isLarge ? 11.0 : 10.0, design: .round, weight: .semibold), textColor: color)
|
||||
|
||||
var offset = CGPoint(x: 1.0, y: 0.0)
|
||||
if rate.count >= 3 {
|
||||
if rate == "0.5x" {
|
||||
string.addAttribute(.kern, value: -0.8 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
|
||||
offset.x += -0.5
|
||||
} else {
|
||||
string.addAttribute(.kern, value: -0.5 as NSNumber, range: NSRange(string.string.startIndex ..< string.string.endIndex, in: string.string))
|
||||
offset.x += -0.3
|
||||
}
|
||||
} else {
|
||||
offset.x += -0.3
|
||||
}
|
||||
|
||||
if !isLarge {
|
||||
offset.x *= 0.5
|
||||
offset.y *= 0.5
|
||||
}
|
||||
|
||||
let boundingRect = string.boundingRect(with: size, options: [], context: nil)
|
||||
string.draw(at: CGPoint(x: offset.x + floor((size.width - boundingRect.width) / 2.0), y: offset.y + floor((size.height - boundingRect.height) / 2.0)))
|
||||
|
||||
UIGraphicsPopContext()
|
||||
})
|
||||
}
|
||||
|
||||
private final class MoreHeaderButton: HighlightableButtonNode {
|
||||
enum Content {
|
||||
case image(UIImage?)
|
||||
case more(UIImage?)
|
||||
}
|
||||
|
||||
let referenceNode: ContextReferenceContentNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
private let iconNode: ASImageNode
|
||||
private var animationNode: AnimationNode?
|
||||
|
||||
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.iconNode = ASImageNode()
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
self.iconNode.contentMode = .scaleToFill
|
||||
|
||||
super.init()
|
||||
|
||||
self.containerNode.addSubnode(self.referenceNode)
|
||||
self.referenceNode.addSubnode(self.iconNode)
|
||||
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: wide ? 32.0 : 22.0, height: 22.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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
animationNode.frame = self.containerNode.bounds
|
||||
self.addSubnode(animationNode)
|
||||
self.animationNode = animationNode
|
||||
}
|
||||
if animated {
|
||||
if let snapshotView = self.referenceNode.view.snapshotContentTree() {
|
||||
snapshotView.frame = self.referenceNode.frame
|
||||
self.view.addSubview(snapshotView)
|
||||
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
snapshotView.layer.animateScale(from: 1.0, to: 0.1, duration: 0.3, removeOnCompletion: false)
|
||||
|
||||
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 {
|
||||
case let .image(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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
self.iconNode.image = image
|
||||
self.iconNode.isHidden = false
|
||||
self.animationNode?.isHidden = false
|
||||
}
|
||||
} else {
|
||||
self.content = content
|
||||
switch content {
|
||||
case let .image(image):
|
||||
self.iconNode.image = image
|
||||
self.iconNode.isHidden = false
|
||||
self.animationNode?.isHidden = true
|
||||
case let .more(image):
|
||||
self.iconNode.image = image
|
||||
self.iconNode.isHidden = false
|
||||
self.animationNode?.isHidden = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
self.view.isOpaque = false
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
return CGSize(width: wide ? 32.0 : 22.0, height: 22.0)
|
||||
}
|
||||
|
||||
func onLayout() {
|
||||
}
|
||||
|
||||
func play() {
|
||||
self.animationNode?.playOnce()
|
||||
}
|
||||
}
|
||||
|
||||
final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private let context: AccountContext
|
||||
private let presentationData: PresentationData
|
||||
@ -266,6 +465,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private let scrubberView: ChatVideoGalleryItemScrubberView
|
||||
private let footerContentNode: ChatItemGalleryFooterContentNode
|
||||
private let overlayContentNode: UniversalVideoGalleryItemOverlayNode
|
||||
|
||||
private let moreBarButton: MoreHeaderButton
|
||||
private var moreBarButtonRate: Double = 1.0
|
||||
private var moreBarButtonRateTimestamp: Double?
|
||||
|
||||
private var videoNode: UniversalVideoNode?
|
||||
private var videoNodeUserInteractionEnabled: Bool = false
|
||||
@ -294,6 +497,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private var item: UniversalVideoGalleryItem?
|
||||
|
||||
private let statusDisposable = MetaDisposable()
|
||||
private let moreButtonStateDisposable = MetaDisposable()
|
||||
private let mediaPlaybackStateDisposable = MetaDisposable()
|
||||
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
@ -307,6 +511,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private let isPlayingPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private let isInteractingPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private let controlsVisiblePromise = ValuePromise<Bool>(true, ignoreRepeated: true)
|
||||
private let isShowingContextMenuPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private var hideControlsDisposable: Disposable?
|
||||
|
||||
var playbackCompleted: (() -> Void)?
|
||||
@ -330,8 +535,14 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
self.statusNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 50.0, height: 50.0))
|
||||
|
||||
self._title.set(.single(""))
|
||||
|
||||
self.moreBarButton = MoreHeaderButton()
|
||||
self.moreBarButton.isUserInteractionEnabled = true
|
||||
self.moreBarButton.setContent(.more(optionsCircleImage(dark: false)))
|
||||
|
||||
super.init()
|
||||
|
||||
self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.footerContentNode.interacting = { [weak self] value in
|
||||
self?.isInteractingPromise.set(value)
|
||||
@ -428,6 +639,19 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
self.footerContentNode.toggleFullscreen = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var toLandscape = false
|
||||
let size = strongSelf.bounds.size
|
||||
if size.width < size.height {
|
||||
toLandscape = true
|
||||
}
|
||||
strongSelf.updateControlsVisibility(!toLandscape)
|
||||
strongSelf.updateOrientation(toLandscape ? .landscapeRight : .portrait)
|
||||
}
|
||||
|
||||
self.scrubbingFrameDisposable = (self.scrubbingFrame.get()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
@ -453,12 +677,19 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
strongSelf.pictureInPictureButtonPressed()
|
||||
return true
|
||||
}
|
||||
|
||||
self.moreBarButton.contextAction = { [weak self] sourceNode, gesture in
|
||||
self?.openMoreMenu(sourceNode: sourceNode, gesture: gesture)
|
||||
}
|
||||
|
||||
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())
|
||||
|> mapToSignal { isPlaying, isIntracting, controlsVisible -> Signal<Void, NoError> in
|
||||
let shouldHideControlsSignal: Signal<Void, NoError> = combineLatest(self.isPlayingPromise.get(), self.isInteractingPromise.get(), self.controlsVisiblePromise.get(), self.isShowingContextMenuPromise.get())
|
||||
|> mapToSignal { isPlaying, isIntracting, controlsVisible, isShowingContextMenu -> Signal<Void, NoError> in
|
||||
if isShowingContextMenu {
|
||||
return .complete()
|
||||
}
|
||||
if isPlaying && !isIntracting && controlsVisible {
|
||||
return .single(Void())
|
||||
|> delay(4.0, queue: Queue.mainQueue())
|
||||
@ -477,6 +708,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
deinit {
|
||||
self.statusDisposable.dispose()
|
||||
self.moreButtonStateDisposable.dispose()
|
||||
self.mediaPlaybackStateDisposable.dispose()
|
||||
self.scrubbingFrameDisposable?.dispose()
|
||||
self.hideControlsDisposable?.dispose()
|
||||
@ -646,7 +878,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
if status.timestamp > 5.0 && status.timestamp < status.duration - 5.0 {
|
||||
timestamp = status.timestamp
|
||||
}
|
||||
item.storeMediaPlaybackState(message.id, timestamp)
|
||||
item.storeMediaPlaybackState(message.id, timestamp, status.baseRate)
|
||||
}
|
||||
}))
|
||||
}
|
||||
@ -687,6 +919,56 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
self.moreButtonStateDisposable.set(combineLatest(queue: .mainQueue(),
|
||||
videoNode.status,
|
||||
self.isShowingContextMenuPromise.get()
|
||||
).start(next: { [weak self] status, isShowingContextMenu in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard let status = status else {
|
||||
return
|
||||
}
|
||||
|
||||
let effectiveBaseRate: Double
|
||||
if isShowingContextMenu {
|
||||
effectiveBaseRate = 1.0
|
||||
} else {
|
||||
effectiveBaseRate = status.baseRate
|
||||
}
|
||||
|
||||
if abs(effectiveBaseRate - strongSelf.moreBarButtonRate) > 0.01 {
|
||||
strongSelf.moreBarButtonRate = effectiveBaseRate
|
||||
let animated: Bool
|
||||
if let moreBarButtonRateTimestamp = strongSelf.moreBarButtonRateTimestamp {
|
||||
animated = CFAbsoluteTimeGetCurrent() > (moreBarButtonRateTimestamp + 0.2)
|
||||
} else {
|
||||
animated = false
|
||||
}
|
||||
strongSelf.moreBarButtonRateTimestamp = CFAbsoluteTimeGetCurrent()
|
||||
|
||||
if abs(effectiveBaseRate - 1.0) > 0.01 {
|
||||
let rateString: String
|
||||
if abs(effectiveBaseRate - 0.5) < 0.01 {
|
||||
rateString = "0.5x"
|
||||
} else if abs(effectiveBaseRate - 1.5) < 0.01 {
|
||||
rateString = "1.5x"
|
||||
} else if abs(effectiveBaseRate - 2.0) < 0.01 {
|
||||
rateString = "2x"
|
||||
} else {
|
||||
rateString = "x"
|
||||
}
|
||||
strongSelf.moreBarButton.setContent(.image(optionsRateImage(rate: rateString, isLarge: true)), animated: animated)
|
||||
} else {
|
||||
strongSelf.moreBarButton.setContent(.more(optionsCircleImage(dark: false)), animated: animated)
|
||||
}
|
||||
} else {
|
||||
if strongSelf.moreBarButtonRateTimestamp == nil {
|
||||
strongSelf.moreBarButtonRateTimestamp = CFAbsoluteTimeGetCurrent()
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
self.statusDisposable.set((combineLatest(queue: .mainQueue(), videoNode.status, mediaFileStatus)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value, fetchStatus in
|
||||
if let strongSelf = self {
|
||||
@ -825,6 +1107,27 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
} else {
|
||||
self.hasPictureInPicture = false
|
||||
}
|
||||
|
||||
if let contentInfo = item.contentInfo, case let .message(message) = contentInfo {
|
||||
var file: TelegramMediaFile?
|
||||
var isWebpage = false
|
||||
for m in message.media {
|
||||
if let m = m as? TelegramMediaFile, m.isVideo {
|
||||
file = m
|
||||
break
|
||||
} else if let m = m as? TelegramMediaWebpage, case let .Loaded(content) = m.content, let f = content.file, f.isVideo {
|
||||
file = f
|
||||
isWebpage = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isWebpage, let file = file, !file.isAnimated {
|
||||
let moreMenuItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)!
|
||||
barButtonItems.append(moreMenuItem)
|
||||
}
|
||||
}
|
||||
|
||||
self._rightBarButtonItems.set(.single(barButtonItems))
|
||||
|
||||
videoNode.playbackCompleted = { [weak self, weak videoNode] in
|
||||
@ -1001,18 +1304,21 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
var isAnimated = false
|
||||
var seek = MediaPlayerSeek.start
|
||||
var playbackRate: Double = 1.0
|
||||
if let item = self.item {
|
||||
if let content = item.content as? NativeVideoContent {
|
||||
isAnimated = content.fileReference.media.isAnimated
|
||||
if let time = item.timecode {
|
||||
seek = .timecode(time)
|
||||
}
|
||||
playbackRate = item.playbackRate
|
||||
} else if let _ = item.content as? WebEmbedVideoContent {
|
||||
if let time = item.timecode {
|
||||
seek = .timecode(time)
|
||||
}
|
||||
}
|
||||
}
|
||||
videoNode.setBaseRate(playbackRate)
|
||||
if isAnimated {
|
||||
videoNode.seek(0.0)
|
||||
videoNode.play()
|
||||
@ -1615,7 +1921,208 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func contentInfo() -> (message: Message, file: TelegramMediaFile, isWebpage: Bool)? {
|
||||
guard let item = self.item else {
|
||||
return nil
|
||||
}
|
||||
if let contentInfo = item.contentInfo, case let .message(message) = contentInfo {
|
||||
var file: TelegramMediaFile?
|
||||
var isWebpage = false
|
||||
for m in message.media {
|
||||
if let m = m as? TelegramMediaFile, m.isVideo {
|
||||
file = m
|
||||
break
|
||||
} else if let m = m as? TelegramMediaWebpage, case let .Loaded(content) = m.content, let f = content.file, f.isVideo {
|
||||
file = f
|
||||
isWebpage = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let file = file {
|
||||
return (message, file, isWebpage)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func canDelete() -> Bool {
|
||||
guard let (message, _, _) = self.contentInfo() else {
|
||||
return false
|
||||
}
|
||||
|
||||
var canDelete = false
|
||||
if let peer = message.peers[message.id.peerId] {
|
||||
if peer is TelegramUser || peer is TelegramSecretChat {
|
||||
canDelete = true
|
||||
} else if let _ = peer as? TelegramGroup {
|
||||
canDelete = true
|
||||
} else if let channel = peer as? TelegramChannel {
|
||||
if message.flags.contains(.Incoming) {
|
||||
canDelete = channel.hasPermission(.deleteAllMessages)
|
||||
} else {
|
||||
canDelete = true
|
||||
}
|
||||
} else {
|
||||
canDelete = false
|
||||
}
|
||||
} else {
|
||||
canDelete = false
|
||||
}
|
||||
return canDelete
|
||||
}
|
||||
|
||||
@objc private func moreButtonPressed() {
|
||||
self.moreBarButton.play()
|
||||
self.moreBarButton.contextAction?(self.moreBarButton.containerNode, nil)
|
||||
}
|
||||
|
||||
private func openMoreMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) {
|
||||
let items: Signal<[ContextMenuItem], NoError> = self.contextMenuMainItems()
|
||||
guard let controller = self.baseNavigationController()?.topViewController as? ViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
let contextController = ContextController(account: self.context.account, presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: self.moreBarButton.referenceNode)), items: items, reactionItems: [], gesture: gesture)
|
||||
self.isShowingContextMenuPromise.set(true)
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
|
||||
contextController.dismissed = { [weak self] in
|
||||
Queue.mainQueue().after(0.1, {
|
||||
self?.isShowingContextMenuPromise.set(false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func speedList() -> [(String, String, Double)] {
|
||||
let speedList: [(String, String, Double)] = [
|
||||
("0.5x", "0.5x", 0.5),
|
||||
("Normal", "1x", 1.0),
|
||||
("1.5x", "1.5x", 1.5),
|
||||
("2x", "2x", 2.0)
|
||||
]
|
||||
|
||||
return speedList
|
||||
}
|
||||
|
||||
private func contextMenuMainItems() -> Signal<[ContextMenuItem], NoError> {
|
||||
guard let videoNode = self.videoNode else {
|
||||
return .single([])
|
||||
}
|
||||
|
||||
return videoNode.status
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
|> map { [weak self] status -> [ContextMenuItem] in
|
||||
guard let status = status, let strongSelf = self else {
|
||||
return []
|
||||
}
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
var speedValue: String = "Normal"
|
||||
var speedIconText: String = "1x"
|
||||
for (text, iconText, speed) in strongSelf.speedList() {
|
||||
if abs(speed - status.baseRate) < 0.01 {
|
||||
speedValue = text
|
||||
speedIconText = iconText
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "Playback Speed", textLayout: .secondLineWithValue(speedValue), icon: { theme in
|
||||
return optionsRateImage(rate: speedIconText, isLarge: false, color: theme.contextMenu.primaryColor)
|
||||
}, action: { c, _ in
|
||||
guard let strongSelf = self else {
|
||||
c.dismiss(completion: nil)
|
||||
return
|
||||
}
|
||||
|
||||
c.setItems(strongSelf.contextMenuSpeedItems())
|
||||
})))
|
||||
if let (message, file, isWebpage) = strongSelf.contentInfo(), !isWebpage {
|
||||
items.append(.action(ContextMenuActionItem(text: "Save to Gallery", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
if let strongSelf = self {
|
||||
let _ = (SaveToCameraRoll.saveToCameraRoll(context: strongSelf.context, postbox: strongSelf.context.account.postbox, mediaReference: .message(message: MessageReference(message), media: file))
|
||||
|> deliverOnMainQueue).start(completed: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard let controller = strongSelf.galleryController() else {
|
||||
return
|
||||
}
|
||||
//TODO:localize
|
||||
controller.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .mediaSaved(text: "Video Saved"), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||
})
|
||||
}
|
||||
})))
|
||||
}
|
||||
if strongSelf.canDelete() {
|
||||
items.append(.action(ContextMenuActionItem(text: "Delete", textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
if let strongSelf = self {
|
||||
strongSelf.footerContentNode.deleteButtonPressed()
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
||||
private func contextMenuSpeedItems() -> Signal<[ContextMenuItem], NoError> {
|
||||
guard let videoNode = self.videoNode else {
|
||||
return .single([])
|
||||
}
|
||||
|
||||
return videoNode.status
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
|> map { [weak self] status -> [ContextMenuItem] in
|
||||
guard let status = status, let strongSelf = self else {
|
||||
return []
|
||||
}
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
for (text, _, rate) in strongSelf.speedList() {
|
||||
let isSelected = abs(status.baseRate - rate) < 0.01
|
||||
items.append(.action(ContextMenuActionItem(text: text, icon: { theme in
|
||||
if isSelected {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
guard let strongSelf = self, let videoNode = strongSelf.videoNode else {
|
||||
return
|
||||
}
|
||||
|
||||
videoNode.setBaseRate(rate)
|
||||
})))
|
||||
}
|
||||
|
||||
items.append(.separator)
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Back, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor)
|
||||
}, action: { c, _ in
|
||||
guard let strongSelf = self else {
|
||||
c.dismiss(completion: nil)
|
||||
return
|
||||
}
|
||||
c.setItems(strongSelf.contextMenuMainItems())
|
||||
})))
|
||||
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
||||
@objc func openStickersButtonPressed() {
|
||||
if let content = self.item?.content as? NativeVideoContent {
|
||||
let media = content.fileReference.abstract
|
||||
@ -1667,6 +2174,20 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
|
||||
override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> {
|
||||
return .single((self.footerContentNode, self.overlayContentNode))
|
||||
return .single((self.footerContentNode, nil))
|
||||
}
|
||||
}
|
||||
|
||||
private final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
||||
private let controller: ViewController
|
||||
private let sourceNode: ContextReferenceContentNode
|
||||
|
||||
init(controller: ViewController, sourceNode: ContextReferenceContentNode) {
|
||||
self.controller = controller
|
||||
self.sourceNode = sourceNode
|
||||
}
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo? {
|
||||
return ContextControllerReferenceViewInfo(referenceNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds)
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ public struct InstantPageGalleryEntry: Equatable {
|
||||
nativeId = .instantPage(self.pageId, file.fileId)
|
||||
}
|
||||
|
||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: NativeVideoContent(id: nativeId, fileReference: .webPage(webPage: WebpageReference(webPage), media: file), streamVideo: isMediaStreamable(media: file) ? .conservative : .none), originData: nil, indexData: indexData, contentInfo: .webPage(webPage, file, nil), caption: caption, credit: credit, fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in }, storeMediaPlaybackState: { _, _ in }, present: { _, _ in })
|
||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: NativeVideoContent(id: nativeId, fileReference: .webPage(webPage: WebpageReference(webPage), media: file), streamVideo: isMediaStreamable(media: file) ? .conservative : .none), originData: nil, indexData: indexData, contentInfo: .webPage(webPage, file, nil), caption: caption, credit: credit, fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in }, storeMediaPlaybackState: { _, _, _ in }, present: { _, _ in })
|
||||
} else {
|
||||
var representations: [TelegramMediaImageRepresentation] = []
|
||||
representations.append(contentsOf: file.previewRepresentations)
|
||||
@ -135,12 +135,12 @@ public struct InstantPageGalleryEntry: Equatable {
|
||||
present(gallery, InstantPageGalleryControllerPresentationArguments(transitionArguments: { entry -> GalleryTransitionArguments? in
|
||||
return makeArguments()
|
||||
}))
|
||||
}), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in }, storeMediaPlaybackState: { _, _ in }, present: { _, _ in })
|
||||
}), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in }, storeMediaPlaybackState: { _, _, _ in }, present: { _, _ in })
|
||||
} else {
|
||||
if let content = WebEmbedVideoContent(webPage: embedWebpage, webpageContent: webpageContent, openUrl: { url in
|
||||
|
||||
}) {
|
||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: nil, indexData: nil, contentInfo: .webPage(webPage, embedWebpage, nil), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in }, storeMediaPlaybackState: { _, _ in }, present: { _, _ in })
|
||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: nil, indexData: nil, contentInfo: .webPage(webPage, embedWebpage, nil), caption: NSAttributedString(string: ""), fromPlayingVideo: fromPlayingVideo, landscape: landscape, performAction: { _ in }, openActionOptions: { _ in }, storeMediaPlaybackState: { _, _, _ in }, present: { _, _ in })
|
||||
} else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Collapse.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Collapse.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "smallscreen_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
161
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Collapse.imageset/smallscreen_30.pdf
vendored
Normal file
161
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Collapse.imageset/smallscreen_30.pdf
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 5.334991 5.334999 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
6.665000 19.330002 m
|
||||
7.032269 19.330002 7.330000 19.032270 7.330000 18.665001 c
|
||||
7.330000 15.865002 l
|
||||
7.330000 15.837518 l
|
||||
7.330000 15.837514 l
|
||||
7.330000 15.837511 l
|
||||
7.330009 15.300820 7.330016 14.857977 7.300543 14.497252 c
|
||||
7.269935 14.122624 7.204253 13.778399 7.039532 13.455116 c
|
||||
6.784030 12.953665 6.376337 12.545971 5.874885 12.290468 c
|
||||
5.551602 12.125748 5.207376 12.060066 4.832748 12.029457 c
|
||||
4.472024 11.999985 4.029181 11.999992 3.492490 12.000001 c
|
||||
3.492487 12.000001 l
|
||||
3.492483 12.000001 l
|
||||
3.465000 12.000001 l
|
||||
0.665000 12.000001 l
|
||||
0.297731 12.000001 0.000000 12.297731 0.000000 12.665001 c
|
||||
0.000000 13.032270 0.297731 13.330001 0.665000 13.330001 c
|
||||
3.465000 13.330001 l
|
||||
4.036026 13.330001 4.424301 13.330519 4.724444 13.355041 c
|
||||
5.016824 13.378929 5.166537 13.422241 5.271077 13.475508 c
|
||||
5.522274 13.603498 5.726503 13.807728 5.854494 14.058924 c
|
||||
5.907760 14.163464 5.951072 14.313177 5.974960 14.605556 c
|
||||
5.999483 14.905701 6.000000 15.293976 6.000000 15.865002 c
|
||||
6.000000 18.665001 l
|
||||
6.000000 19.032270 6.297730 19.330002 6.665000 19.330002 c
|
||||
h
|
||||
13.330000 18.665001 m
|
||||
13.330000 19.032270 13.032269 19.330002 12.665000 19.330002 c
|
||||
12.297730 19.330002 12.000000 19.032270 12.000000 18.665001 c
|
||||
12.000000 15.865002 l
|
||||
12.000000 15.837526 l
|
||||
12.000000 15.837497 l
|
||||
11.999991 15.300814 11.999985 14.857974 12.029457 14.497252 c
|
||||
12.060065 14.122624 12.125747 13.778399 12.290467 13.455116 c
|
||||
12.545970 12.953665 12.953663 12.545971 13.455115 12.290468 c
|
||||
13.778399 12.125748 14.122623 12.060066 14.497252 12.029457 c
|
||||
14.857978 11.999985 15.300823 11.999992 15.837517 12.000001 c
|
||||
15.865000 12.000001 l
|
||||
18.665001 12.000001 l
|
||||
19.032270 12.000001 19.330002 12.297731 19.330002 12.665001 c
|
||||
19.330002 13.032270 19.032270 13.330001 18.665001 13.330001 c
|
||||
15.865000 13.330001 l
|
||||
15.293975 13.330001 14.905700 13.330519 14.605556 13.355041 c
|
||||
14.313176 13.378929 14.163464 13.422241 14.058923 13.475508 c
|
||||
13.807726 13.603498 13.603498 13.807728 13.475507 14.058924 c
|
||||
13.422240 14.163464 13.378928 14.313177 13.355040 14.605556 c
|
||||
13.330517 14.905701 13.330000 15.293976 13.330000 15.865002 c
|
||||
13.330000 18.665001 l
|
||||
h
|
||||
15.865000 7.330001 m
|
||||
15.837525 7.330001 l
|
||||
15.837497 7.330001 l
|
||||
15.300811 7.330009 14.857973 7.330016 14.497252 7.300544 c
|
||||
14.122623 7.269936 13.778399 7.204254 13.455115 7.039534 c
|
||||
12.953663 6.784031 12.545970 6.376338 12.290467 5.874886 c
|
||||
12.125747 5.551602 12.060065 5.207377 12.029457 4.832749 c
|
||||
11.999985 4.472028 11.999991 4.029190 12.000000 3.492504 c
|
||||
12.000000 3.492476 l
|
||||
12.000000 3.465000 l
|
||||
12.000000 0.665001 l
|
||||
12.000000 0.297731 12.297730 0.000000 12.665000 0.000000 c
|
||||
13.032269 0.000000 13.330000 0.297731 13.330000 0.665001 c
|
||||
13.330000 3.465000 l
|
||||
13.330000 4.036026 13.330517 4.424301 13.355040 4.724444 c
|
||||
13.378928 5.016825 13.422240 5.166537 13.475507 5.271078 c
|
||||
13.603498 5.522275 13.807726 5.726503 14.058923 5.854494 c
|
||||
14.163464 5.907761 14.313176 5.951073 14.605556 5.974961 c
|
||||
14.905700 5.999484 15.293975 6.000001 15.865000 6.000001 c
|
||||
18.665001 6.000001 l
|
||||
19.032270 6.000001 19.330002 6.297731 19.330002 6.665001 c
|
||||
19.330002 7.032270 19.032270 7.330001 18.665001 7.330001 c
|
||||
15.865000 7.330001 l
|
||||
h
|
||||
3.465000 6.000001 m
|
||||
4.036026 6.000001 4.424301 5.999484 4.724444 5.974961 c
|
||||
5.016824 5.951073 5.166537 5.907761 5.271077 5.854494 c
|
||||
5.522274 5.726503 5.726503 5.522275 5.854494 5.271078 c
|
||||
5.907760 5.166537 5.951072 5.016825 5.974960 4.724444 c
|
||||
5.999483 4.424301 6.000000 4.036026 6.000000 3.465000 c
|
||||
6.000000 0.665001 l
|
||||
6.000000 0.297731 6.297730 0.000000 6.665000 0.000000 c
|
||||
7.032269 0.000000 7.330000 0.297731 7.330000 0.665001 c
|
||||
7.330000 3.465000 l
|
||||
7.330000 3.492484 l
|
||||
7.330009 4.029178 7.330016 4.472023 7.300543 4.832749 c
|
||||
7.269935 5.207377 7.204253 5.551602 7.039532 5.874886 c
|
||||
6.784030 6.376338 6.376337 6.784031 5.874885 7.039534 c
|
||||
5.551602 7.204254 5.207376 7.269936 4.832748 7.300544 c
|
||||
4.472027 7.330016 4.029188 7.330009 3.492504 7.330001 c
|
||||
3.492474 7.330001 l
|
||||
3.465000 7.330001 l
|
||||
0.665000 7.330001 l
|
||||
0.297731 7.330001 0.000000 7.032270 0.000000 6.665001 c
|
||||
0.000000 6.297731 0.297731 6.000001 0.665000 6.000001 c
|
||||
3.465000 6.000001 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
4194
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000004284 00000 n
|
||||
0000004307 00000 n
|
||||
0000004480 00000 n
|
||||
0000004554 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
4613
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Download.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Download.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "down_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
92
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Download.imageset/down_24.pdf
vendored
Normal file
92
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Download.imageset/down_24.pdf
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 3.334999 3.334999 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
8.665000 16.000002 m
|
||||
4.613991 16.000002 1.330000 12.716011 1.330000 8.665002 c
|
||||
1.330000 4.613994 4.613991 1.330002 8.665000 1.330002 c
|
||||
12.716008 1.330002 16.000000 4.613994 16.000000 8.665002 c
|
||||
16.000000 12.716011 12.716008 16.000002 8.665000 16.000002 c
|
||||
h
|
||||
0.000000 8.665002 m
|
||||
0.000000 13.450549 3.879453 17.330002 8.665000 17.330002 c
|
||||
13.450547 17.330002 17.330002 13.450549 17.330002 8.665002 c
|
||||
17.330002 3.879455 13.450547 0.000000 8.665000 0.000000 c
|
||||
3.879453 0.000000 0.000000 3.879455 0.000000 8.665002 c
|
||||
h
|
||||
8.665000 12.830002 m
|
||||
9.032269 12.830002 9.330000 12.532271 9.330000 12.165002 c
|
||||
9.330000 6.770453 l
|
||||
11.194775 8.635228 l
|
||||
11.454473 8.894926 11.875527 8.894926 12.135225 8.635228 c
|
||||
12.394924 8.375529 12.394924 7.954474 12.135225 7.694776 c
|
||||
9.135226 4.694776 l
|
||||
8.875527 4.435078 8.454473 4.435078 8.194774 4.694776 c
|
||||
5.194774 7.694776 l
|
||||
4.935075 7.954474 4.935075 8.375529 5.194774 8.635228 c
|
||||
5.454473 8.894926 5.875527 8.894926 6.135226 8.635228 c
|
||||
8.000000 6.770453 l
|
||||
8.000000 12.165002 l
|
||||
8.000000 12.532271 8.297730 12.830002 8.665000 12.830002 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1188
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001278 00000 n
|
||||
0000001301 00000 n
|
||||
0000001474 00000 n
|
||||
0000001548 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1607
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Expand.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Expand.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "fullscreen_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
161
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Expand.imageset/fullscreen_30.pdf
vendored
Normal file
161
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Expand.imageset/fullscreen_30.pdf
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 5.334991 5.334970 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
15.465001 18.000031 m
|
||||
16.036026 18.000031 16.424301 17.999512 16.724445 17.974989 c
|
||||
17.016825 17.951101 17.166538 17.907789 17.271078 17.854523 c
|
||||
17.522274 17.726532 17.726503 17.522303 17.854494 17.271107 c
|
||||
17.907761 17.166567 17.951073 17.016853 17.974960 16.724474 c
|
||||
17.999483 16.424330 18.000000 16.036055 18.000000 15.465029 c
|
||||
18.000000 12.665030 l
|
||||
18.000000 12.297760 18.297731 12.000029 18.665001 12.000029 c
|
||||
19.032270 12.000029 19.330002 12.297760 19.330002 12.665030 c
|
||||
19.330002 15.465029 l
|
||||
19.330002 15.492513 l
|
||||
19.330002 15.492586 l
|
||||
19.330009 16.029245 19.330015 16.472069 19.300545 16.832777 c
|
||||
19.269936 17.207407 19.204254 17.551632 19.039534 17.874914 c
|
||||
18.784031 18.376366 18.376337 18.784060 17.874886 19.039562 c
|
||||
17.551603 19.204283 17.207378 19.269964 16.832748 19.300573 c
|
||||
16.472025 19.330046 16.029184 19.330038 15.492496 19.330030 c
|
||||
15.492476 19.330030 l
|
||||
15.465001 19.330030 l
|
||||
12.665001 19.330030 l
|
||||
12.297731 19.330030 12.000001 19.032299 12.000001 18.665030 c
|
||||
12.000001 18.297760 12.297731 18.000031 12.665001 18.000031 c
|
||||
15.465001 18.000031 l
|
||||
h
|
||||
3.865001 19.330030 m
|
||||
3.837527 19.330030 l
|
||||
3.837508 19.330030 l
|
||||
3.300819 19.330038 2.857976 19.330046 2.497253 19.300573 c
|
||||
2.122624 19.269964 1.778399 19.204283 1.455116 19.039562 c
|
||||
0.953664 18.784060 0.545971 18.376366 0.290469 17.874914 c
|
||||
0.125748 17.551632 0.060066 17.207407 0.029458 16.832777 c
|
||||
-0.000015 16.472054 -0.000008 16.029211 0.000000 15.492522 c
|
||||
0.000000 15.492504 l
|
||||
0.000000 15.465029 l
|
||||
0.000000 12.665030 l
|
||||
0.000000 12.297760 0.297732 12.000029 0.665001 12.000029 c
|
||||
1.032270 12.000029 1.330001 12.297760 1.330001 12.665030 c
|
||||
1.330001 15.465029 l
|
||||
1.330001 16.036055 1.330518 16.424330 1.355041 16.724474 c
|
||||
1.378929 17.016853 1.422241 17.166567 1.475507 17.271107 c
|
||||
1.603498 17.522303 1.807727 17.726532 2.058923 17.854523 c
|
||||
2.163464 17.907789 2.313177 17.951101 2.605557 17.974989 c
|
||||
2.905700 17.999512 3.293975 18.000031 3.865001 18.000031 c
|
||||
6.665001 18.000031 l
|
||||
7.032271 18.000031 7.330001 18.297760 7.330001 18.665030 c
|
||||
7.330001 19.032299 7.032271 19.330030 6.665001 19.330030 c
|
||||
3.865001 19.330030 l
|
||||
h
|
||||
1.330001 6.665030 m
|
||||
1.330001 7.032299 1.032270 7.330029 0.665001 7.330029 c
|
||||
0.297732 7.330029 0.000000 7.032299 0.000000 6.665030 c
|
||||
0.000000 3.865030 l
|
||||
0.000000 3.837555 l
|
||||
0.000000 3.837535 l
|
||||
-0.000008 3.300846 -0.000015 2.858006 0.029458 2.497282 c
|
||||
0.060066 2.122652 0.125748 1.778427 0.290469 1.455145 c
|
||||
0.545971 0.953693 0.953664 0.546000 1.455116 0.290497 c
|
||||
1.778399 0.125776 2.122624 0.060095 2.497253 0.029486 c
|
||||
2.857963 0.000015 3.300784 0.000021 3.837445 0.000029 c
|
||||
3.837518 0.000029 l
|
||||
3.865001 0.000029 l
|
||||
6.665001 0.000029 l
|
||||
7.032271 0.000029 7.330001 0.297760 7.330001 0.665030 c
|
||||
7.330001 1.032299 7.032271 1.330030 6.665001 1.330030 c
|
||||
3.865001 1.330030 l
|
||||
3.293975 1.330030 2.905700 1.330547 2.605557 1.355070 c
|
||||
2.313177 1.378958 2.163464 1.422270 2.058923 1.475536 c
|
||||
1.807727 1.603527 1.603498 1.807756 1.475507 2.058952 c
|
||||
1.422241 2.163492 1.378929 2.313206 1.355041 2.605585 c
|
||||
1.330518 2.905729 1.330001 3.294004 1.330001 3.865030 c
|
||||
1.330001 6.665030 l
|
||||
h
|
||||
18.665001 7.330029 m
|
||||
19.032270 7.330029 19.330002 7.032299 19.330002 6.665030 c
|
||||
19.330002 3.865030 l
|
||||
19.330002 3.837546 l
|
||||
19.330002 3.837475 l
|
||||
19.330009 3.300812 19.330015 2.857990 19.300545 2.497282 c
|
||||
19.269936 2.122652 19.204254 1.778427 19.039534 1.455145 c
|
||||
18.784031 0.953693 18.376337 0.546000 17.874886 0.290497 c
|
||||
17.551603 0.125776 17.207378 0.060095 16.832748 0.029486 c
|
||||
16.472040 0.000015 16.029217 0.000021 15.492556 0.000029 c
|
||||
15.492484 0.000029 l
|
||||
15.465001 0.000029 l
|
||||
12.665001 0.000029 l
|
||||
12.297731 0.000029 12.000001 0.297760 12.000001 0.665030 c
|
||||
12.000001 1.032299 12.297731 1.330030 12.665001 1.330030 c
|
||||
15.465001 1.330030 l
|
||||
16.036026 1.330030 16.424301 1.330547 16.724445 1.355070 c
|
||||
17.016825 1.378958 17.166538 1.422270 17.271078 1.475536 c
|
||||
17.522274 1.603527 17.726503 1.807756 17.854494 2.058952 c
|
||||
17.907761 2.163492 17.951073 2.313206 17.974960 2.605585 c
|
||||
17.999483 2.905729 18.000000 3.294004 18.000000 3.865030 c
|
||||
18.000000 6.665030 l
|
||||
18.000000 7.032299 18.297731 7.330029 18.665001 7.330029 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
4198
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000004288 00000 n
|
||||
0000004311 00000 n
|
||||
0000004484 00000 n
|
||||
0000004558 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
4617
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Playspeed24.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Playspeed24.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "playspeed_24.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
113
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Playspeed24.imageset/playspeed_24.pdf
vendored
Normal file
113
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Playspeed24.imageset/playspeed_24.pdf
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 3.335007 3.334999 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.000007 16.665001 m
|
||||
0.000007 17.032270 0.297737 17.330002 0.665007 17.330002 c
|
||||
1.165007 17.330002 l
|
||||
1.532276 17.330002 1.830007 17.032270 1.830007 16.665001 c
|
||||
1.830007 16.297733 1.532276 16.000002 1.165007 16.000002 c
|
||||
0.665007 16.000002 l
|
||||
0.297737 16.000002 0.000007 16.297733 0.000007 16.665001 c
|
||||
h
|
||||
9.665000 17.330002 m
|
||||
9.297730 17.330002 9.000000 17.032270 9.000000 16.665001 c
|
||||
9.000000 16.297731 9.297730 16.000000 9.665000 16.000000 c
|
||||
16.665001 16.000000 l
|
||||
17.032270 16.000000 17.330002 16.297731 17.330002 16.665001 c
|
||||
17.330002 17.032270 17.032270 17.330002 16.665001 17.330002 c
|
||||
9.665000 17.330002 l
|
||||
h
|
||||
9.665000 1.330002 m
|
||||
9.297730 1.330002 9.000000 1.032270 9.000000 0.665001 c
|
||||
9.000000 0.297731 9.297730 0.000000 9.665000 0.000000 c
|
||||
16.665001 0.000000 l
|
||||
17.032270 0.000000 17.330002 0.297731 17.330002 0.665001 c
|
||||
17.330002 1.032270 17.032270 1.330002 16.665001 1.330002 c
|
||||
9.665000 1.330002 l
|
||||
h
|
||||
0.665000 1.330002 m
|
||||
0.297731 1.330002 0.000000 1.032270 0.000000 0.665001 c
|
||||
0.000000 0.297731 0.297731 0.000000 0.665000 0.000000 c
|
||||
1.165000 0.000000 l
|
||||
1.532269 0.000000 1.830000 0.297731 1.830000 0.665001 c
|
||||
1.830000 1.032270 1.532269 1.330002 1.165000 1.330002 c
|
||||
0.665000 1.330002 l
|
||||
h
|
||||
3.000000 16.665001 m
|
||||
3.000000 17.032270 3.297731 17.330002 3.665000 17.330002 c
|
||||
7.165000 17.330002 l
|
||||
7.532269 17.330002 7.830000 17.032270 7.830000 16.665001 c
|
||||
7.830000 16.297731 7.532269 16.000000 7.165000 16.000000 c
|
||||
3.665000 16.000000 l
|
||||
3.297731 16.000000 3.000000 16.297731 3.000000 16.665001 c
|
||||
h
|
||||
3.665000 1.330002 m
|
||||
3.297731 1.330002 3.000000 1.032270 3.000000 0.665001 c
|
||||
3.000000 0.297731 3.297731 0.000000 3.665000 0.000000 c
|
||||
7.165000 0.000000 l
|
||||
7.532269 0.000000 7.830000 0.297731 7.830000 0.665001 c
|
||||
7.830000 1.032270 7.532269 1.330002 7.165000 1.330002 c
|
||||
3.665000 1.330002 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1901
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001991 00000 n
|
||||
0000002014 00000 n
|
||||
0000002187 00000 n
|
||||
0000002261 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2320
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Playspeed30.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Playspeed30.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "playspeed_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
113
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Playspeed30.imageset/playspeed_30.pdf
vendored
Normal file
113
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Playspeed30.imageset/playspeed_30.pdf
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 5.335007 5.334999 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.000000 18.665001 m
|
||||
0.000000 19.032270 0.297731 19.330002 0.665000 19.330002 c
|
||||
1.665000 19.330002 l
|
||||
2.032269 19.330002 2.330000 19.032270 2.330000 18.665001 c
|
||||
2.330000 18.297733 2.032269 18.000002 1.665000 18.000002 c
|
||||
0.665000 18.000002 l
|
||||
0.297731 18.000002 0.000000 18.297733 0.000000 18.665001 c
|
||||
h
|
||||
11.665000 19.330002 m
|
||||
11.297730 19.330002 11.000000 19.032270 11.000000 18.665001 c
|
||||
11.000000 18.297733 11.297730 18.000002 11.665000 18.000002 c
|
||||
18.665001 18.000002 l
|
||||
19.032269 18.000002 19.330000 18.297733 19.330000 18.665001 c
|
||||
19.330000 19.032270 19.032269 19.330002 18.665001 19.330002 c
|
||||
11.665000 19.330002 l
|
||||
h
|
||||
11.665000 1.330002 m
|
||||
11.297730 1.330002 11.000000 1.032270 11.000000 0.665001 c
|
||||
11.000000 0.297731 11.297730 0.000000 11.665000 0.000000 c
|
||||
18.665001 0.000000 l
|
||||
19.032269 0.000000 19.330000 0.297731 19.330000 0.665001 c
|
||||
19.330000 1.032270 19.032269 1.330002 18.665001 1.330002 c
|
||||
11.665000 1.330002 l
|
||||
h
|
||||
0.665000 1.330002 m
|
||||
0.297731 1.330002 0.000000 1.032270 0.000000 0.665001 c
|
||||
0.000000 0.297731 0.297731 0.000000 0.665000 0.000000 c
|
||||
1.665000 0.000000 l
|
||||
2.032269 0.000000 2.330000 0.297731 2.330000 0.665001 c
|
||||
2.330000 1.032270 2.032269 1.330002 1.665000 1.330002 c
|
||||
0.665000 1.330002 l
|
||||
h
|
||||
4.000000 18.665001 m
|
||||
4.000000 19.032270 4.297730 19.330002 4.665000 19.330002 c
|
||||
8.665000 19.330002 l
|
||||
9.032269 19.330002 9.330000 19.032270 9.330000 18.665001 c
|
||||
9.330000 18.297733 9.032269 18.000002 8.665000 18.000002 c
|
||||
4.665000 18.000002 l
|
||||
4.297730 18.000002 4.000000 18.297733 4.000000 18.665001 c
|
||||
h
|
||||
4.665000 1.330002 m
|
||||
4.297730 1.330002 4.000000 1.032270 4.000000 0.665001 c
|
||||
4.000000 0.297731 4.297730 0.000000 4.665000 0.000000 c
|
||||
8.665000 0.000000 l
|
||||
9.032269 0.000000 9.330000 0.297731 9.330000 0.665001 c
|
||||
9.330000 1.032270 9.032269 1.330002 8.665000 1.330002 c
|
||||
4.665000 1.330002 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1917
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Type /Catalog
|
||||
/Pages 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000002007 00000 n
|
||||
0000002030 00000 n
|
||||
0000002203 00000 n
|
||||
0000002277 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2336
|
||||
%%EOF
|
@ -804,13 +804,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerInteraction?.addContact(phoneNumber)
|
||||
}
|
||||
}, storeMediaPlaybackState: { [weak self] messageId, timestamp in
|
||||
}, storeMediaPlaybackState: { [weak self] messageId, timestamp, playbackRate in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var storedState: MediaPlaybackStoredState?
|
||||
if let timestamp = timestamp {
|
||||
storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: .x1)
|
||||
storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: AudioPlaybackRate(playbackRate))
|
||||
}
|
||||
let _ = updateMediaPlaybackStoredStateInteractively(postbox: strongSelf.context.account.postbox, messageId: messageId, state: storedState).start()
|
||||
}, editMedia: { [weak self] messageId, snapshots, transitionCompletion in
|
||||
|
@ -3072,13 +3072,13 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
}
|
||||
}, completed: {})
|
||||
}
|
||||
}, storeMediaPlaybackState: { [weak self] messageId, timestamp in
|
||||
}, storeMediaPlaybackState: { [weak self] messageId, timestamp, playbackRate in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
var storedState: MediaPlaybackStoredState?
|
||||
if let timestamp = timestamp {
|
||||
storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: .x1)
|
||||
storedState = MediaPlaybackStoredState(timestamp: timestamp, playbackRate: AudioPlaybackRate(playbackRate) ?? .x1)
|
||||
}
|
||||
let _ = updateMediaPlaybackStoredStateInteractively(postbox: strongSelf.context.account.postbox, messageId: messageId, state: storedState).start()
|
||||
}, editMedia: { [weak self] messageId, snapshots, transitionCompletion in
|
||||
|
@ -442,19 +442,7 @@ final class SharedMediaPlayer {
|
||||
case let .setBaseRate(baseRate):
|
||||
self.playbackRate = baseRate
|
||||
if let playbackItem = self.playbackItem {
|
||||
let rateValue: Double
|
||||
switch baseRate {
|
||||
case .x1:
|
||||
rateValue = 1.0
|
||||
case .x2:
|
||||
rateValue = 1.8
|
||||
case .x4:
|
||||
rateValue = 4.0
|
||||
case .x8:
|
||||
rateValue = 8.0
|
||||
case .x16:
|
||||
rateValue = 16.0
|
||||
}
|
||||
let rateValue: Double = baseRate.doubleValue
|
||||
switch playbackItem {
|
||||
case let .audio(player):
|
||||
player.setBaseRate(rateValue)
|
||||
|
@ -15,15 +15,25 @@ public enum MusicPlaybackSettingsLooping: Int32 {
|
||||
}
|
||||
|
||||
public enum AudioPlaybackRate: Int32 {
|
||||
case x0_5 = 500
|
||||
case x1 = 1000
|
||||
case x1_5 = 1500
|
||||
case x2 = 2000
|
||||
case x4 = 4000
|
||||
case x8 = 8000
|
||||
case x16 = 16000
|
||||
|
||||
var doubleValue: Double {
|
||||
public var doubleValue: Double {
|
||||
return Double(self.rawValue) / 1000.0
|
||||
}
|
||||
|
||||
public init(_ value: Double) {
|
||||
if let resolved = AudioPlaybackRate(rawValue: Int32(value * 1000.0)) {
|
||||
self = resolved
|
||||
} else {
|
||||
self = .x1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct MusicPlaybackSettings: PreferencesEntry, Equatable {
|
||||
|
Loading…
x
Reference in New Issue
Block a user