mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
Merge commit 'a006cb06c6dd18db2ccba859035cbed5d18599be'
This commit is contained in:
commit
321fe4e052
@ -280,6 +280,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
self.scrubberView?.setCollapsed(alpha < 1.0, animated: animated)
|
self.scrubberView?.setCollapsed(alpha < 1.0, animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var hasExpandedCaptionPromise = ValuePromise<Bool>(false)
|
||||||
|
var hasExpandedCaption: Signal<Bool, NoError> {
|
||||||
|
return hasExpandedCaptionPromise.get()
|
||||||
|
}
|
||||||
|
|
||||||
init(context: AccountContext, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void = { _, _ in }) {
|
init(context: AccountContext, presentationData: PresentationData, present: @escaping (ViewController, Any?) -> Void = { _, _ in }) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
@ -716,6 +721,20 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
|
|||||||
self.requestLayout?(.immediate)
|
self.requestLayout?(.immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||||
|
self.hasExpandedCaptionPromise.set(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||||
|
if !decelerate {
|
||||||
|
self.hasExpandedCaptionPromise.set(scrollView.contentOffset.y > 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||||
|
self.hasExpandedCaptionPromise.set(scrollView.contentOffset.y > 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
override func updateLayout(size: CGSize, metrics: LayoutMetrics, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, contentInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||||
self.validLayout = (size, metrics, leftInset, rightInset, bottomInset, contentInset)
|
self.validLayout = (size, metrics, leftInset, rightInset, bottomInset, contentInset)
|
||||||
|
|
||||||
|
@ -58,8 +58,9 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||||||
var updateScrubbingHandlePosition: (CGFloat) -> Void = { _ in }
|
var updateScrubbingHandlePosition: (CGFloat) -> Void = { _ in }
|
||||||
var seek: (Double) -> Void = { _ in }
|
var seek: (Double) -> Void = { _ in }
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
init(chapters: [MediaPlayerScrubbingChapter]) {
|
||||||
self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 5.0, lineCap: .round, scrubberHandle: .circle, backgroundColor: scrubberBackgroundColor, foregroundColor: scrubberForegroundColor, bufferingColor: scrubberBufferingColor, chapters: self.chapters))
|
self.chapters = chapters
|
||||||
|
self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 5.0, lineCap: .round, scrubberHandle: .circle, backgroundColor: scrubberBackgroundColor, foregroundColor: scrubberForegroundColor, bufferingColor: scrubberBufferingColor, chapters: chapters))
|
||||||
|
|
||||||
self.leftTimestampNode = MediaPlayerTimeTextNode(textColor: .white)
|
self.leftTimestampNode = MediaPlayerTimeTextNode(textColor: .white)
|
||||||
self.rightTimestampNode = MediaPlayerTimeTextNode(textColor: .white)
|
self.rightTimestampNode = MediaPlayerTimeTextNode(textColor: .white)
|
||||||
@ -71,7 +72,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||||||
self.infoNode.isUserInteractionEnabled = false
|
self.infoNode.isUserInteractionEnabled = false
|
||||||
self.infoNode.displaysAsynchronously = false
|
self.infoNode.displaysAsynchronously = false
|
||||||
|
|
||||||
super.init(frame: frame)
|
super.init(frame: CGRect())
|
||||||
|
|
||||||
self.scrubberNode.seek = { [weak self] timestamp in
|
self.scrubberNode.seek = { [weak self] timestamp in
|
||||||
self?.seek(timestamp)
|
self?.seek(timestamp)
|
||||||
@ -167,7 +168,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||||||
self.leftTimestampNode.status = mappedStatus
|
self.leftTimestampNode.status = mappedStatus
|
||||||
self.rightTimestampNode.status = mappedStatus
|
self.rightTimestampNode.status = mappedStatus
|
||||||
|
|
||||||
if let mappedStatus = mappedStatus, false {
|
if let mappedStatus = mappedStatus {
|
||||||
self.chapterDisposable.set((mappedStatus
|
self.chapterDisposable.set((mappedStatus
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||||
if let strongSelf = self, status.duration > 1.0 {
|
if let strongSelf = self, status.duration > 1.0 {
|
||||||
|
@ -175,7 +175,7 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese
|
|||||||
if let result = addLocallyGeneratedEntities(text, enabledTypes: [.timecode], entities: entities, mediaDuration: file.duration.flatMap(Double.init)) {
|
if let result = addLocallyGeneratedEntities(text, enabledTypes: [.timecode], entities: entities, mediaDuration: file.duration.flatMap(Double.init)) {
|
||||||
entities = result
|
entities = result
|
||||||
}
|
}
|
||||||
|
|
||||||
let caption = galleryCaptionStringWithAppliedEntities(text, entities: entities)
|
let caption = galleryCaptionStringWithAppliedEntities(text, entities: entities)
|
||||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.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)
|
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.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 {
|
} else {
|
||||||
@ -218,7 +218,15 @@ public func galleryItemForEntry(context: AccountContext, presentationData: Prese
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let content = content {
|
if let content = content {
|
||||||
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.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)
|
var description: NSAttributedString?
|
||||||
|
if let descriptionText = webpageContent.text {
|
||||||
|
var entities: [MessageTextEntity] = []
|
||||||
|
if let result = addLocallyGeneratedEntities(descriptionText, enabledTypes: [.timecode], entities: entities, mediaDuration: 86400) {
|
||||||
|
entities = result
|
||||||
|
}
|
||||||
|
description = galleryCaptionStringWithAppliedEntities(descriptionText, entities: entities)
|
||||||
|
}
|
||||||
|
return UniversalVideoGalleryItem(context: context, presentationData: presentationData, content: content, originData: GalleryItemOriginData(title: message.effectiveAuthor.flatMap(EnginePeer.init)?.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: ""), description: description, displayInfoOnTop: displayInfoOnTop, fromPlayingVideo: fromPlayingVideo, isSecret: isSecret, landscape: landscape, timecode: timecode, playbackRate: playbackRate, configuration: configuration, performAction: performAction, openActionOptions: openActionOptions, storeMediaPlaybackState: storeMediaPlaybackState, present: present)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import UndoUI
|
|||||||
import TelegramUIPreferences
|
import TelegramUIPreferences
|
||||||
import OpenInExternalAppUI
|
import OpenInExternalAppUI
|
||||||
import AVKit
|
import AVKit
|
||||||
|
import TextFormat
|
||||||
|
|
||||||
public enum UniversalVideoGalleryItemContentInfo {
|
public enum UniversalVideoGalleryItemContentInfo {
|
||||||
case message(Message)
|
case message(Message)
|
||||||
@ -39,6 +40,7 @@ public class UniversalVideoGalleryItem: GalleryItem {
|
|||||||
let indexData: GalleryItemIndexData?
|
let indexData: GalleryItemIndexData?
|
||||||
let contentInfo: UniversalVideoGalleryItemContentInfo?
|
let contentInfo: UniversalVideoGalleryItemContentInfo?
|
||||||
let caption: NSAttributedString
|
let caption: NSAttributedString
|
||||||
|
let description: NSAttributedString?
|
||||||
let credit: NSAttributedString?
|
let credit: NSAttributedString?
|
||||||
let displayInfoOnTop: Bool
|
let displayInfoOnTop: Bool
|
||||||
let hideControls: Bool
|
let hideControls: Bool
|
||||||
@ -54,7 +56,7 @@ public class UniversalVideoGalleryItem: GalleryItem {
|
|||||||
let storeMediaPlaybackState: (MessageId, Double?, Double) -> Void
|
let storeMediaPlaybackState: (MessageId, Double?, Double) -> Void
|
||||||
let present: (ViewController, Any?) -> 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, playbackRate: @escaping () -> Double?, configuration: GalleryConfiguration? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?, Double) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
public init(context: AccountContext, presentationData: PresentationData, content: UniversalVideoContent, originData: GalleryItemOriginData?, indexData: GalleryItemIndexData?, contentInfo: UniversalVideoGalleryItemContentInfo?, caption: NSAttributedString, description: NSAttributedString? = nil, credit: NSAttributedString? = nil, displayInfoOnTop: Bool = false, hideControls: Bool = false, fromPlayingVideo: Bool = false, isSecret: Bool = false, landscape: Bool = false, timecode: Double? = nil, playbackRate: @escaping () -> Double?, configuration: GalleryConfiguration? = nil, playbackCompleted: @escaping () -> Void = {}, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, storeMediaPlaybackState: @escaping (MessageId, Double?, Double) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.content = content
|
self.content = content
|
||||||
@ -62,6 +64,7 @@ public class UniversalVideoGalleryItem: GalleryItem {
|
|||||||
self.indexData = indexData
|
self.indexData = indexData
|
||||||
self.contentInfo = contentInfo
|
self.contentInfo = contentInfo
|
||||||
self.caption = caption
|
self.caption = caption
|
||||||
|
self.description = description
|
||||||
self.credit = credit
|
self.credit = credit
|
||||||
self.displayInfoOnTop = displayInfoOnTop
|
self.displayInfoOnTop = displayInfoOnTop
|
||||||
self.hideControls = hideControls
|
self.hideControls = hideControls
|
||||||
@ -707,7 +710,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
fileprivate let _rightBarButtonItems = Promise<[UIBarButtonItem]?>()
|
fileprivate let _rightBarButtonItems = Promise<[UIBarButtonItem]?>()
|
||||||
|
|
||||||
fileprivate var titleContentView: GalleryTitleView?
|
fileprivate var titleContentView: GalleryTitleView?
|
||||||
private let scrubberView: ChatVideoGalleryItemScrubberView
|
private var scrubberView: ChatVideoGalleryItemScrubberView?
|
||||||
private let footerContentNode: ChatItemGalleryFooterContentNode
|
private let footerContentNode: ChatItemGalleryFooterContentNode
|
||||||
private let overlayContentNode: UniversalVideoGalleryItemOverlayNode
|
private let overlayContentNode: UniversalVideoGalleryItemOverlayNode
|
||||||
|
|
||||||
@ -762,7 +765,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
private let isInteractingPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let isInteractingPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
private let controlsVisiblePromise = ValuePromise<Bool>(true, ignoreRepeated: true)
|
private let controlsVisiblePromise = ValuePromise<Bool>(true, ignoreRepeated: true)
|
||||||
private let isShowingContextMenuPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let isShowingContextMenuPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||||
private let hasExpandedCaptionPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
private let hasExpandedCaptionPromise = Promise<Bool>()
|
||||||
private var hideControlsDisposable: Disposable?
|
private var hideControlsDisposable: Disposable?
|
||||||
|
|
||||||
var playbackCompleted: (() -> Void)?
|
var playbackCompleted: (() -> Void)?
|
||||||
@ -774,10 +777,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.presentationData = presentationData
|
self.presentationData = presentationData
|
||||||
self.scrubberView = ChatVideoGalleryItemScrubberView()
|
|
||||||
|
|
||||||
self.footerContentNode = ChatItemGalleryFooterContentNode(context: context, presentationData: presentationData, present: present)
|
self.footerContentNode = ChatItemGalleryFooterContentNode(context: context, presentationData: presentationData, present: present)
|
||||||
self.footerContentNode.scrubberView = self.scrubberView
|
self.hasExpandedCaptionPromise.set(self.footerContentNode.hasExpandedCaption)
|
||||||
|
|
||||||
self.footerContentNode.performAction = performAction
|
self.footerContentNode.performAction = performAction
|
||||||
self.footerContentNode.openActionOptions = openActionOptions
|
self.footerContentNode.openActionOptions = openActionOptions
|
||||||
|
|
||||||
@ -806,34 +810,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
self?.updateOrientation(toLandscape ? .landscapeRight : .portrait)
|
self?.updateOrientation(toLandscape ? .landscapeRight : .portrait)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.scrubberView.seek = { [weak self] timecode in
|
|
||||||
self?.videoNode?.seek(timecode)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.scrubberView.updateScrubbing = { [weak self] timecode in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.isInteractingPromise.set(timecode != nil)
|
|
||||||
|
|
||||||
if let videoFramePreview = strongSelf.videoFramePreview {
|
|
||||||
if let timecode = timecode {
|
|
||||||
if !strongSelf.scrubbingFrames {
|
|
||||||
strongSelf.scrubbingFrames = true
|
|
||||||
strongSelf.scrubbingFrame.set(videoFramePreview.generatedFrames
|
|
||||||
|> map(Optional.init))
|
|
||||||
}
|
|
||||||
videoFramePreview.generateFrame(at: timecode)
|
|
||||||
} else {
|
|
||||||
strongSelf.isInteractingPromise.set(false)
|
|
||||||
strongSelf.scrubbingFrame.set(.single(nil))
|
|
||||||
videoFramePreview.cancelPendingFrames()
|
|
||||||
strongSelf.scrubbingFrames = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.statusButtonNode.addSubnode(self.statusNode)
|
self.statusButtonNode.addSubnode(self.statusNode)
|
||||||
self.statusButtonNode.addTarget(self, action: #selector(self.statusButtonPressed), forControlEvents: .touchUpInside)
|
self.statusButtonNode.addTarget(self, action: #selector(self.statusButtonPressed), forControlEvents: .touchUpInside)
|
||||||
|
|
||||||
@ -1013,6 +989,68 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
|
|
||||||
func setupItem(_ item: UniversalVideoGalleryItem) {
|
func setupItem(_ item: UniversalVideoGalleryItem) {
|
||||||
if self.item?.content.id != item.content.id {
|
if self.item?.content.id != item.content.id {
|
||||||
|
func parseChapters(_ string: NSAttributedString) -> [MediaPlayerScrubbingChapter] {
|
||||||
|
var timecodeRanges: [(NSRange, TelegramTimecode)] = []
|
||||||
|
var lineRanges: [NSRange] = []
|
||||||
|
string.enumerateAttributes(in: NSMakeRange(0, string.length), options: [], using: { attributes, range, _ in
|
||||||
|
if let timecode = attributes[NSAttributedString.Key(TelegramTextAttributes.Timecode)] as? TelegramTimecode {
|
||||||
|
timecodeRanges.append((range, timecode))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
(string.string as NSString).enumerateSubstrings(in: NSMakeRange(0, string.length), options: .byLines, using: { _, range, _, _ in
|
||||||
|
lineRanges.append(range)
|
||||||
|
})
|
||||||
|
|
||||||
|
var chapters: [MediaPlayerScrubbingChapter] = []
|
||||||
|
for (timecodeRange, timecode) in timecodeRanges {
|
||||||
|
inner: for lineRange in lineRanges {
|
||||||
|
if lineRange.contains(timecodeRange.location) {
|
||||||
|
if lineRange.length > timecodeRange.length {
|
||||||
|
var title = ((string.string as NSString).substring(with: lineRange) as NSString).replacingCharacters(in: NSMakeRange(timecodeRange.location - lineRange.location, timecodeRange.length), with: "")
|
||||||
|
title = title.trimmingCharacters(in: .whitespacesAndNewlines).trimmingCharacters(in: .punctuationCharacters)
|
||||||
|
chapters.append(MediaPlayerScrubbingChapter(title: title, start: timecode.time))
|
||||||
|
}
|
||||||
|
break inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chapters
|
||||||
|
}
|
||||||
|
|
||||||
|
var chapters = parseChapters(item.caption)
|
||||||
|
if chapters.isEmpty, let description = item.description {
|
||||||
|
chapters = parseChapters(description)
|
||||||
|
}
|
||||||
|
let scrubberView = ChatVideoGalleryItemScrubberView(chapters: chapters)
|
||||||
|
self.scrubberView = scrubberView
|
||||||
|
scrubberView.seek = { [weak self] timecode in
|
||||||
|
self?.videoNode?.seek(timecode)
|
||||||
|
}
|
||||||
|
scrubberView.updateScrubbing = { [weak self] timecode in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.isInteractingPromise.set(timecode != nil)
|
||||||
|
|
||||||
|
if let videoFramePreview = strongSelf.videoFramePreview {
|
||||||
|
if let timecode = timecode {
|
||||||
|
if !strongSelf.scrubbingFrames {
|
||||||
|
strongSelf.scrubbingFrames = true
|
||||||
|
strongSelf.scrubbingFrame.set(videoFramePreview.generatedFrames
|
||||||
|
|> map(Optional.init))
|
||||||
|
}
|
||||||
|
videoFramePreview.generateFrame(at: timecode)
|
||||||
|
} else {
|
||||||
|
strongSelf.isInteractingPromise.set(false)
|
||||||
|
strongSelf.scrubbingFrame.set(.single(nil))
|
||||||
|
videoFramePreview.cancelPendingFrames()
|
||||||
|
strongSelf.scrubbingFrames = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.footerContentNode.scrubberView = scrubberView
|
||||||
|
|
||||||
self.isPlayingPromise.set(false)
|
self.isPlayingPromise.set(false)
|
||||||
|
|
||||||
if item.hideControls {
|
if item.hideControls {
|
||||||
@ -1114,7 +1152,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
self.updateDisplayPlaceholder(!videoNode.ownsContentNode)
|
self.updateDisplayPlaceholder(!videoNode.ownsContentNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.scrubberView.setStatusSignal(videoNode.status |> map { value -> MediaPlayerStatus in
|
scrubberView.setStatusSignal(videoNode.status |> map { value -> MediaPlayerStatus in
|
||||||
if let value = value, !value.duration.isZero {
|
if let value = value, !value.duration.isZero {
|
||||||
return value
|
return value
|
||||||
} else {
|
} else {
|
||||||
@ -1122,7 +1160,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
self.scrubberView.setBufferingStatusSignal(videoNode.bufferingStatus)
|
scrubberView.setBufferingStatusSignal(videoNode.bufferingStatus)
|
||||||
|
|
||||||
self.requiresDownload = true
|
self.requiresDownload = true
|
||||||
var mediaFileStatus: Signal<MediaResourceStatus?, NoError> = .single(nil)
|
var mediaFileStatus: Signal<MediaResourceStatus?, NoError> = .single(nil)
|
||||||
@ -1174,7 +1212,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
}
|
}
|
||||||
let status = messageMediaFileStatus(context: item.context, messageId: message.id, file: file)
|
let status = messageMediaFileStatus(context: item.context, messageId: message.id, file: file)
|
||||||
if !isWebpage {
|
if !isWebpage {
|
||||||
self.scrubberView.setFetchStatusSignal(status, strings: self.presentationData.strings, decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator, fileSize: file.size)
|
scrubberView.setFetchStatusSignal(status, strings: self.presentationData.strings, decimalSeparator: self.presentationData.dateTimeFormat.decimalSeparator, fileSize: file.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.requiresDownload = !isMediaStreamable(message: message, media: file)
|
self.requiresDownload = !isMediaStreamable(message: message, media: file)
|
||||||
@ -1578,7 +1616,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case let .timecode(timecode):
|
case let .timecode(timecode):
|
||||||
self.scrubberView.animateTo(timecode)
|
self.scrubberView?.animateTo(timecode)
|
||||||
videoNode.seek(timecode)
|
videoNode.seek(timecode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2586,7 +2624,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||||||
override func adjustForPreviewing() {
|
override func adjustForPreviewing() {
|
||||||
super.adjustForPreviewing()
|
super.adjustForPreviewing()
|
||||||
|
|
||||||
self.scrubberView.isHidden = true
|
self.scrubberView?.isHidden = true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> {
|
override func footerContent() -> Signal<(GalleryFooterContentNode?, GalleryOverlayContentNode?), NoError> {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#import <LegacyComponents/TGPhotoEditorButton.h>
|
#import <LegacyComponents/TGPhotoEditorButton.h>
|
||||||
|
|
||||||
|
#import <LegacyComponents/LegacyComponentsContext.h>
|
||||||
|
|
||||||
typedef NS_OPTIONS(NSUInteger, TGPhotoEditorTab) {
|
typedef NS_OPTIONS(NSUInteger, TGPhotoEditorTab) {
|
||||||
TGPhotoEditorNoneTab = 0,
|
TGPhotoEditorNoneTab = 0,
|
||||||
TGPhotoEditorCropTab = 1 << 0,
|
TGPhotoEditorCropTab = 1 << 0,
|
||||||
@ -50,7 +52,7 @@ typedef enum
|
|||||||
@property (nonatomic, assign) TGPhotoEditorBackButton backButtonType;
|
@property (nonatomic, assign) TGPhotoEditorBackButton backButtonType;
|
||||||
@property (nonatomic, assign) TGPhotoEditorDoneButton doneButtonType;
|
@property (nonatomic, assign) TGPhotoEditorDoneButton doneButtonType;
|
||||||
|
|
||||||
- (instancetype)initWithBackButton:(TGPhotoEditorBackButton)backButton doneButton:(TGPhotoEditorDoneButton)doneButton solidBackground:(bool)solidBackground;
|
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context backButton:(TGPhotoEditorBackButton)backButton doneButton:(TGPhotoEditorDoneButton)doneButton solidBackground:(bool)solidBackground;
|
||||||
|
|
||||||
- (void)transitionInAnimated:(bool)animated;
|
- (void)transitionInAnimated:(bool)animated;
|
||||||
- (void)transitionInAnimated:(bool)animated transparent:(bool)transparent;
|
- (void)transitionInAnimated:(bool)animated transparent:(bool)transparent;
|
||||||
|
@ -343,13 +343,13 @@
|
|||||||
_captionMixin.stickersContext = stickersContext;
|
_captionMixin.stickersContext = stickersContext;
|
||||||
[_captionMixin createInputPanelIfNeeded];
|
[_captionMixin createInputPanelIfNeeded];
|
||||||
|
|
||||||
_portraitToolbarView = [[TGPhotoToolbarView alloc] initWithBackButton:TGPhotoEditorBackButtonBack doneButton:TGPhotoEditorDoneButtonSend solidBackground:false];
|
_portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:TGPhotoEditorDoneButtonSend solidBackground:false];
|
||||||
_portraitToolbarView.cancelPressed = toolbarCancelPressed;
|
_portraitToolbarView.cancelPressed = toolbarCancelPressed;
|
||||||
_portraitToolbarView.donePressed = toolbarDonePressed;
|
_portraitToolbarView.donePressed = toolbarDonePressed;
|
||||||
_portraitToolbarView.doneLongPressed = toolbarDoneLongPressed;
|
_portraitToolbarView.doneLongPressed = toolbarDoneLongPressed;
|
||||||
[_wrapperView addSubview:_portraitToolbarView];
|
[_wrapperView addSubview:_portraitToolbarView];
|
||||||
|
|
||||||
_landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithBackButton:TGPhotoEditorBackButtonBack doneButton:TGPhotoEditorDoneButtonSend solidBackground:false];
|
_landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:TGPhotoEditorDoneButtonSend solidBackground:false];
|
||||||
_landscapeToolbarView.cancelPressed = toolbarCancelPressed;
|
_landscapeToolbarView.cancelPressed = toolbarCancelPressed;
|
||||||
_landscapeToolbarView.donePressed = toolbarDonePressed;
|
_landscapeToolbarView.donePressed = toolbarDonePressed;
|
||||||
_landscapeToolbarView.doneLongPressed = toolbarDoneLongPressed;
|
_landscapeToolbarView.doneLongPressed = toolbarDoneLongPressed;
|
||||||
|
@ -83,6 +83,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)buttonPressed {
|
- (void)buttonPressed {
|
||||||
|
_buttonView.enabled = false;
|
||||||
|
|
||||||
if (self.pressed != nil)
|
if (self.pressed != nil)
|
||||||
self.pressed();
|
self.pressed();
|
||||||
}
|
}
|
||||||
@ -310,6 +312,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)sendPressed {
|
- (void)sendPressed {
|
||||||
|
_sendButton.enabled = false;
|
||||||
|
|
||||||
[self animateOut:false];
|
[self animateOut:false];
|
||||||
|
|
||||||
if (self.send != nil)
|
if (self.send != nil)
|
||||||
|
@ -309,7 +309,7 @@
|
|||||||
|
|
||||||
TGPhotoEditorBackButton backButton = TGPhotoEditorBackButtonCancel;
|
TGPhotoEditorBackButton backButton = TGPhotoEditorBackButtonCancel;
|
||||||
TGPhotoEditorDoneButton doneButton = TGPhotoEditorDoneButtonCheck;
|
TGPhotoEditorDoneButton doneButton = TGPhotoEditorDoneButtonCheck;
|
||||||
_portraitToolbarView = [[TGPhotoToolbarView alloc] initWithBackButton:backButton doneButton:doneButton solidBackground:true];
|
_portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:backButton doneButton:doneButton solidBackground:true];
|
||||||
[_portraitToolbarView setToolbarTabs:_availableTabs animated:false];
|
[_portraitToolbarView setToolbarTabs:_availableTabs animated:false];
|
||||||
[_portraitToolbarView setActiveTab:_currentTab];
|
[_portraitToolbarView setActiveTab:_currentTab];
|
||||||
_portraitToolbarView.cancelPressed = toolbarCancelPressed;
|
_portraitToolbarView.cancelPressed = toolbarCancelPressed;
|
||||||
@ -318,7 +318,7 @@
|
|||||||
_portraitToolbarView.tabPressed = toolbarTabPressed;
|
_portraitToolbarView.tabPressed = toolbarTabPressed;
|
||||||
[_wrapperView addSubview:_portraitToolbarView];
|
[_wrapperView addSubview:_portraitToolbarView];
|
||||||
|
|
||||||
_landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithBackButton:backButton doneButton:doneButton solidBackground:true];
|
_landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:backButton doneButton:doneButton solidBackground:true];
|
||||||
[_landscapeToolbarView setToolbarTabs:_availableTabs animated:false];
|
[_landscapeToolbarView setToolbarTabs:_availableTabs animated:false];
|
||||||
[_landscapeToolbarView setActiveTab:_currentTab];
|
[_landscapeToolbarView setActiveTab:_currentTab];
|
||||||
_landscapeToolbarView.cancelPressed = toolbarCancelPressed;
|
_landscapeToolbarView.cancelPressed = toolbarCancelPressed;
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
@interface TGPhotoToolbarView ()
|
@interface TGPhotoToolbarView ()
|
||||||
{
|
{
|
||||||
|
id<LegacyComponentsContext> _context;
|
||||||
|
|
||||||
UIView *_backgroundView;
|
UIView *_backgroundView;
|
||||||
|
|
||||||
UIView *_buttonsWrapperView;
|
UIView *_buttonsWrapperView;
|
||||||
@ -28,11 +30,13 @@
|
|||||||
|
|
||||||
@implementation TGPhotoToolbarView
|
@implementation TGPhotoToolbarView
|
||||||
|
|
||||||
- (instancetype)initWithBackButton:(TGPhotoEditorBackButton)backButton doneButton:(TGPhotoEditorDoneButton)doneButton solidBackground:(bool)solidBackground
|
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context backButton:(TGPhotoEditorBackButton)backButton doneButton:(TGPhotoEditorDoneButton)doneButton solidBackground:(bool)solidBackground
|
||||||
{
|
{
|
||||||
self = [super initWithFrame:CGRectZero];
|
self = [super initWithFrame:CGRectZero];
|
||||||
if (self != nil)
|
if (self != nil)
|
||||||
{
|
{
|
||||||
|
_context = context;
|
||||||
|
|
||||||
_interfaceOrientation = [[LegacyComponentsGlobals provider] applicationStatusBarOrientation];
|
_interfaceOrientation = [[LegacyComponentsGlobals provider] applicationStatusBarOrientation];
|
||||||
|
|
||||||
_backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
|
_backgroundView = [[UIView alloc] initWithFrame:CGRectZero];
|
||||||
@ -85,6 +89,10 @@
|
|||||||
- (void)setDoneButtonType:(TGPhotoEditorDoneButton)doneButtonType {
|
- (void)setDoneButtonType:(TGPhotoEditorDoneButton)doneButtonType {
|
||||||
_doneButtonType = doneButtonType;
|
_doneButtonType = doneButtonType;
|
||||||
|
|
||||||
|
TGMediaAssetsPallete *pallete = nil;
|
||||||
|
if ([_context respondsToSelector:@selector(mediaAssetsPallete)])
|
||||||
|
pallete = [_context mediaAssetsPallete];
|
||||||
|
|
||||||
UIImage *doneImage;
|
UIImage *doneImage;
|
||||||
switch (doneButtonType)
|
switch (doneButtonType)
|
||||||
{
|
{
|
||||||
@ -94,19 +102,11 @@
|
|||||||
|
|
||||||
case TGPhotoEditorDoneButtonDone:
|
case TGPhotoEditorDoneButtonDone:
|
||||||
{
|
{
|
||||||
TGMediaAssetsPallete *pallete = nil;
|
|
||||||
if ([[LegacyComponentsGlobals provider] respondsToSelector:@selector(mediaAssetsPallete)])
|
|
||||||
pallete = [[LegacyComponentsGlobals provider] mediaAssetsPallete];
|
|
||||||
|
|
||||||
doneImage = pallete != nil ? pallete.doneIconImage : TGTintedImage([UIImage imageNamed:@"Editor/Commit"], [UIColor whiteColor]);
|
doneImage = pallete != nil ? pallete.doneIconImage : TGTintedImage([UIImage imageNamed:@"Editor/Commit"], [UIColor whiteColor]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
TGMediaAssetsPallete *pallete = nil;
|
|
||||||
if ([[LegacyComponentsGlobals provider] respondsToSelector:@selector(mediaAssetsPallete)])
|
|
||||||
pallete = [[LegacyComponentsGlobals provider] mediaAssetsPallete];
|
|
||||||
|
|
||||||
doneImage = pallete != nil ? pallete.sendIconImage : TGComponentsImageNamed(@"PhotoPickerSendIcon");
|
doneImage = pallete != nil ? pallete.sendIconImage : TGComponentsImageNamed(@"PhotoPickerSendIcon");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -161,6 +161,7 @@ public enum MediaPlayerScrubbingNodeContent {
|
|||||||
private final class StandardMediaPlayerScrubbingNodeContentNode {
|
private final class StandardMediaPlayerScrubbingNodeContentNode {
|
||||||
let lineHeight: CGFloat
|
let lineHeight: CGFloat
|
||||||
let lineCap: MediaPlayerScrubbingNodeCap
|
let lineCap: MediaPlayerScrubbingNodeCap
|
||||||
|
let containerNode: ASDisplayNode
|
||||||
let backgroundNode: ASImageNode
|
let backgroundNode: ASImageNode
|
||||||
let bufferingNode: MediaPlayerScrubbingBufferingNode
|
let bufferingNode: MediaPlayerScrubbingBufferingNode
|
||||||
let foregroundContentNode: ASImageNode
|
let foregroundContentNode: ASImageNode
|
||||||
@ -172,9 +173,10 @@ private final class StandardMediaPlayerScrubbingNodeContentNode {
|
|||||||
let highlightedHandleNode: ASDisplayNode?
|
let highlightedHandleNode: ASDisplayNode?
|
||||||
let handleNodeContainer: MediaPlayerScrubbingNodeButton?
|
let handleNodeContainer: MediaPlayerScrubbingNodeButton?
|
||||||
|
|
||||||
init(lineHeight: CGFloat, lineCap: MediaPlayerScrubbingNodeCap, backgroundNode: ASImageNode, bufferingNode: MediaPlayerScrubbingBufferingNode, foregroundContentNode: ASImageNode, foregroundNode: MediaPlayerScrubbingForegroundNode, chapterNodesContainer: ASDisplayNode?, chapterNodes: [(MediaPlayerScrubbingChapter, ASDisplayNode)], handle: MediaPlayerScrubbingNodeHandle, handleNode: ASDisplayNode?, highlightedHandleNode: ASDisplayNode?, handleNodeContainer: MediaPlayerScrubbingNodeButton?) {
|
init(lineHeight: CGFloat, lineCap: MediaPlayerScrubbingNodeCap, containerNode: ASDisplayNode, backgroundNode: ASImageNode, bufferingNode: MediaPlayerScrubbingBufferingNode, foregroundContentNode: ASImageNode, foregroundNode: MediaPlayerScrubbingForegroundNode, chapterNodesContainer: ASDisplayNode?, chapterNodes: [(MediaPlayerScrubbingChapter, ASDisplayNode)], handle: MediaPlayerScrubbingNodeHandle, handleNode: ASDisplayNode?, highlightedHandleNode: ASDisplayNode?, handleNodeContainer: MediaPlayerScrubbingNodeButton?) {
|
||||||
self.lineHeight = lineHeight
|
self.lineHeight = lineHeight
|
||||||
self.lineCap = lineCap
|
self.lineCap = lineCap
|
||||||
|
self.containerNode = containerNode
|
||||||
self.backgroundNode = backgroundNode
|
self.backgroundNode = backgroundNode
|
||||||
self.bufferingNode = bufferingNode
|
self.bufferingNode = bufferingNode
|
||||||
self.foregroundContentNode = foregroundContentNode
|
self.foregroundContentNode = foregroundContentNode
|
||||||
@ -438,19 +440,21 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||||||
chapterNodesContainer.isUserInteractionEnabled = false
|
chapterNodesContainer.isUserInteractionEnabled = false
|
||||||
chapterNodesContainerImpl = chapterNodesContainer
|
chapterNodesContainerImpl = chapterNodesContainer
|
||||||
|
|
||||||
|
var chapters = chapters
|
||||||
|
if let firstChapter = chapters.first, firstChapter.start > 0.0 {
|
||||||
|
chapters.insert(MediaPlayerScrubbingChapter(title: "", start: 0.0), at: 0)
|
||||||
|
}
|
||||||
|
|
||||||
for i in 0 ..< chapters.count {
|
for i in 0 ..< chapters.count {
|
||||||
let chapterNode = ASDisplayNode()
|
let chapterNode = ASDisplayNode()
|
||||||
chapterNode.backgroundColor = .black
|
chapterNode.backgroundColor = .white
|
||||||
|
chapterNodesContainer.addSubnode(chapterNode)
|
||||||
|
|
||||||
if i > 0 {
|
|
||||||
chapterNodesContainer.addSubnode(chapterNode)
|
|
||||||
}
|
|
||||||
chapterNodes.append((chapters[i], chapterNode))
|
chapterNodes.append((chapters[i], chapterNode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return .standard(StandardMediaPlayerScrubbingNodeContentNode(lineHeight: lineHeight, lineCap: lineCap, containerNode: ASDisplayNode(), backgroundNode: backgroundNode, bufferingNode: bufferingNode, foregroundContentNode: foregroundContentNode, foregroundNode: foregroundNode, chapterNodesContainer: chapterNodesContainerImpl, chapterNodes: chapterNodes, handle: scrubberHandle, handleNode: handleNodeImpl, highlightedHandleNode: highlightedHandleNodeImpl, handleNodeContainer: handleNodeContainerImpl))
|
||||||
return .standard(StandardMediaPlayerScrubbingNodeContentNode(lineHeight: lineHeight, lineCap: lineCap, backgroundNode: backgroundNode, bufferingNode: bufferingNode, foregroundContentNode: foregroundContentNode, foregroundNode: foregroundNode, chapterNodesContainer: chapterNodesContainerImpl, chapterNodes: chapterNodes, handle: scrubberHandle, handleNode: handleNodeImpl, highlightedHandleNode: highlightedHandleNodeImpl, handleNodeContainer: handleNodeContainerImpl))
|
|
||||||
case let .custom(backgroundNode, foregroundContentNode):
|
case let .custom(backgroundNode, foregroundContentNode):
|
||||||
let foregroundNode = MediaPlayerScrubbingForegroundNode()
|
let foregroundNode = MediaPlayerScrubbingForegroundNode()
|
||||||
foregroundNode.isLayerBacked = true
|
foregroundNode.isLayerBacked = true
|
||||||
@ -507,14 +511,12 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||||||
|
|
||||||
switch self.contentNodes {
|
switch self.contentNodes {
|
||||||
case let .standard(node):
|
case let .standard(node):
|
||||||
self.addSubnode(node.backgroundNode)
|
self.addSubnode(node.containerNode)
|
||||||
self.addSubnode(node.bufferingNode)
|
|
||||||
node.foregroundNode.addSubnode(node.foregroundContentNode)
|
|
||||||
self.addSubnode(node.foregroundNode)
|
|
||||||
|
|
||||||
if let chapterNodesContainer = node.chapterNodesContainer {
|
node.containerNode.addSubnode(node.backgroundNode)
|
||||||
self.addSubnode(chapterNodesContainer)
|
node.containerNode.addSubnode(node.bufferingNode)
|
||||||
}
|
node.foregroundNode.addSubnode(node.foregroundContentNode)
|
||||||
|
node.containerNode.addSubnode(node.foregroundNode)
|
||||||
|
|
||||||
if let handleNodeContainer = node.handleNodeContainer {
|
if let handleNodeContainer = node.handleNodeContainer {
|
||||||
self.addSubnode(handleNodeContainer)
|
self.addSubnode(handleNodeContainer)
|
||||||
@ -789,7 +791,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||||||
let bounds = self.bounds
|
let bounds = self.bounds
|
||||||
|
|
||||||
var isPlaying = false
|
var isPlaying = false
|
||||||
var timestampAndDuration: (timestamp: Double, duration: Double)?
|
var timestampAndDuration: (timestamp: Double?, duration: Double)?
|
||||||
if let statusValue = self.statusValue {
|
if let statusValue = self.statusValue {
|
||||||
switch statusValue.status {
|
switch statusValue.status {
|
||||||
case .playing:
|
case .playing:
|
||||||
@ -798,6 +800,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if case .buffering(true, _, _, _) = statusValue.status {
|
if case .buffering(true, _, _, _) = statusValue.status {
|
||||||
|
timestampAndDuration = (nil, statusValue.duration)
|
||||||
//initialBuffering = true
|
//initialBuffering = true
|
||||||
} else if Double(0.0).isLess(than: statusValue.duration) {
|
} else if Double(0.0).isLess(than: statusValue.duration) {
|
||||||
if let scrubbingTimestampValue = self.scrubbingTimestampValue {
|
if let scrubbingTimestampValue = self.scrubbingTimestampValue {
|
||||||
@ -819,6 +822,8 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||||||
|
|
||||||
switch self.contentNodes {
|
switch self.contentNodes {
|
||||||
case let .standard(node):
|
case let .standard(node):
|
||||||
|
node.containerNode.frame = CGRect(origin: CGPoint(), size: bounds.size)
|
||||||
|
|
||||||
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((bounds.size.height - node.lineHeight) / 2.0)), size: CGSize(width: bounds.size.width, height: node.lineHeight))
|
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((bounds.size.height - node.lineHeight) / 2.0)), size: CGSize(width: bounds.size.width, height: node.lineHeight))
|
||||||
node.backgroundNode.position = backgroundFrame.center
|
node.backgroundNode.position = backgroundFrame.center
|
||||||
node.backgroundNode.bounds = CGRect(origin: CGPoint(), size: backgroundFrame.size)
|
node.backgroundNode.bounds = CGRect(origin: CGPoint(), size: backgroundFrame.size)
|
||||||
@ -827,24 +832,50 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||||||
node.foregroundContentNode.position = foregroundContentFrame.center
|
node.foregroundContentNode.position = foregroundContentFrame.center
|
||||||
node.foregroundContentNode.bounds = CGRect(origin: CGPoint(), size: foregroundContentFrame.size)
|
node.foregroundContentNode.bounds = CGRect(origin: CGPoint(), size: foregroundContentFrame.size)
|
||||||
|
|
||||||
|
|
||||||
node.bufferingNode.frame = backgroundFrame
|
node.bufferingNode.frame = backgroundFrame
|
||||||
node.bufferingNode.updateLayout(size: backgroundFrame.size, transition: .immediate)
|
node.bufferingNode.updateLayout(size: backgroundFrame.size, transition: .immediate)
|
||||||
|
|
||||||
if let chapterNodesContainer = node.chapterNodesContainer, let duration = timestampAndDuration?.duration, duration > 0.0, backgroundFrame.width > 0.0 {
|
if let chapterNodesContainer = node.chapterNodesContainer {
|
||||||
chapterNodesContainer.frame = backgroundFrame
|
if let duration = timestampAndDuration?.duration, duration > 1.0, backgroundFrame.width > 0.0, node.chapterNodes.count > 1 {
|
||||||
|
if node.containerNode.view.mask == nil {
|
||||||
for i in 0 ..< node.chapterNodes.count {
|
node.containerNode.view.mask = chapterNodesContainer.view
|
||||||
let (chapter, chapterNode) = node.chapterNodes[i]
|
|
||||||
if i == 0 || chapter.start > duration {
|
let transitionView = UIView()
|
||||||
continue
|
transitionView.backgroundColor = .white
|
||||||
|
transitionView.frame = node.containerNode.bounds
|
||||||
|
chapterNodesContainer.view.addSubview(transitionView)
|
||||||
|
transitionView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak transitionView] _ in
|
||||||
|
transitionView?.removeFromSuperview()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
let chapterPosition: CGFloat = floor(backgroundFrame.width * CGFloat(chapter.start / duration))
|
|
||||||
let chapterLineWidth: CGFloat = 1.5
|
chapterNodesContainer.frame = backgroundFrame
|
||||||
chapterNode.frame = CGRect(x: chapterPosition - chapterLineWidth / 2.0, y: 0.0, width: chapterLineWidth, height: backgroundFrame.size.height)
|
|
||||||
|
for i in 1 ..< node.chapterNodes.count {
|
||||||
|
let (previousChapter, previousChapterNode) = node.chapterNodes[i - 1]
|
||||||
|
let (chapter, chapterNode) = node.chapterNodes[i]
|
||||||
|
|
||||||
|
let lineWidth: CGFloat = 1.0 + UIScreenPixel * 2.0
|
||||||
|
let startPosition: CGFloat
|
||||||
|
if i == 1 {
|
||||||
|
startPosition = 0.0
|
||||||
|
} else {
|
||||||
|
startPosition = floor(backgroundFrame.width * CGFloat(previousChapter.start / duration)) + lineWidth / 2.0
|
||||||
|
}
|
||||||
|
let endPosition: CGFloat = max(startPosition, floor(backgroundFrame.width * CGFloat(chapter.start / duration)) - lineWidth / 2.0)
|
||||||
|
previousChapterNode.frame = CGRect(x: startPosition, y: 0.0, width: endPosition - startPosition, height: backgroundFrame.size.height)
|
||||||
|
|
||||||
|
if i == node.chapterNodes.count - 1 {
|
||||||
|
let startPosition = endPosition + lineWidth
|
||||||
|
chapterNode.frame = CGRect(x: startPosition, y: 0.0, width: backgroundFrame.size.width - startPosition, height: backgroundFrame.size.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
node.containerNode.view.mask = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if let handleNode = node.handleNode {
|
if let handleNode = node.handleNode {
|
||||||
var handleSize: CGSize = CGSize(width: 2.0, height: bounds.size.height)
|
var handleSize: CGSize = CGSize(width: 2.0, height: bounds.size.height)
|
||||||
var handleOffset: CGFloat = 0.0
|
var handleOffset: CGFloat = 0.0
|
||||||
@ -863,7 +894,7 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||||||
handleNodeContainer.frame = bounds
|
handleNodeContainer.frame = bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
if let (timestamp, duration) = timestampAndDuration {
|
if let (maybeTimestamp, duration) = timestampAndDuration, let timestamp = maybeTimestamp, duration > 0.01 {
|
||||||
if let scrubbingTimestampValue = self.scrubbingTimestampValue {
|
if let scrubbingTimestampValue = self.scrubbingTimestampValue {
|
||||||
var progress = CGFloat(scrubbingTimestampValue / duration)
|
var progress = CGFloat(scrubbingTimestampValue / duration)
|
||||||
if progress.isNaN || !progress.isFinite {
|
if progress.isNaN || !progress.isFinite {
|
||||||
|
@ -13,8 +13,8 @@ import AnimatedStickerNode
|
|||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
import PresentationDataUtils
|
import PresentationDataUtils
|
||||||
|
|
||||||
private func shareQrCode(context: AccountContext, link: String, view: UIView) {
|
private func shareQrCode(context: AccountContext, link: String, ecl: String, view: UIView) {
|
||||||
let _ = (qrCode(string: link, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Chat/Links/QrLogo")))
|
let _ = (qrCode(string: link, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Chat/Links/QrLogo")), ecl: ecl)
|
||||||
|> map { _, generator -> UIImage? in
|
|> map { _, generator -> UIImage? in
|
||||||
let imageSize = CGSize(width: 768.0, height: 768.0)
|
let imageSize = CGSize(width: 768.0, height: 768.0)
|
||||||
let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0))
|
let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0))
|
||||||
@ -47,6 +47,15 @@ public final class QrCodeScreen: ViewController {
|
|||||||
return invite.link
|
return invite.link
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ecl: String {
|
||||||
|
switch self {
|
||||||
|
case .peer:
|
||||||
|
return "Q"
|
||||||
|
case .invite:
|
||||||
|
return "Q"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var controllerNode: Node {
|
private var controllerNode: Node {
|
||||||
@ -293,11 +302,11 @@ public final class QrCodeScreen: ViewController {
|
|||||||
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
|
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
|
||||||
self.buttonNode.pressed = { [weak self] in
|
self.buttonNode.pressed = { [weak self] in
|
||||||
if let strongSelf = self{
|
if let strongSelf = self{
|
||||||
shareQrCode(context: strongSelf.context, link: subject.link, view: strongSelf.view)
|
shareQrCode(context: strongSelf.context, link: subject.link, ecl: subject.ecl, view: strongSelf.view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.qrImageNode.setSignal(qrCode(string: subject.link, color: .black, backgroundColor: .white, icon: .cutout) |> beforeNext { [weak self] size, _ in
|
self.qrImageNode.setSignal(qrCode(string: subject.link, color: .black, backgroundColor: .white, icon: .cutout, ecl: subject.ecl) |> beforeNext { [weak self] size, _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
21
submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Contents.json
vendored
Normal file
21
submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Snowflake.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Snowflake.png
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Components/Snowflake.imageset/Snowflake.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
@ -309,7 +309,7 @@ public func addLocallyGeneratedEntities(_ text: String, enabledTypes: EnabledEnt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasDigits {
|
if hasDigits || hasColons {
|
||||||
if let phoneNumberDetector = phoneNumberDetector, detectPhoneNumbers {
|
if let phoneNumberDetector = phoneNumberDetector, detectPhoneNumbers {
|
||||||
let utf16 = text.utf16
|
let utf16 = text.utf16
|
||||||
phoneNumberDetector.enumerateMatches(in: text, options: [], range: NSMakeRange(0, utf16.count), using: { result, _, _ in
|
phoneNumberDetector.enumerateMatches(in: text, options: [], range: NSMakeRange(0, utf16.count), using: { result, _, _ in
|
||||||
|
@ -431,6 +431,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
return self._isReady.get()
|
return self._isReady.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var newYearNode: WallpaperNewYearNode?
|
||||||
|
|
||||||
init(context: AccountContext, useSharedAnimationPhase: Bool) {
|
init(context: AccountContext, useSharedAnimationPhase: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.useSharedAnimationPhase = useSharedAnimationPhase
|
self.useSharedAnimationPhase = useSharedAnimationPhase
|
||||||
@ -450,7 +452,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
self.contentNode.frame = self.bounds
|
self.contentNode.frame = self.bounds
|
||||||
self.addSubnode(self.contentNode)
|
self.addSubnode(self.contentNode)
|
||||||
self.addSubnode(self.patternImageNode)
|
self.addSubnode(self.patternImageNode)
|
||||||
|
|
||||||
//self.view.addSubview(self.bakedBackgroundView)
|
//self.view.addSubview(self.bakedBackgroundView)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -793,10 +795,19 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.loadPatternForSizeIfNeeded(size: size, transition: transition)
|
self.loadPatternForSizeIfNeeded(size: size, transition: transition)
|
||||||
|
|
||||||
if isFirstLayout && !self.frame.isEmpty {
|
if isFirstLayout && !self.frame.isEmpty {
|
||||||
self.updateScale()
|
self.updateScale()
|
||||||
|
|
||||||
|
if false, self.newYearNode == nil {
|
||||||
|
let newYearNode = WallpaperNewYearNode()
|
||||||
|
self.addSubnode(newYearNode)
|
||||||
|
self.newYearNode = newYearNode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.newYearNode?.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
self.newYearNode?.updateLayout(size: size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) {
|
func animateEvent(transition: ContainedViewLayoutTransition, extendAnimation: Bool) {
|
||||||
@ -1695,3 +1706,47 @@ public func createWallpaperBackgroundNode(context: AccountContext, forChatDispla
|
|||||||
|
|
||||||
return WallpaperBackgroundNodeImpl(context: context, useSharedAnimationPhase: useSharedAnimationPhase)
|
return WallpaperBackgroundNodeImpl(context: context, useSharedAnimationPhase: useSharedAnimationPhase)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class WallpaperNewYearNode: ASDisplayNode {
|
||||||
|
private var emitterLayer: CAEmitterLayer?
|
||||||
|
|
||||||
|
func updateLayout(size: CGSize) {
|
||||||
|
if self.emitterLayer == nil {
|
||||||
|
let particlesLayer = CAEmitterLayer()
|
||||||
|
self.emitterLayer = particlesLayer
|
||||||
|
|
||||||
|
self.layer.addSublayer(particlesLayer)
|
||||||
|
self.layer.masksToBounds = true
|
||||||
|
|
||||||
|
particlesLayer.backgroundColor = UIColor.clear.cgColor
|
||||||
|
particlesLayer.emitterShape = .circle
|
||||||
|
particlesLayer.emitterMode = .surface
|
||||||
|
particlesLayer.renderMode = .oldestLast
|
||||||
|
|
||||||
|
let cell1 = CAEmitterCell()
|
||||||
|
cell1.contents = UIImage(bundleImageName: "Components/Snowflake")?.cgImage
|
||||||
|
cell1.name = "snow"
|
||||||
|
cell1.birthRate = 352.0
|
||||||
|
cell1.lifetime = 20.0
|
||||||
|
cell1.velocity = 39.0
|
||||||
|
cell1.velocityRange = -15.0
|
||||||
|
cell1.xAcceleration = 5.0
|
||||||
|
cell1.yAcceleration = 25.0
|
||||||
|
cell1.emissionRange = .pi
|
||||||
|
cell1.spin = -28.6 * (.pi / 180.0)
|
||||||
|
cell1.spinRange = 57.2 * (.pi / 180.0)
|
||||||
|
cell1.scale = 0.04
|
||||||
|
cell1.scaleRange = 0.15
|
||||||
|
cell1.color = UIColor.white.withAlphaComponent(0.88).cgColor
|
||||||
|
cell1.alphaRange = -0.2
|
||||||
|
|
||||||
|
particlesLayer.emitterCells = [cell1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if let emitterLayer = self.emitterLayer {
|
||||||
|
emitterLayer.emitterPosition = CGPoint(x: size.width / 2.0, y: -size.height / 2.0)
|
||||||
|
emitterLayer.emitterSize = CGSize(width: size.width * 2.5, height: size.height)
|
||||||
|
emitterLayer.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user