mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Video playback improvements
This commit is contained in:
parent
df249f5302
commit
a57d64cfe3
@ -104,8 +104,9 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
case let .custom(item, _):
|
||||
itemNodes.append(.custom(item.node(presentationData: presentationData, getController: getController, actionSelected: actionSelected)))
|
||||
if i != items.count - 1 {
|
||||
let itemNode = item.node(presentationData: presentationData, getController: getController, actionSelected: actionSelected)
|
||||
itemNodes.append(.custom(itemNode))
|
||||
if i != items.count - 1 && itemNode.needsSeparator {
|
||||
switch items[i + 1] {
|
||||
case .action, .custom:
|
||||
let separatorNode = ASDisplayNode()
|
||||
|
@ -226,6 +226,14 @@ public protocol ContextMenuCustomNode: ASDisplayNode {
|
||||
func canBeHighlighted() -> Bool
|
||||
func updateIsHighlighted(isHighlighted: Bool)
|
||||
func performAction()
|
||||
|
||||
var needsSeparator: Bool { get }
|
||||
}
|
||||
|
||||
public extension ContextMenuCustomNode {
|
||||
var needsSeparator: Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public protocol ContextMenuCustomItem {
|
||||
|
@ -629,7 +629,7 @@ private final class ContextControllerActionsListCustomItemNode: ASDisplayNode, C
|
||||
private let requestDismiss: (ContextMenuActionResult) -> Void
|
||||
|
||||
private var presentationData: PresentationData?
|
||||
private var itemNode: ContextMenuCustomNode?
|
||||
private(set) var itemNode: ContextMenuCustomNode?
|
||||
|
||||
init(
|
||||
getController: @escaping () -> ContextControllerProtocol?,
|
||||
@ -862,18 +862,28 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
|
||||
|
||||
if let separatorNode = item.separatorNode {
|
||||
itemTransition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: itemFrame.minX, y: itemFrame.maxY), size: CGSize(width: itemFrame.width, height: UIScreenPixel)), beginWithCurrentState: true)
|
||||
|
||||
var separatorHidden = false
|
||||
if i != self.itemNodes.count - 1 {
|
||||
switch self.items[i + 1] {
|
||||
case .separator:
|
||||
separatorNode.isHidden = true
|
||||
separatorHidden = true
|
||||
case .action:
|
||||
separatorNode.isHidden = false
|
||||
separatorHidden = false
|
||||
case .custom:
|
||||
separatorNode.isHidden = false
|
||||
separatorHidden = false
|
||||
}
|
||||
} else {
|
||||
separatorNode.isHidden = true
|
||||
separatorHidden = true
|
||||
}
|
||||
|
||||
if let itemContainerNode = item.node as? ContextControllerActionsListCustomItemNode, let itemNode = itemContainerNode.itemNode {
|
||||
if !itemNode.needsSeparator {
|
||||
separatorHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
separatorNode.isHidden = separatorHidden
|
||||
}
|
||||
|
||||
itemNodeLayout.apply(itemSize, itemTransition)
|
||||
|
@ -45,7 +45,8 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities",
|
||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||
"//submodules/TelegramUI/Components/SliderContextItem:SliderContextItem",
|
||||
"//submodules/TelegramUI/Components/SliderContextItem",
|
||||
"//submodules/TelegramUI/Components/SectionTitleContextItem",
|
||||
"//submodules/TooltipUI",
|
||||
"//submodules/TelegramNotices",
|
||||
"//submodules/Pasteboard",
|
||||
|
@ -28,6 +28,7 @@ import AdUI
|
||||
import AdsInfoScreen
|
||||
import AdsReportScreen
|
||||
import SaveProgressScreen
|
||||
import SectionTitleContextItem
|
||||
|
||||
public enum UniversalVideoGalleryItemContentInfo {
|
||||
case message(Message, Int?)
|
||||
@ -503,6 +504,111 @@ final class MoreHeaderButton: HighlightableButtonNode {
|
||||
}
|
||||
}
|
||||
|
||||
final class SettingsHeaderButton: HighlightableButtonNode {
|
||||
let referenceNode: ContextReferenceContentNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
private let iconNode: ASImageNode
|
||||
private let iconDotNode: ASImageNode
|
||||
|
||||
private var isMenuOpen: Bool = false
|
||||
|
||||
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
|
||||
private let wide: Bool
|
||||
|
||||
init(wide: Bool = false) {
|
||||
self.wide = wide
|
||||
|
||||
self.referenceNode = ContextReferenceContentNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.containerNode.animateScale = false
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.displaysAsynchronously = false
|
||||
self.iconNode.displayWithoutProcessing = true
|
||||
self.iconNode.contentMode = .scaleToFill
|
||||
|
||||
self.iconDotNode = ASImageNode()
|
||||
self.iconDotNode.displaysAsynchronously = false
|
||||
self.iconDotNode.displayWithoutProcessing = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/NavigationSettingsNoDot"), color: .white)
|
||||
self.iconDotNode.image = generateFilledCircleImage(diameter: 4.0, color: .white)
|
||||
|
||||
self.containerNode.addSubnode(self.referenceNode)
|
||||
self.referenceNode.addSubnode(self.iconNode)
|
||||
self.referenceNode.addSubnode(self.iconDotNode)
|
||||
self.addSubnode(self.containerNode)
|
||||
|
||||
self.containerNode.shouldBegin = { [weak self] location in
|
||||
guard let strongSelf = self, let _ = strongSelf.contextAction else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
self.containerNode.activated = { [weak self] gesture, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.contextAction?(strongSelf.containerNode, gesture)
|
||||
}
|
||||
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 26.0, height: 44.0))
|
||||
self.referenceNode.frame = self.containerNode.bounds
|
||||
|
||||
self.hitTestSlop = UIEdgeInsets(top: 0.0, left: -4.0, bottom: 0.0, right: -4.0)
|
||||
|
||||
if let image = self.iconNode.image {
|
||||
let iconFrame = 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.position = iconFrame.center
|
||||
self.iconNode.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
|
||||
if let dotImage = self.iconDotNode.image {
|
||||
let dotFrame = CGRect(origin: CGPoint(x: iconFrame.minX + floorToScreenPixels((iconFrame.width - dotImage.size.width) * 0.5), y: iconFrame.minY + floorToScreenPixels((iconFrame.height - dotImage.size.height) * 0.5)), size: dotImage.size)
|
||||
self.iconDotNode.position = dotFrame.center
|
||||
self.iconDotNode.bounds = CGRect(origin: CGPoint(), size: dotFrame.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
self.view.isOpaque = false
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
return CGSize(width: wide ? 32.0 : 22.0, height: 44.0)
|
||||
}
|
||||
|
||||
func onLayout() {
|
||||
}
|
||||
|
||||
func setIsMenuOpen(isMenuOpen: Bool) {
|
||||
if self.isMenuOpen == isMenuOpen {
|
||||
return
|
||||
}
|
||||
self.isMenuOpen = isMenuOpen
|
||||
|
||||
let rotationTransition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
|
||||
rotationTransition.updateTransform(node: self.iconNode, transform: CGAffineTransformMakeRotation(isMenuOpen ? (CGFloat.pi * 2.0 / 6.0) : 0.0))
|
||||
self.iconNode.layer.animateScale(from: 1.0, to: 1.07, duration: 0.1, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
guard let self, finished else {
|
||||
return
|
||||
}
|
||||
self.iconNode.layer.animateScale(from: 1.07, to: 1.0, duration: 0.1, removeOnCompletion: false)
|
||||
})
|
||||
|
||||
self.iconDotNode.layer.animateScale(from: 1.0, to: 0.8, duration: 0.1, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
guard let self, finished else {
|
||||
return
|
||||
}
|
||||
self.iconDotNode.layer.animateScale(from: 0.8, to: 1.0, duration: 0.1, removeOnCompletion: false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
private final class PictureInPictureContentImpl: NSObject, PictureInPictureContent, AVPictureInPictureControllerDelegate {
|
||||
private final class PlaybackDelegate: NSObject, AVPictureInPictureSampleBufferPlaybackDelegate {
|
||||
@ -1062,7 +1168,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private var moreBarButtonRate: Double = 1.0
|
||||
private var moreBarButtonRateTimestamp: Double?
|
||||
|
||||
private let settingsBarButton: MoreHeaderButton
|
||||
private let settingsBarButton: SettingsHeaderButton
|
||||
|
||||
private var videoNode: UniversalVideoNode?
|
||||
private var videoNodeUserInteractionEnabled: Bool = false
|
||||
@ -1098,6 +1204,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
private let statusDisposable = MetaDisposable()
|
||||
private let moreButtonStateDisposable = MetaDisposable()
|
||||
private let settingsButtonStateDisposable = MetaDisposable()
|
||||
private let mediaPlaybackStateDisposable = MetaDisposable()
|
||||
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
@ -1113,6 +1220,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
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 let isShowingSettingsMenuPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
private let hasExpandedCaptionPromise = Promise<Bool>()
|
||||
private var hideControlsDisposable: Disposable?
|
||||
private var automaticPictureInPictureDisposable: Disposable?
|
||||
@ -1150,7 +1258,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
self.moreBarButton.isUserInteractionEnabled = true
|
||||
self.moreBarButton.setContent(.more(optionsCircleImage(dark: false)))
|
||||
|
||||
self.settingsBarButton = MoreHeaderButton()
|
||||
self.settingsBarButton = SettingsHeaderButton()
|
||||
self.settingsBarButton.isUserInteractionEnabled = true
|
||||
|
||||
super.init()
|
||||
@ -1282,9 +1390,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
self.titleContentView = GalleryTitleView(frame: CGRect())
|
||||
self._titleView.set(.single(self.titleContentView))
|
||||
|
||||
let shouldHideControlsSignal: Signal<Void, NoError> = combineLatest(self.isPlayingPromise.get(), self.isInteractingPromise.get(), self.controlsVisiblePromise.get(), self.isShowingContextMenuPromise.get(), self.hasExpandedCaptionPromise.get())
|
||||
|> mapToSignal { isPlaying, isInteracting, controlsVisible, isShowingContextMenu, hasExpandedCaptionPromise -> Signal<Void, NoError> in
|
||||
if isShowingContextMenu || hasExpandedCaptionPromise {
|
||||
let shouldHideControlsSignal: Signal<Void, NoError> = combineLatest(self.isPlayingPromise.get(), self.isInteractingPromise.get(), self.controlsVisiblePromise.get(), self.isShowingContextMenuPromise.get(), self.isShowingSettingsMenuPromise.get(), self.hasExpandedCaptionPromise.get())
|
||||
|> mapToSignal { isPlaying, isInteracting, controlsVisible, isShowingContextMenu, isShowingSettingsMenu, hasExpandedCaptionPromise -> Signal<Void, NoError> in
|
||||
if isShowingContextMenu || isShowingSettingsMenu || hasExpandedCaptionPromise {
|
||||
return .complete()
|
||||
}
|
||||
if isPlaying && !isInteracting && controlsVisible {
|
||||
@ -1306,6 +1414,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
deinit {
|
||||
self.statusDisposable.dispose()
|
||||
self.moreButtonStateDisposable.dispose()
|
||||
self.settingsButtonStateDisposable.dispose()
|
||||
self.mediaPlaybackStateDisposable.dispose()
|
||||
self.scrubbingFrameDisposable?.dispose()
|
||||
self.hideControlsDisposable?.dispose()
|
||||
@ -1453,11 +1562,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
isAdaptive = true
|
||||
}
|
||||
|
||||
if isAdaptive {
|
||||
self.settingsBarButton.setContent(.image(generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/NavigationSettingsQAuto"), color: .white)))
|
||||
} else {
|
||||
self.settingsBarButton.setContent(.image(generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/NavigationSettings"), color: .white)))
|
||||
}
|
||||
//TODO:release
|
||||
//self.settingsBarButton.setContent(.image(generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/NavigationSettingsQAuto"), color: .white)))
|
||||
let _ = isAdaptive
|
||||
|
||||
let dimensions = item.content.dimensions
|
||||
if dimensions.height > 0.0 {
|
||||
@ -1639,6 +1746,14 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
}))*/
|
||||
|
||||
self.settingsButtonStateDisposable.set((self.isShowingSettingsMenuPromise.get()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] isShowingSettingsMenu in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.settingsBarButton.setIsMenuOpen(isMenuOpen: isShowingSettingsMenu)
|
||||
}))
|
||||
|
||||
self.statusDisposable.set((combineLatest(queue: .mainQueue(), videoNode.status, mediaFileStatus)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value, fetchStatus in
|
||||
if let strongSelf = self {
|
||||
@ -2951,25 +3066,42 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
guard let controller = self.baseNavigationController()?.topViewController as? ViewController else {
|
||||
return
|
||||
}
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
let items: Signal<[ContextMenuItem], NoError>
|
||||
let items: Signal<(items: [ContextMenuItem], topItems: [ContextMenuItem]), NoError>
|
||||
if case let .message(message, _) = self.item?.contentInfo, let _ = message.adAttribute {
|
||||
items = self.adMenuMainItems()
|
||||
items = self.adMenuMainItems() |> map { items in
|
||||
return (items, [])
|
||||
}
|
||||
} else {
|
||||
items = self.contextMenuMainItems(isSettings: isSettings, dismiss: {
|
||||
dismissImpl?()
|
||||
})
|
||||
}
|
||||
|
||||
let contextController = ContextController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: gesture)
|
||||
self.isShowingContextMenuPromise.set(true)
|
||||
let contextController = ContextController(presentationData: self.presentationData.withUpdated(theme: defaultDarkColorPresentationTheme), source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: items |> map { items in
|
||||
if !items.topItems.isEmpty {
|
||||
return ContextController.Items(content: .twoLists(items.items, items.topItems))
|
||||
} else {
|
||||
return ContextController.Items(content: .list(items.items))
|
||||
}
|
||||
}, gesture: gesture)
|
||||
if isSettings {
|
||||
self.isShowingSettingsMenuPromise.set(true)
|
||||
} else {
|
||||
self.isShowingContextMenuPromise.set(true)
|
||||
}
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
dismissImpl = { [weak contextController] in
|
||||
contextController?.dismiss()
|
||||
}
|
||||
contextController.dismissed = { [weak self] in
|
||||
Queue.mainQueue().after(0.1, {
|
||||
self?.isShowingContextMenuPromise.set(false)
|
||||
Queue.mainQueue().after(isSettings ? 0.0 : 0.1, {
|
||||
if isSettings {
|
||||
self?.isShowingSettingsMenuPromise.set(false)
|
||||
} else {
|
||||
self?.isShowingContextMenuPromise.set(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -3112,9 +3244,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
|
||||
|
||||
private func contextMenuMainItems(isSettings: Bool, dismiss: @escaping () -> Void) -> Signal<[ContextMenuItem], NoError> {
|
||||
private func contextMenuMainItems(isSettings: Bool, dismiss: @escaping () -> Void) -> Signal<(items: [ContextMenuItem], topItems: [ContextMenuItem]), NoError> {
|
||||
guard let videoNode = self.videoNode, let item = self.item else {
|
||||
return .single([])
|
||||
return .single(([], []))
|
||||
}
|
||||
|
||||
let peer: Signal<EnginePeer?, NoError>
|
||||
@ -3126,129 +3258,131 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
return combineLatest(queue: Queue.mainQueue(), videoNode.status, peer)
|
||||
|> take(1)
|
||||
|> map { [weak self] status, peer -> [ContextMenuItem] in
|
||||
|> map { [weak self] status, peer -> (items: [ContextMenuItem], topItems: [ContextMenuItem]) in
|
||||
guard let status = status, let strongSelf = self else {
|
||||
return []
|
||||
return ([], [])
|
||||
}
|
||||
|
||||
var topItems: [ContextMenuItem] = []
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
if isSettings {
|
||||
var speedValue: String = strongSelf.presentationData.strings.PlaybackSpeed_Normal
|
||||
var speedIconText: String = "1x"
|
||||
var didSetSpeedValue = false
|
||||
for (text, iconText, speed) in strongSelf.speedList(strings: strongSelf.presentationData.strings) {
|
||||
if abs(speed - status.baseRate) < 0.01 {
|
||||
speedValue = text
|
||||
speedIconText = iconText
|
||||
didSetSpeedValue = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !didSetSpeedValue && status.baseRate != 1.0 {
|
||||
speedValue = String(format: "%.1fx", status.baseRate)
|
||||
speedIconText = speedValue
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.PlaybackSpeed_Title, 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)
|
||||
let sliderValuePromise = ValuePromise<Double?>(nil)
|
||||
topItems.append(.custom(SliderContextItem(title: "Speed", minValue: 0.2, maxValue: 2.5, value: status.baseRate, valueChanged: { [weak self] newValue, _ in
|
||||
guard let strongSelf = self, let videoNode = strongSelf.videoNode else {
|
||||
return
|
||||
}
|
||||
|
||||
c?.pushItems(items: strongSelf.contextMenuSpeedItems(dismiss: dismiss) |> map { ContextController.Items(content: .list($0)) })
|
||||
})))
|
||||
let newValue = normalizeValue(newValue)
|
||||
videoNode.setBaseRate(newValue)
|
||||
if let controller = strongSelf.galleryController() as? GalleryController {
|
||||
controller.updateSharedPlaybackRate(newValue)
|
||||
}
|
||||
sliderValuePromise.set(newValue)
|
||||
}), true))
|
||||
|
||||
if let videoQualityState = strongSelf.videoNode?.videoQualityState(), !videoQualityState.available.isEmpty {
|
||||
items.append(.separator)
|
||||
} else {
|
||||
items.append(.custom(SectionTitleContextItem(text: "PLAYBACK SPEED"), false))
|
||||
for (text, _, rate) in strongSelf.speedList(strings: strongSelf.presentationData.strings) {
|
||||
let isSelected = abs(status.baseRate - rate) < 0.01
|
||||
items.append(.action(ContextMenuActionItem(text: text, icon: { _ in return nil }, iconSource: ContextMenuActionItemIconSource(size: CGSize(width: 24.0, height: 24.0), signal: sliderValuePromise.get()
|
||||
|> map { value in
|
||||
if isSelected && value == nil {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}), action: { _, f in
|
||||
f(.default)
|
||||
|
||||
guard let strongSelf = self, let videoNode = strongSelf.videoNode else {
|
||||
return
|
||||
}
|
||||
|
||||
videoNode.setBaseRate(rate)
|
||||
if let controller = strongSelf.galleryController() as? GalleryController {
|
||||
controller.updateSharedPlaybackRate(rate)
|
||||
}
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
if let videoQualityState = strongSelf.videoNode?.videoQualityState(), !videoQualityState.available.isEmpty {
|
||||
items.append(.custom(SectionTitleContextItem(text: "VIDEO QUALITY"), false))
|
||||
|
||||
//TODO:localize
|
||||
|
||||
let qualityText: String
|
||||
switch videoQualityState.preferred {
|
||||
case .auto:
|
||||
do {
|
||||
let isSelected = videoQualityState.preferred == .auto
|
||||
let qualityText: String = "Auto"
|
||||
let textLayout: ContextMenuActionItemTextLayout
|
||||
if videoQualityState.current != 0 {
|
||||
qualityText = "Auto (\(videoQualityState.current)p)"
|
||||
textLayout = .secondLineWithValue("\(videoQualityState.current)p")
|
||||
} else {
|
||||
qualityText = "Auto"
|
||||
textLayout = .singleLine
|
||||
}
|
||||
case let .quality(value):
|
||||
qualityText = "\(value)p"
|
||||
items.append(.action(ContextMenuActionItem(text: qualityText, textLayout: textLayout, icon: { _ in
|
||||
if isSelected {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self, let videoNode = self.videoNode else {
|
||||
return
|
||||
}
|
||||
videoNode.setVideoQuality(.auto)
|
||||
//TODO:release
|
||||
//self.settingsBarButton.setContent(.image(generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/NavigationSettingsQAuto"), color: .white)))
|
||||
|
||||
/*if let controller = strongSelf.galleryController() as? GalleryController {
|
||||
controller.updateSharedPlaybackRate(rate)
|
||||
}*/
|
||||
})))
|
||||
}
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "Video Quality", textLayout: .secondLineWithValue(qualityText), icon: { _ in
|
||||
return nil
|
||||
}, action: { c, _ in
|
||||
guard let strongSelf = self else {
|
||||
c?.dismiss(completion: nil)
|
||||
return
|
||||
for quality in videoQualityState.available {
|
||||
let isSelected = videoQualityState.preferred == .quality(quality)
|
||||
let qualityTitle: String
|
||||
if quality <= 360 {
|
||||
qualityTitle = "Low"
|
||||
} else if quality <= 480 {
|
||||
qualityTitle = "Medium"
|
||||
} else if quality <= 720 {
|
||||
qualityTitle = "High"
|
||||
} else {
|
||||
qualityTitle = "Ultra-High"
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: qualityTitle, textLayout: .secondLineWithValue("\(quality)p"), icon: { _ in
|
||||
if isSelected {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
c?.pushItems(items: .single(ContextController.Items(content: .list(strongSelf.contextMenuVideoQualityItems(dismiss: dismiss)))))
|
||||
})))
|
||||
guard let self, let videoNode = self.videoNode else {
|
||||
return
|
||||
}
|
||||
videoNode.setVideoQuality(.quality(quality))
|
||||
//TODO:release
|
||||
/*if quality >= 700 {
|
||||
self.settingsBarButton.setContent(.image(generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/NavigationSettingsQHD"), color: .white)))
|
||||
} else {
|
||||
self.settingsBarButton.setContent(.image(generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/NavigationSettingsQSD"), color: .white)))
|
||||
}*/
|
||||
|
||||
/*if let controller = strongSelf.galleryController() as? GalleryController {
|
||||
controller.updateSharedPlaybackRate(rate)
|
||||
}*/
|
||||
})))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let (message, _, _) = strongSelf.contentInfo() {
|
||||
let context = strongSelf.context
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
|
||||
guard let strongSelf = self, let peer = peer else {
|
||||
return
|
||||
}
|
||||
if let navigationController = strongSelf.baseNavigationController() {
|
||||
strongSelf.beginCustomDismiss(true)
|
||||
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false)))
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
strongSelf.completeCustomDismiss(false)
|
||||
}
|
||||
}
|
||||
f(.default)
|
||||
})))
|
||||
}
|
||||
|
||||
// if #available(iOS 11.0, *) {
|
||||
// items.append(.action(ContextMenuActionItem(text: "AirPlay", textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/AirPlay"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
// f(.default)
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// strongSelf.beginAirPlaySetup()
|
||||
// })))
|
||||
// }
|
||||
|
||||
if let (message, _, _) = strongSelf.contentInfo() {
|
||||
for media in message.media {
|
||||
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
let url = content.url
|
||||
|
||||
let item = OpenInItem.url(url: url)
|
||||
let openText = strongSelf.presentationData.strings.Conversation_FileOpenIn
|
||||
items.append(.action(ContextMenuActionItem(text: openText, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
if let strongSelf = self, let controller = strongSelf.galleryController() {
|
||||
var presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
if !presentationData.theme.overallDarkAppearance {
|
||||
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
}
|
||||
let actionSheet = OpenInActionSheetController(context: strongSelf.context, forceTheme: presentationData.theme, item: item, openUrl: { [weak self] url in
|
||||
if let strongSelf = self {
|
||||
strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: strongSelf.baseNavigationController(), dismissInput: {})
|
||||
}
|
||||
})
|
||||
controller.present(actionSheet, in: .window(.root))
|
||||
}
|
||||
})))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let (message, maybeFile, _) = strongSelf.contentInfo(), let file = maybeFile, !message.isCopyProtected() && !item.peerIsCopyProtected && message.paidContent == nil {
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Gallery_SaveVideo, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { c, _ in
|
||||
items.append(.action(ContextMenuActionItem(text: "Save to Gallery", icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Download"), color: theme.actionSheet.primaryTextColor) }, action: { c, _ in
|
||||
guard let self else {
|
||||
c?.dismiss(result: .default, completion: nil)
|
||||
return
|
||||
@ -3281,7 +3415,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
continue
|
||||
}
|
||||
let fileSizeString = dataSizeString(qualityFileSize, formatting: DataSizeStringFormatting(presentationData: self.presentationData))
|
||||
items.append(.action(ContextMenuActionItem(text: "\(quality)p (\(fileSizeString))", icon: { _ in
|
||||
items.append(.action(ContextMenuActionItem(text: "Save in \(quality)p", textLayout: .secondLineWithValue(fileSizeString), icon: { _ in
|
||||
return nil
|
||||
}, action: { [weak self] c, _ in
|
||||
c?.dismiss(result: .default, completion: nil)
|
||||
@ -3351,6 +3485,66 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
})))
|
||||
}
|
||||
|
||||
if !items.isEmpty {
|
||||
items.append(.separator)
|
||||
}
|
||||
if let (message, _, _) = strongSelf.contentInfo() {
|
||||
let context = strongSelf.context
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
|
||||
guard let strongSelf = self, let peer = peer else {
|
||||
return
|
||||
}
|
||||
if let navigationController = strongSelf.baseNavigationController() {
|
||||
strongSelf.beginCustomDismiss(true)
|
||||
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false)))
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
strongSelf.completeCustomDismiss(false)
|
||||
}
|
||||
}
|
||||
f(.default)
|
||||
})))
|
||||
}
|
||||
|
||||
// if #available(iOS 11.0, *) {
|
||||
// items.append(.action(ContextMenuActionItem(text: "AirPlay", textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/AirPlay"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
// f(.default)
|
||||
// guard let strongSelf = self else {
|
||||
// return
|
||||
// }
|
||||
// strongSelf.beginAirPlaySetup()
|
||||
// })))
|
||||
// }
|
||||
|
||||
if let (message, _, _) = strongSelf.contentInfo() {
|
||||
for media in message.media {
|
||||
if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content {
|
||||
let url = content.url
|
||||
|
||||
let item = OpenInItem.url(url: url)
|
||||
let openText = strongSelf.presentationData.strings.Conversation_FileOpenIn
|
||||
items.append(.action(ContextMenuActionItem(text: openText, textColor: .primary, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Share"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||
f(.default)
|
||||
|
||||
if let strongSelf = self, let controller = strongSelf.galleryController() {
|
||||
var presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
if !presentationData.theme.overallDarkAppearance {
|
||||
presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
}
|
||||
let actionSheet = OpenInActionSheetController(context: strongSelf.context, forceTheme: presentationData.theme, item: item, openUrl: { [weak self] url in
|
||||
if let strongSelf = self {
|
||||
strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: strongSelf.baseNavigationController(), dismissInput: {})
|
||||
}
|
||||
})
|
||||
controller.present(actionSheet, in: .window(.root))
|
||||
}
|
||||
})))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let peer, let (message, _, _) = strongSelf.contentInfo(), canSendMessagesToPeer(peer._asPeer()) {
|
||||
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuReply, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
|
||||
if let self, let navigationController = self.baseNavigationController() {
|
||||
@ -3377,148 +3571,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
return (items, topItems)
|
||||
}
|
||||
}
|
||||
|
||||
private func contextMenuSpeedItems(dismiss: @escaping () -> Void) -> 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] = []
|
||||
|
||||
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)
|
||||
}, iconPosition: .left, action: { c, _ in
|
||||
c?.popItems()
|
||||
})))
|
||||
|
||||
let sliderValuePromise = ValuePromise<Double?>(nil)
|
||||
items.append(.custom(SliderContextItem(minValue: 0.2, maxValue: 2.5, value: status.baseRate, valueChanged: { [weak self] newValue, _ in
|
||||
guard let strongSelf = self, let videoNode = strongSelf.videoNode else {
|
||||
return
|
||||
}
|
||||
let newValue = normalizeValue(newValue)
|
||||
videoNode.setBaseRate(newValue)
|
||||
if let controller = strongSelf.galleryController() as? GalleryController {
|
||||
controller.updateSharedPlaybackRate(newValue)
|
||||
}
|
||||
sliderValuePromise.set(newValue)
|
||||
}), true))
|
||||
|
||||
items.append(.separator)
|
||||
|
||||
for (text, _, rate) in strongSelf.speedList(strings: strongSelf.presentationData.strings) {
|
||||
let isSelected = abs(status.baseRate - rate) < 0.01
|
||||
items.append(.action(ContextMenuActionItem(text: text, icon: { _ in return nil }, iconSource: ContextMenuActionItemIconSource(size: CGSize(width: 24.0, height: 24.0), signal: sliderValuePromise.get()
|
||||
|> map { value in
|
||||
if isSelected && value == nil {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}), action: { _, f in
|
||||
f(.default)
|
||||
|
||||
guard let strongSelf = self, let videoNode = strongSelf.videoNode else {
|
||||
return
|
||||
}
|
||||
|
||||
videoNode.setBaseRate(rate)
|
||||
if let controller = strongSelf.galleryController() as? GalleryController {
|
||||
controller.updateSharedPlaybackRate(rate)
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
||||
private func contextMenuVideoQualityItems(dismiss: @escaping () -> Void) -> [ContextMenuItem] {
|
||||
guard let videoNode = self.videoNode else {
|
||||
return []
|
||||
}
|
||||
guard let qualityState = videoNode.videoQualityState(), !qualityState.available.isEmpty else {
|
||||
return []
|
||||
}
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Common_Back, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor)
|
||||
}, iconPosition: .left, action: { c, _ in
|
||||
c?.popItems()
|
||||
})))
|
||||
|
||||
do {
|
||||
let isSelected = qualityState.preferred == .auto
|
||||
let qualityText: String
|
||||
if qualityState.current != 0 {
|
||||
qualityText = "Auto (\(qualityState.current)p)"
|
||||
} else {
|
||||
qualityText = "Auto"
|
||||
}
|
||||
items.append(.action(ContextMenuActionItem(text: qualityText, icon: { _ in
|
||||
if isSelected {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self, let videoNode = self.videoNode else {
|
||||
return
|
||||
}
|
||||
videoNode.setVideoQuality(.auto)
|
||||
self.settingsBarButton.setContent(.image(generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/NavigationSettingsQAuto"), color: .white)))
|
||||
|
||||
/*if let controller = strongSelf.galleryController() as? GalleryController {
|
||||
controller.updateSharedPlaybackRate(rate)
|
||||
}*/
|
||||
})))
|
||||
}
|
||||
|
||||
for quality in qualityState.available {
|
||||
let isSelected = qualityState.preferred == .quality(quality)
|
||||
items.append(.action(ContextMenuActionItem(text: "\(quality)p", icon: { _ in
|
||||
if isSelected {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self, let videoNode = self.videoNode else {
|
||||
return
|
||||
}
|
||||
videoNode.setVideoQuality(.quality(quality))
|
||||
if quality >= 700 {
|
||||
self.settingsBarButton.setContent(.image(generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/NavigationSettingsQHD"), color: .white)))
|
||||
} else {
|
||||
self.settingsBarButton.setContent(.image(generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/NavigationSettingsQSD"), color: .white)))
|
||||
}
|
||||
|
||||
/*if let controller = strongSelf.galleryController() as? GalleryController {
|
||||
controller.updateSharedPlaybackRate(rate)
|
||||
}*/
|
||||
})))
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
private var isAirPlayActive = false
|
||||
private var externalVideoPlayer: ExternalVideoPlayer?
|
||||
func beginAirPlaySetup() {
|
||||
|
@ -709,6 +709,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
|
||||
private var forceStopAnimations: Bool = false
|
||||
|
||||
typealias Params = (item: ChatMessageItem, params: ListViewItemLayoutParams, mergedTop: ChatMessageMerge, mergedBottom: ChatMessageMerge, dateHeaderAtBottom: Bool)
|
||||
private var currentInputParams: Params?
|
||||
private var currentApplyParams: ListViewItemApply?
|
||||
|
||||
required public init(rotated: Bool) {
|
||||
self.mainContextSourceNode = ContextExtractedContentContainingNode()
|
||||
self.mainContainerNode = ContextControllerSourceNode()
|
||||
@ -1363,6 +1367,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
}
|
||||
|
||||
private func internalUpdateLayout() {
|
||||
if let inputParams = self.currentInputParams, let currentApplyParams = self.currentApplyParams {
|
||||
let (_, applyLayout) = self.asyncLayout()(inputParams.item, inputParams.params, inputParams.mergedTop, inputParams.mergedBottom, inputParams.dateHeaderAtBottom)
|
||||
applyLayout(.None, ListViewItemApply(isOnScreen: currentApplyParams.isOnScreen, timestamp: nil), false)
|
||||
}
|
||||
}
|
||||
|
||||
override public func asyncLayout() -> (_ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool) -> (ListViewItemNodeLayout, (ListViewItemUpdateAnimation, ListViewItemApply, Bool) -> Void) {
|
||||
var currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, Int?, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))] = []
|
||||
for contentNode in self.contentNodes {
|
||||
@ -1421,7 +1432,12 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
|
||||
private static func beginLayout(
|
||||
selfReference: Weak<ChatMessageBubbleItemNode>, _ item: ChatMessageItem, _ params: ListViewItemLayoutParams, _ mergedTop: ChatMessageMerge, _ mergedBottom: ChatMessageMerge, _ dateHeaderAtBottom: Bool,
|
||||
selfReference: Weak<ChatMessageBubbleItemNode>,
|
||||
_ item: ChatMessageItem,
|
||||
_ params: ListViewItemLayoutParams,
|
||||
_ mergedTop: ChatMessageMerge,
|
||||
_ mergedBottom: ChatMessageMerge,
|
||||
_ dateHeaderAtBottom: Bool,
|
||||
currentContentClassesPropertiesAndLayouts: [(Message, AnyClass, Bool, Int?, (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, CGSize?, CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))))],
|
||||
authorNameLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||
viaMeasureLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode),
|
||||
@ -3095,6 +3111,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
|
||||
return (layout, { animation, applyInfo, synchronousLoads in
|
||||
return ChatMessageBubbleItemNode.applyLayout(selfReference: selfReference, animation, synchronousLoads,
|
||||
inputParams: (item, params, mergedTop, mergedBottom, dateHeaderAtBottom),
|
||||
params: params,
|
||||
applyInfo: applyInfo,
|
||||
layout: layout,
|
||||
@ -3153,6 +3170,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
private static func applyLayout(selfReference: Weak<ChatMessageBubbleItemNode>,
|
||||
_ animation: ListViewItemUpdateAnimation,
|
||||
_ synchronousLoads: Bool,
|
||||
inputParams: Params,
|
||||
params: ListViewItemLayoutParams,
|
||||
applyInfo: ListViewItemApply,
|
||||
layout: ListViewItemNodeLayout,
|
||||
@ -3209,6 +3227,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.currentInputParams = inputParams
|
||||
strongSelf.currentApplyParams = applyInfo
|
||||
|
||||
if item.message.id.namespace == Namespaces.Message.Local || item.message.id.namespace == Namespaces.Message.ScheduledLocal || item.message.id.namespace == Namespaces.Message.QuickReplyLocal {
|
||||
strongSelf.wasPending = true
|
||||
}
|
||||
@ -4025,10 +4046,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
contextSourceNode?.updateDistractionFreeMode?(value)
|
||||
}
|
||||
contentNode.requestInlineUpdate = { [weak strongSelf] in
|
||||
guard let strongSelf, let item = strongSelf.item else {
|
||||
guard let strongSelf else {
|
||||
return
|
||||
}
|
||||
item.controllerInteraction.requestMessageUpdate(item.message.id, false)
|
||||
|
||||
strongSelf.internalUpdateLayout()
|
||||
}
|
||||
contentNode.updateIsExtractedToContextPreview(contextSourceNode.isExtractedToContextPreview)
|
||||
}
|
||||
|
@ -627,7 +627,11 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
}
|
||||
|
||||
public func isAvailableForGalleryTransition() -> Bool {
|
||||
return self.automaticPlayback ?? false
|
||||
if let automaticPlayback = self.automaticPlayback, automaticPlayback, self.decoration != nil {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public func isAvailableForInstantPageTransition() -> Bool {
|
||||
@ -1094,9 +1098,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
} else {
|
||||
mediaUpdated = true
|
||||
}
|
||||
if hlsInlinePlaybackRange != appliedHlsInlinePlaybackRange {
|
||||
mediaUpdated = true
|
||||
}
|
||||
let inlinePlaybackRangeUpdated = hlsInlinePlaybackRange != appliedHlsInlinePlaybackRange
|
||||
|
||||
var isSendingUpdated = false
|
||||
if let currentMessage = currentMessage {
|
||||
@ -1154,7 +1156,8 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
}
|
||||
}
|
||||
|
||||
if mediaUpdated || isSendingUpdated || automaticPlaybackUpdated {
|
||||
let reloadMedia = mediaUpdated || isSendingUpdated || automaticPlaybackUpdated
|
||||
if mediaUpdated || isSendingUpdated || automaticPlaybackUpdated || inlinePlaybackRangeUpdated {
|
||||
var media = media
|
||||
|
||||
var extendedMedia: TelegramExtendedMedia?
|
||||
@ -1381,31 +1384,6 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
chatMessageWebFileCancelInteractiveFetch(account: context.account, image: image)
|
||||
})
|
||||
} else if var file = media as? TelegramMediaFile {
|
||||
if isSecretMedia {
|
||||
updateImageSignal = { synchronousLoad, _ in
|
||||
return chatSecretMessageVideo(account: context.account, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file))
|
||||
}
|
||||
} else {
|
||||
if file.isAnimatedSticker {
|
||||
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
updateImageSignal = { synchronousLoad, _ in
|
||||
return chatMessageAnimatedSticker(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), file: file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0)))
|
||||
}
|
||||
} else if file.isSticker || file.isVideoSticker {
|
||||
updateImageSignal = { synchronousLoad, _ in
|
||||
return chatMessageSticker(account: context.account, userLocation: .peer(message.id.peerId), file: file, small: false)
|
||||
}
|
||||
} else {
|
||||
onlyFullSizeVideoThumbnail = isSendingUpdated
|
||||
updateImageSignal = { synchronousLoad, _ in
|
||||
return mediaGridMessageVideo(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file), onlyFullSize: currentMedia?.id?.namespace == Namespaces.Media.LocalFile, autoFetchFullSizeThumbnail: true)
|
||||
}
|
||||
updateBlurredImageSignal = { synchronousLoad, _ in
|
||||
return chatSecretMessageVideo(account: context.account, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file), synchronousLoad: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var uploading = false
|
||||
if file.resource is VideoLibraryMediaResource {
|
||||
uploading = true
|
||||
@ -1463,6 +1441,31 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
}
|
||||
}
|
||||
|
||||
if isSecretMedia {
|
||||
updateImageSignal = { synchronousLoad, _ in
|
||||
return chatSecretMessageVideo(account: context.account, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file))
|
||||
}
|
||||
} else {
|
||||
if file.isAnimatedSticker {
|
||||
let dimensions = file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
updateImageSignal = { synchronousLoad, _ in
|
||||
return chatMessageAnimatedSticker(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), file: file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 400.0, height: 400.0)))
|
||||
}
|
||||
} else if file.isSticker || file.isVideoSticker {
|
||||
updateImageSignal = { synchronousLoad, _ in
|
||||
return chatMessageSticker(account: context.account, userLocation: .peer(message.id.peerId), file: file, small: false)
|
||||
}
|
||||
} else {
|
||||
onlyFullSizeVideoThumbnail = isSendingUpdated
|
||||
updateImageSignal = { synchronousLoad, _ in
|
||||
return mediaGridMessageVideo(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file), onlyFullSize: currentMedia?.id?.namespace == Namespaces.Media.LocalFile, autoFetchFullSizeThumbnail: true)
|
||||
}
|
||||
updateBlurredImageSignal = { synchronousLoad, _ in
|
||||
return chatSecretMessageVideo(account: context.account, userLocation: .peer(message.id.peerId), videoReference: .message(message: MessageReference(message), media: file), synchronousLoad: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatedFetchControls = FetchControls(fetch: { manual in
|
||||
if let strongSelf = self {
|
||||
if file.isAnimated {
|
||||
@ -1531,6 +1534,9 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
}
|
||||
}
|
||||
}
|
||||
if !reloadMedia {
|
||||
updateImageSignal = nil
|
||||
}
|
||||
|
||||
var isExtendedMedia = false
|
||||
if statusUpdated {
|
||||
@ -1988,19 +1994,18 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
guard let strongSelf else {
|
||||
return
|
||||
}
|
||||
let hlsInlinePlaybackRange: Range<Int64>?
|
||||
if let preloadData {
|
||||
strongSelf.hlsInlinePlaybackRange = preloadData.1
|
||||
hlsInlinePlaybackRange = preloadData.1
|
||||
} else {
|
||||
strongSelf.hlsInlinePlaybackRange = nil
|
||||
hlsInlinePlaybackRange = nil
|
||||
}
|
||||
if strongSelf.hlsInlinePlaybackRange != hlsInlinePlaybackRange {
|
||||
strongSelf.hlsInlinePlaybackRange = hlsInlinePlaybackRange
|
||||
strongSelf.requestInlineUpdate?()
|
||||
}
|
||||
strongSelf.requestInlineUpdate?()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if let hlsInlinePlaybackRangeDisposable = strongSelf.hlsInlinePlaybackRangeDisposable {
|
||||
strongSelf.hlsInlinePlaybackRangeDisposable = nil
|
||||
hlsInlinePlaybackRangeDisposable.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -0,0 +1,21 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "SectionTitleContextItem",
|
||||
module_name = "SectionTitleContextItem",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/ContextUI",
|
||||
"//submodules/TelegramPresentationData",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,89 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ContextUI
|
||||
import TelegramPresentationData
|
||||
|
||||
public final class SectionTitleContextItem: ContextMenuCustomItem {
|
||||
let text: String
|
||||
|
||||
public init(text: String) {
|
||||
self.text = text
|
||||
}
|
||||
|
||||
public func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||
return SectionTitleContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected)
|
||||
}
|
||||
}
|
||||
|
||||
private final class SectionTitleContextItemNode: ASDisplayNode, ContextMenuCustomNode {
|
||||
private let item: SectionTitleContextItem
|
||||
private let presentationData: PresentationData
|
||||
private let getController: () -> ContextControllerProtocol?
|
||||
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let textNode: ImmediateTextNode
|
||||
|
||||
var needsSeparator: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
init(presentationData: PresentationData, item: SectionTitleContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||
self.item = item
|
||||
self.presentationData = presentationData
|
||||
self.getController = getController
|
||||
self.actionSelected = actionSelected
|
||||
|
||||
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 12.0 / 17.0)
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isAccessibilityElement = false
|
||||
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.sectionSeparatorColor
|
||||
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.isAccessibilityElement = false
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.attributedText = NSAttributedString(string: item.text, font: textFont, textColor: presentationData.theme.contextMenu.secondaryColor)
|
||||
self.textNode.maximumNumberOfLines = 1
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.textNode)
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let sideInset: CGFloat = 16.0
|
||||
|
||||
let textSize = self.textNode.updateLayout(CGSize(width: constrainedWidth - sideInset - sideInset, height: .greatestFiniteMagnitude))
|
||||
let height: CGFloat = 28.0
|
||||
|
||||
return (CGSize(width: textSize.width + sideInset + sideInset, height: height), { size, transition in
|
||||
let verticalOrigin = floor((size.height - textSize.height) / 2.0)
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize)
|
||||
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||
})
|
||||
}
|
||||
|
||||
func updateTheme(presentationData: PresentationData) {
|
||||
self.backgroundNode.backgroundColor = presentationData.theme.contextMenu.sectionSeparatorColor
|
||||
|
||||
let textFont = Font.regular(presentationData.listsFontSize.baseDisplaySize * 12.0 / 17.0)
|
||||
self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: textFont, textColor: presentationData.theme.contextMenu.secondaryColor)
|
||||
}
|
||||
|
||||
func canBeHighlighted() -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func updateIsHighlighted(isHighlighted: Bool) {
|
||||
}
|
||||
|
||||
func performAction() {
|
||||
}
|
||||
}
|
@ -8,12 +8,14 @@ import TelegramPresentationData
|
||||
import AnimatedCountLabelNode
|
||||
|
||||
public final class SliderContextItem: ContextMenuCustomItem {
|
||||
private let title: String?
|
||||
private let minValue: CGFloat
|
||||
private let maxValue: CGFloat
|
||||
private let value: CGFloat
|
||||
private let valueChanged: (CGFloat, Bool) -> Void
|
||||
|
||||
public init(minValue: CGFloat, maxValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
|
||||
public init(title: String? = nil, minValue: CGFloat, maxValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
|
||||
self.title = title
|
||||
self.minValue = minValue
|
||||
self.maxValue = maxValue
|
||||
self.value = value
|
||||
@ -21,7 +23,7 @@ public final class SliderContextItem: ContextMenuCustomItem {
|
||||
}
|
||||
|
||||
public func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||
return SliderContextItemNode(presentationData: presentationData, getController: getController, minValue: self.minValue, maxValue: self.maxValue, value: self.value, valueChanged: self.valueChanged)
|
||||
return SliderContextItemNode(presentationData: presentationData, getController: getController, title: self.title, minValue: self.minValue, maxValue: self.maxValue, value: self.value, valueChanged: self.valueChanged)
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,12 +33,18 @@ private final class SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode,
|
||||
private var presentationData: PresentationData
|
||||
|
||||
private(set) var vibrancyEffectView: UIVisualEffectView?
|
||||
|
||||
private let backgroundTitleNode: ImmediateTextNode
|
||||
private let dimBackgroundTitleNode: ImmediateTextNode
|
||||
private let foregroundTitleNode: ImmediateTextNode
|
||||
|
||||
private let backgroundTextNode: ImmediateAnimatedCountLabelNode
|
||||
private let dimBackgroundTextNode: ImmediateAnimatedCountLabelNode
|
||||
|
||||
private let foregroundNode: ASDisplayNode
|
||||
private let foregroundTextNode: ImmediateAnimatedCountLabelNode
|
||||
|
||||
let title: String?
|
||||
let minValue: CGFloat
|
||||
let maxValue: CGFloat
|
||||
var value: CGFloat = 1.0 {
|
||||
@ -49,13 +57,18 @@ private final class SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode,
|
||||
|
||||
private let hapticFeedback = HapticFeedback()
|
||||
|
||||
init(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, minValue: CGFloat, maxValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
|
||||
init(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, title: String?, minValue: CGFloat, maxValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
|
||||
self.presentationData = presentationData
|
||||
self.title = title
|
||||
self.minValue = minValue
|
||||
self.maxValue = maxValue
|
||||
self.value = value
|
||||
self.valueChanged = valueChanged
|
||||
|
||||
self.backgroundTitleNode = ImmediateTextNode()
|
||||
self.dimBackgroundTitleNode = ImmediateTextNode()
|
||||
self.foregroundTitleNode = ImmediateTextNode()
|
||||
|
||||
self.backgroundTextNode = ImmediateAnimatedCountLabelNode()
|
||||
self.backgroundTextNode.alwaysOneDirection = true
|
||||
|
||||
@ -76,7 +89,6 @@ private final class SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode,
|
||||
self.isUserInteractionEnabled = true
|
||||
|
||||
if presentationData.theme.overallDarkAppearance {
|
||||
|
||||
} else {
|
||||
let style: UIBlurEffect.Style
|
||||
style = .extraLight
|
||||
@ -87,9 +99,12 @@ private final class SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode,
|
||||
self.vibrancyEffectView = vibrancyEffectView
|
||||
}
|
||||
|
||||
self.addSubnode(self.backgroundTitleNode)
|
||||
self.addSubnode(self.dimBackgroundTitleNode)
|
||||
self.addSubnode(self.backgroundTextNode)
|
||||
self.addSubnode(self.dimBackgroundTextNode)
|
||||
self.addSubnode(self.foregroundNode)
|
||||
self.foregroundNode.addSubnode(self.foregroundTitleNode)
|
||||
self.foregroundNode.addSubnode(self.foregroundTextNode)
|
||||
|
||||
let stringValue = "1.0x"
|
||||
@ -114,6 +129,11 @@ private final class SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode,
|
||||
textCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
self.backgroundTitleNode.attributedText = NSAttributedString(string: self.title ?? "", font: textFont, textColor: backgroundTextColor)
|
||||
self.dimBackgroundTitleNode.attributedText = NSAttributedString(string: self.title ?? "", font: textFont, textColor: dimBackgroundTextColor)
|
||||
self.foregroundTitleNode.attributedText = NSAttributedString(string: self.title ?? "", font: textFont, textColor: foregroundTextColor)
|
||||
|
||||
self.dimBackgroundTextNode.segments = dimBackgroundSegments
|
||||
self.backgroundTextNode.segments = backgroundSegments
|
||||
self.foregroundTextNode.segments = foregroundSegments
|
||||
@ -179,6 +199,10 @@ private final class SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode,
|
||||
self.backgroundTextNode.segments = backgroundSegments
|
||||
self.foregroundTextNode.segments = foregroundSegments
|
||||
|
||||
self.backgroundTitleNode.attributedText = NSAttributedString(string: self.title ?? "", font: textFont, textColor: backgroundTextColor)
|
||||
self.dimBackgroundTitleNode.attributedText = NSAttributedString(string: self.title ?? "", font: textFont, textColor: dimBackgroundTextColor)
|
||||
self.foregroundTitleNode.attributedText = NSAttributedString(string: self.title ?? "", font: textFont, textColor: foregroundTextColor)
|
||||
|
||||
let _ = self.dimBackgroundTextNode.updateLayout(size: CGSize(width: 70.0, height: .greatestFiniteMagnitude), animated: transition.isAnimated)
|
||||
let _ = self.backgroundTextNode.updateLayout(size: CGSize(width: 70.0, height: .greatestFiniteMagnitude), animated: transition.isAnimated)
|
||||
let _ = self.foregroundTextNode.updateLayout(size: CGSize(width: 70.0, height: .greatestFiniteMagnitude), animated: transition.isAnimated)
|
||||
@ -188,20 +212,41 @@ private final class SliderContextItemNode: ASDisplayNode, ContextMenuCustomNode,
|
||||
let valueWidth: CGFloat = 70.0
|
||||
let height: CGFloat = 45.0
|
||||
|
||||
var backgroundTextSize = self.backgroundTextNode.updateLayout(size: CGSize(width: 70.0, height: .greatestFiniteMagnitude), animated: true)
|
||||
let originalBackgroundTextSize = self.backgroundTextNode.updateLayout(size: CGSize(width: 70.0, height: .greatestFiniteMagnitude), animated: true)
|
||||
var backgroundTextSize = originalBackgroundTextSize
|
||||
backgroundTextSize.width = valueWidth
|
||||
|
||||
let backgroundTitleSize = self.backgroundTitleNode.updateLayout(CGSize(width: 120.0, height: 100.0))
|
||||
let _ = self.dimBackgroundTitleNode.updateLayout(CGSize(width: 120.0, height: 100.0))
|
||||
let _ = self.foregroundTitleNode.updateLayout(CGSize(width: 120.0, height: 100.0))
|
||||
|
||||
return (CGSize(width: height * 3.0, height: height), { size, transition in
|
||||
let leftInset: CGFloat = 17.0
|
||||
|
||||
self.vibrancyEffectView?.frame = CGRect(origin: .zero, size: size)
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - backgroundTextSize.height) / 2.0)), size: backgroundTextSize)
|
||||
let backgroundTextWidth = self.backgroundTextNode.updateLayout(size: CGSize(width: 70.0, height: .greatestFiniteMagnitude), animated: true).width
|
||||
|
||||
self.updateValue(transition: transition)
|
||||
|
||||
let titleFrame: CGRect
|
||||
let textFrame: CGRect
|
||||
|
||||
titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - backgroundTitleSize.height) / 2.0)), size: backgroundTitleSize)
|
||||
|
||||
if self.title != nil {
|
||||
textFrame = CGRect(origin: CGPoint(x: size.width - leftInset - backgroundTextWidth, y: floor((height - backgroundTextSize.height) / 2.0)), size: backgroundTextSize)
|
||||
} else {
|
||||
textFrame = CGRect(origin: CGPoint(x: leftInset, y: floor((height - backgroundTextSize.height) / 2.0)), size: backgroundTextSize)
|
||||
}
|
||||
|
||||
transition.updateFrameAdditive(node: self.dimBackgroundTitleNode, frame: titleFrame)
|
||||
transition.updateFrameAdditive(node: self.backgroundTitleNode, frame: titleFrame)
|
||||
transition.updateFrameAdditive(node: self.foregroundTitleNode, frame: titleFrame)
|
||||
|
||||
transition.updateFrameAdditive(node: self.dimBackgroundTextNode, frame: textFrame)
|
||||
transition.updateFrameAdditive(node: self.backgroundTextNode, frame: textFrame)
|
||||
transition.updateFrameAdditive(node: self.foregroundTextNode, frame: textFrame)
|
||||
|
||||
self.updateValue(transition: transition)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "settings_24 (1).pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
Binary file not shown.
@ -26,7 +26,7 @@ public final class HLSQualitySet {
|
||||
for attribute in alternativeFile.attributes {
|
||||
if case let .Video(_, size, _, _, _, videoCodec) = attribute {
|
||||
if let videoCodec, NativeVideoContent.isVideoCodecSupported(videoCodec: videoCodec) {
|
||||
let key = Int(size.height)
|
||||
let key = Int(min(size.width, size.height))
|
||||
if let currentFile = qualityFiles[key] {
|
||||
var currentCodec: String?
|
||||
for attribute in currentFile.media.attributes {
|
||||
|
@ -1127,7 +1127,9 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod
|
||||
|
||||
self.playerNode.frame = CGRect(origin: CGPoint(), size: self.intrinsicDimensions)
|
||||
|
||||
self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, userLocation: self.userLocation, videoReference: fileReference, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true) |> map { [weak self] getSize, getData in
|
||||
let thumbnailVideoReference = HLSVideoContent.minimizedHLSQuality(file: fileReference)?.file ?? fileReference
|
||||
|
||||
self.imageNode.setSignal(internalMediaGridMessageVideo(postbox: postbox, userLocation: self.userLocation, videoReference: thumbnailVideoReference, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true) |> map { [weak self] getSize, getData in
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self, strongSelf.dimensions == nil {
|
||||
if let dimensions = getSize() {
|
||||
@ -1557,7 +1559,7 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod
|
||||
case .auto:
|
||||
self.requestedLevelIndex = nil
|
||||
case let .quality(quality):
|
||||
if let level = self.playerAvailableLevels.first(where: { $0.value.height == quality }) {
|
||||
if let level = self.playerAvailableLevels.first(where: { min($0.value.width, $0.value.height) == quality }) {
|
||||
self.requestedLevelIndex = level.key
|
||||
} else {
|
||||
self.requestedLevelIndex = nil
|
||||
@ -1577,10 +1579,10 @@ final class HLSVideoJSNativeContentNode: ASDisplayNode, UniversalVideoContentNod
|
||||
return nil
|
||||
}
|
||||
|
||||
var available = self.playerAvailableLevels.values.map(\.height)
|
||||
var available = self.playerAvailableLevels.values.map { min($0.width, $0.height) }
|
||||
available.sort(by: { $0 > $1 })
|
||||
|
||||
return (currentLevel.height, self.preferredVideoQuality, available)
|
||||
return (min(currentLevel.width, currentLevel.height), self.preferredVideoQuality, available)
|
||||
}
|
||||
|
||||
func addPlaybackCompleted(_ f: @escaping () -> Void) -> Int {
|
||||
|
Loading…
x
Reference in New Issue
Block a user