mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Video improvements
This commit is contained in:
parent
846e495d12
commit
3728be84cb
@ -1615,7 +1615,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
||||
preferredAction = .saveToCameraRoll
|
||||
actionCompletionText = strongSelf.presentationData.strings.Gallery_ImageSaved
|
||||
case .video:
|
||||
preferredAction = .saveToCameraRoll
|
||||
if let message = messages.first, let channel = message.peers[message.id.peerId] as? TelegramChannel, channel.addressName != nil {
|
||||
} else {
|
||||
preferredAction = .saveToCameraRoll
|
||||
}
|
||||
actionCompletionText = strongSelf.presentationData.strings.Gallery_VideoSaved
|
||||
case .file:
|
||||
preferredAction = .saveToCameraRoll
|
||||
|
@ -382,17 +382,17 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
||||
}
|
||||
|
||||
func animateIn(from scrubberTransition: GalleryItemScrubberTransition?, transition: ContainedViewLayoutTransition) {
|
||||
if let scrubberTransition {
|
||||
if let scrubberTransition = scrubberTransition?.scrubber {
|
||||
let fromRect = scrubberTransition.view.convert(scrubberTransition.view.bounds, to: self)
|
||||
|
||||
let targetCloneView = scrubberTransition.makeView()
|
||||
self.addSubview(targetCloneView)
|
||||
targetCloneView.frame = fromRect
|
||||
scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.TransitionState(sourceSize: fromRect.size, destinationSize: CGSize(width: self.scrubberNode.bounds.width, height: fromRect.height), progress: 0.0), .immediate)
|
||||
scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.Scrubber.TransitionState(sourceSize: fromRect.size, destinationSize: CGSize(width: self.scrubberNode.bounds.width, height: fromRect.height), progress: 0.0), .immediate)
|
||||
targetCloneView.alpha = 1.0
|
||||
|
||||
transition.updateFrame(view: targetCloneView, frame: CGRect(origin: CGPoint(x: self.scrubberNode.frame.minX, y: self.scrubberNode.frame.maxY - fromRect.height - 3.0), size: CGSize(width: self.scrubberNode.bounds.width, height: fromRect.height)))
|
||||
scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.TransitionState(sourceSize: fromRect.size, destinationSize: CGSize(width: self.scrubberNode.bounds.width, height: fromRect.height), progress: 1.0), transition)
|
||||
scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.Scrubber.TransitionState(sourceSize: fromRect.size, destinationSize: CGSize(width: self.scrubberNode.bounds.width, height: fromRect.height), progress: 1.0), transition)
|
||||
let scrubberTransitionView = scrubberTransition.view
|
||||
scrubberTransitionView.isHidden = true
|
||||
ContainedViewLayoutTransition.animated(duration: 0.08, curve: .easeInOut).updateAlpha(layer: targetCloneView.layer, alpha: 0.0, completion: { [weak scrubberTransitionView, weak targetCloneView] _ in
|
||||
@ -421,18 +421,18 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
||||
func animateOut(to scrubberTransition: GalleryItemScrubberTransition?, transition: ContainedViewLayoutTransition) {
|
||||
self.isAnimatedOut = true
|
||||
|
||||
if let scrubberTransition {
|
||||
if let scrubberTransition = scrubberTransition?.scrubber {
|
||||
let toRect = scrubberTransition.view.convert(scrubberTransition.view.bounds, to: self)
|
||||
let scrubberDestinationRect = CGRect(origin: CGPoint(x: toRect.minX, y: toRect.maxY - 3.0), size: CGSize(width: toRect.width, height: 3.0))
|
||||
|
||||
let targetCloneView = scrubberTransition.makeView()
|
||||
self.addSubview(targetCloneView)
|
||||
targetCloneView.frame = CGRect(origin: CGPoint(x: self.scrubberNode.frame.minX, y: self.scrubberNode.frame.maxY - toRect.height), size: CGSize(width: self.scrubberNode.bounds.width, height: toRect.height))
|
||||
scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.TransitionState(sourceSize: CGSize(width: self.scrubberNode.bounds.width, height: toRect.height), destinationSize: toRect.size, progress: 0.0), .immediate)
|
||||
scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.Scrubber.TransitionState(sourceSize: CGSize(width: self.scrubberNode.bounds.width, height: toRect.height), destinationSize: toRect.size, progress: 0.0), .immediate)
|
||||
targetCloneView.alpha = 0.0
|
||||
|
||||
transition.updateFrame(view: targetCloneView, frame: toRect)
|
||||
scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.TransitionState(sourceSize: CGSize(width: self.scrubberNode.bounds.width, height: toRect.height), destinationSize: toRect.size, progress: 1.0), transition)
|
||||
scrubberTransition.updateView(targetCloneView, GalleryItemScrubberTransition.Scrubber.TransitionState(sourceSize: CGSize(width: self.scrubberNode.bounds.width, height: toRect.height), destinationSize: toRect.size, progress: 1.0), transition)
|
||||
let scrubberTransitionView = scrubberTransition.view
|
||||
scrubberTransitionView.isHidden = true
|
||||
transition.updateAlpha(layer: targetCloneView.layer, alpha: 1.0, completion: { [weak scrubberTransitionView] _ in
|
||||
|
@ -4,32 +4,73 @@ import AccountContext
|
||||
import Display
|
||||
|
||||
public final class GalleryItemScrubberTransition {
|
||||
public struct TransitionState: Equatable {
|
||||
public var sourceSize: CGSize
|
||||
public var destinationSize: CGSize
|
||||
public var progress: CGFloat
|
||||
public final class Scrubber {
|
||||
public struct TransitionState: Equatable {
|
||||
public var sourceSize: CGSize
|
||||
public var destinationSize: CGSize
|
||||
public var progress: CGFloat
|
||||
|
||||
public init(
|
||||
sourceSize: CGSize,
|
||||
destinationSize: CGSize,
|
||||
progress: CGFloat
|
||||
) {
|
||||
self.sourceSize = sourceSize
|
||||
self.destinationSize = destinationSize
|
||||
self.progress = progress
|
||||
}
|
||||
}
|
||||
|
||||
public init(
|
||||
sourceSize: CGSize,
|
||||
destinationSize: CGSize,
|
||||
progress: CGFloat
|
||||
) {
|
||||
self.sourceSize = sourceSize
|
||||
self.destinationSize = destinationSize
|
||||
self.progress = progress
|
||||
public let view: UIView
|
||||
public let makeView: () -> UIView
|
||||
public let updateView: (UIView, TransitionState, ContainedViewLayoutTransition) -> Void
|
||||
|
||||
public init(view: UIView, makeView: @escaping () -> UIView, updateView: @escaping (UIView, TransitionState, ContainedViewLayoutTransition) -> Void) {
|
||||
self.view = view
|
||||
self.makeView = makeView
|
||||
self.updateView = updateView
|
||||
}
|
||||
}
|
||||
|
||||
public let view: UIView
|
||||
public let makeView: () -> UIView
|
||||
public let updateView: (UIView, TransitionState, ContainedViewLayoutTransition) -> Void
|
||||
public let insertCloneTransitionView: ((UIView) -> Void)?
|
||||
public final class Content {
|
||||
public struct TransitionState: Equatable {
|
||||
public var sourceSize: CGSize
|
||||
public var destinationSize: CGSize
|
||||
public var destinationCornerRadius: CGFloat
|
||||
public var progress: CGFloat
|
||||
|
||||
public init(
|
||||
sourceSize: CGSize,
|
||||
destinationSize: CGSize,
|
||||
destinationCornerRadius: CGFloat,
|
||||
progress: CGFloat
|
||||
) {
|
||||
self.sourceSize = sourceSize
|
||||
self.destinationSize = destinationSize
|
||||
self.destinationCornerRadius = destinationCornerRadius
|
||||
self.progress = progress
|
||||
}
|
||||
}
|
||||
|
||||
public let sourceView: UIView
|
||||
public let sourceRect: CGRect
|
||||
public let makeView: () -> UIView
|
||||
public let updateView: (UIView, TransitionState, ContainedViewLayoutTransition) -> Void
|
||||
|
||||
public init(sourceView: UIView, sourceRect: CGRect, makeView: @escaping () -> UIView, updateView: @escaping (UIView, TransitionState, ContainedViewLayoutTransition) -> Void) {
|
||||
self.sourceView = sourceView
|
||||
self.sourceRect = sourceRect
|
||||
self.makeView = makeView
|
||||
self.updateView = updateView
|
||||
}
|
||||
}
|
||||
|
||||
public init(view: UIView, makeView: @escaping () -> UIView, updateView: @escaping (UIView, TransitionState, ContainedViewLayoutTransition) -> Void, insertCloneTransitionView: ((UIView) -> Void)?) {
|
||||
self.view = view
|
||||
self.makeView = makeView
|
||||
self.updateView = updateView
|
||||
self.insertCloneTransitionView = insertCloneTransitionView
|
||||
public let scrubber: Scrubber?
|
||||
public let content: Content?
|
||||
|
||||
public init(scrubber: Scrubber?, content: Content?) {
|
||||
self.scrubber = scrubber
|
||||
self.content = content
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1374,6 +1374,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private var activeEdgeRateState: (initialRate: Double, currentRate: Double)?
|
||||
private var activeEdgeRateIndicator: ComponentView<Empty>?
|
||||
|
||||
private var isAnimatingOut: Bool = false
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
@ -1568,7 +1570,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
self.hideControlsDisposable = (shouldHideControlsSignal
|
||||
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
if let strongSelf = self, !strongSelf.isAnimatingOut {
|
||||
strongSelf.updateControlsVisibility(false)
|
||||
}
|
||||
}).strict()
|
||||
@ -1882,17 +1884,17 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
if let status = status {
|
||||
let shouldStorePlaybacksState: Bool
|
||||
#if DEBUG && false
|
||||
shouldStorePlaybacksState = status.duration >= 10.0
|
||||
#else
|
||||
shouldStorePlaybacksState = status.duration >= 60.0 * 10.0
|
||||
#endif
|
||||
shouldStorePlaybacksState = status.duration >= 20.0
|
||||
|
||||
var timestamp: Double?
|
||||
if status.timestamp > 5.0 && status.timestamp < status.duration - 5.0 {
|
||||
timestamp = status.timestamp
|
||||
if shouldStorePlaybacksState {
|
||||
var timestamp: Double?
|
||||
if status.timestamp > 5.0 && status.timestamp < status.duration - 5.0 {
|
||||
timestamp = status.timestamp
|
||||
}
|
||||
item.storeMediaPlaybackState(message.id, timestamp, status.baseRate)
|
||||
} else {
|
||||
item.storeMediaPlaybackState(message.id, nil, status.baseRate)
|
||||
}
|
||||
item.storeMediaPlaybackState(message.id, timestamp, status.baseRate)
|
||||
}
|
||||
}))
|
||||
}
|
||||
@ -2401,6 +2403,14 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
var isAnimated = false
|
||||
var seek = MediaPlayerSeek.start
|
||||
if let item = self.item {
|
||||
if let contentInfo = item.contentInfo, case let .message(message, _) = contentInfo {
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ForwardVideoTimestampAttribute {
|
||||
seek = .timecode(Double(attribute.timestamp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let content = item.content as? NativeVideoContent {
|
||||
isAnimated = content.fileReference.media.isAnimated
|
||||
if let time = item.timecode {
|
||||
@ -2416,14 +2426,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
seek = .timecode(time)
|
||||
}
|
||||
}
|
||||
|
||||
if let contentInfo = item.contentInfo, case let .message(message, _) = contentInfo {
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ForwardVideoTimestampAttribute {
|
||||
seek = .timecode(Double(attribute.timestamp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
videoNode.setBaseRate(self.playbackRate ?? 1.0)
|
||||
@ -2458,7 +2460,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
|
||||
override func animateIn(from node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
|
||||
guard let videoNode = self.videoNode else {
|
||||
guard let videoNode = self.videoNode, let validLayout = self.validLayout else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -2487,8 +2489,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
self.context.sharedContext.mediaManager.setOverlayVideoNode(nil)
|
||||
} else {
|
||||
let scrubberTransition = (node.0 as? GalleryItemTransitionNode)?.scrubberTransition()
|
||||
|
||||
if let scrubberView = self.scrubberView {
|
||||
let scrubberTransition = (node.0 as? GalleryItemTransitionNode)?.scrubberTransition()
|
||||
scrubberView.animateIn(from: scrubberTransition, transition: .animated(duration: 0.25, curve: .spring))
|
||||
}
|
||||
|
||||
@ -2563,6 +2566,43 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
videoNode.layer.animate(from: NSValue(caTransform3D: transform), to: NSValue(caTransform3D: videoNode.layer.transform), keyPath: "transform", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25)
|
||||
|
||||
if let scrubberTransition, let contentTransition = scrubberTransition.content {
|
||||
let transitionContentView = contentTransition.makeView()
|
||||
let transitionSelfContentView = contentTransition.makeView()
|
||||
|
||||
addToTransitionSurface(transitionContentView)
|
||||
self.view.insertSubview(transitionSelfContentView, at: 0)
|
||||
transitionSelfContentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, removeOnCompletion: false)
|
||||
|
||||
if let transitionContentSuperview = transitionContentView.superview {
|
||||
let transitionContentSourceFrame = contentTransition.sourceView.convert(contentTransition.sourceRect, to: transitionContentSuperview)
|
||||
let transitionContentDestinationFrame = self.view.convert(self.view.bounds, to: transitionContentSuperview)
|
||||
|
||||
let transitionContentSelfSourceFrame = contentTransition.sourceView.convert(contentTransition.sourceRect, to: self.view)
|
||||
let transitionContentSelfDestinationFrame = self.view.convert(self.view.bounds, to: self.view)
|
||||
|
||||
let screenCornerRadius: CGFloat = validLayout.layout.deviceMetrics.screenCornerRadius
|
||||
|
||||
transitionContentView.frame = transitionContentSourceFrame
|
||||
contentTransition.updateView(transitionContentView, GalleryItemScrubberTransition.Content.TransitionState(sourceSize: transitionContentSourceFrame.size, destinationSize: transitionContentDestinationFrame.size, destinationCornerRadius: screenCornerRadius, progress: 0.0), .immediate)
|
||||
|
||||
transitionSelfContentView.frame = transitionContentSelfSourceFrame
|
||||
contentTransition.updateView(transitionSelfContentView, GalleryItemScrubberTransition.Content.TransitionState(sourceSize: transitionContentSelfSourceFrame.size, destinationSize: transitionContentSelfDestinationFrame.size, destinationCornerRadius: screenCornerRadius, progress: 0.0), .immediate)
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||
|
||||
transition.updateFrame(view: transitionContentView, frame: transitionContentDestinationFrame, completion: { [weak transitionContentView] _ in
|
||||
transitionContentView?.removeFromSuperview()
|
||||
})
|
||||
contentTransition.updateView(transitionContentView, GalleryItemScrubberTransition.Content.TransitionState(sourceSize: transitionContentSourceFrame.size, destinationSize: transitionContentDestinationFrame.size, destinationCornerRadius: screenCornerRadius, progress: 1.0), transition)
|
||||
|
||||
transition.updateFrame(view: transitionSelfContentView, frame: transitionContentSelfDestinationFrame, completion: { [weak transitionSelfContentView] _ in
|
||||
transitionSelfContentView?.removeFromSuperview()
|
||||
})
|
||||
contentTransition.updateView(transitionSelfContentView, GalleryItemScrubberTransition.Content.TransitionState(sourceSize: transitionContentSelfSourceFrame.size, destinationSize: transitionContentSelfDestinationFrame.size, destinationCornerRadius: screenCornerRadius, progress: 1.0), transition)
|
||||
}
|
||||
}
|
||||
|
||||
if self.item?.fromPlayingVideo ?? false {
|
||||
Queue.mainQueue().after(0.001) {
|
||||
videoNode.canAttachContent = true
|
||||
@ -2586,17 +2626,21 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
|
||||
override func animateOut(to node: (ASDisplayNode, CGRect, () -> (UIView?, UIView?)), addToTransitionSurface: (UIView) -> Void, completion: @escaping () -> Void) {
|
||||
self.isAnimatingOut = true
|
||||
|
||||
guard let videoNode = self.videoNode else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
|
||||
let scrubberTransition = (node.0 as? GalleryItemTransitionNode)?.scrubberTransition()
|
||||
|
||||
if let scrubberView = self.scrubberView {
|
||||
var scrubberTransition = (node.0 as? GalleryItemTransitionNode)?.scrubberTransition()
|
||||
var scrubberEffectiveTransition = scrubberTransition
|
||||
if !self.controlsVisibility() {
|
||||
scrubberTransition = nil
|
||||
scrubberEffectiveTransition = nil
|
||||
}
|
||||
scrubberView.animateOut(to: scrubberTransition, transition: .animated(duration: 0.25, curve: .spring))
|
||||
scrubberView.animateOut(to: scrubberEffectiveTransition, transition: .animated(duration: 0.25, curve: .spring))
|
||||
}
|
||||
|
||||
let transformedFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view)
|
||||
@ -2753,6 +2797,47 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
intermediateCompletion()
|
||||
})
|
||||
|
||||
var scrubberContentTransition = scrubberTransition
|
||||
if !self.controlsVisibility() {
|
||||
scrubberContentTransition = nil
|
||||
}
|
||||
if let scrubberContentTransition, let contentTransition = scrubberContentTransition.content {
|
||||
let transitionContentView = contentTransition.makeView()
|
||||
let transitionSelfContentView = contentTransition.makeView()
|
||||
|
||||
addToTransitionSurface(transitionContentView)
|
||||
//self.view.insertSubview(transitionSelfContentView, at: 0)
|
||||
transitionSelfContentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false)
|
||||
|
||||
if let validLayout = self.validLayout, let transitionContentSuperview = transitionContentView.superview {
|
||||
let transitionContentSourceFrame = contentTransition.sourceView.convert(contentTransition.sourceRect, to: transitionContentSuperview)
|
||||
let transitionContentDestinationFrame = self.view.convert(self.view.bounds, to: transitionContentSuperview)
|
||||
|
||||
let transitionContentSelfSourceFrame = contentTransition.sourceView.convert(contentTransition.sourceRect, to: self.view)
|
||||
let transitionContentSelfDestinationFrame = self.view.convert(self.view.bounds, to: self.view)
|
||||
|
||||
let screenCornerRadius: CGFloat = validLayout.layout.deviceMetrics.screenCornerRadius
|
||||
|
||||
transitionContentView.frame = transitionContentDestinationFrame
|
||||
contentTransition.updateView(transitionContentView, GalleryItemScrubberTransition.Content.TransitionState(sourceSize: transitionContentSourceFrame.size, destinationSize: transitionContentDestinationFrame.size, destinationCornerRadius: screenCornerRadius, progress: 1.0), .immediate)
|
||||
|
||||
transitionSelfContentView.frame = transitionContentSelfDestinationFrame
|
||||
contentTransition.updateView(transitionSelfContentView, GalleryItemScrubberTransition.Content.TransitionState(sourceSize: transitionContentSelfSourceFrame.size, destinationSize: transitionContentSelfDestinationFrame.size, destinationCornerRadius: screenCornerRadius, progress: 1.0), .immediate)
|
||||
|
||||
let transition: ContainedViewLayoutTransition = .animated(duration: 0.25, curve: .spring)
|
||||
|
||||
transition.updateFrame(view: transitionContentView, frame: transitionContentSourceFrame, completion: { [weak transitionContentView] _ in
|
||||
transitionContentView?.removeFromSuperview()
|
||||
})
|
||||
contentTransition.updateView(transitionContentView, GalleryItemScrubberTransition.Content.TransitionState(sourceSize: transitionContentSourceFrame.size, destinationSize: transitionContentDestinationFrame.size, destinationCornerRadius: screenCornerRadius, progress: 0.0), transition)
|
||||
|
||||
transition.updateFrame(view: transitionSelfContentView, frame: transitionContentSelfSourceFrame, completion: { [weak transitionSelfContentView] _ in
|
||||
transitionSelfContentView?.removeFromSuperview()
|
||||
})
|
||||
contentTransition.updateView(transitionSelfContentView, GalleryItemScrubberTransition.Content.TransitionState(sourceSize: transitionContentSelfSourceFrame.size, destinationSize: transitionContentSelfDestinationFrame.size, destinationCornerRadius: screenCornerRadius, progress: 0.0), transition)
|
||||
}
|
||||
}
|
||||
|
||||
if let pictureInPictureNode = self.pictureInPictureNode {
|
||||
let transformedPlaceholderFrame = node.0.view.convert(node.0.view.bounds, to: pictureInPictureNode.view)
|
||||
let pictureInPictureTransform = CATransform3DScale(pictureInPictureNode.layer.transform, transformedPlaceholderFrame.size.width / pictureInPictureNode.layer.bounds.size.width, transformedPlaceholderFrame.size.height / pictureInPictureNode.layer.bounds.size.height, 1.0)
|
||||
|
@ -950,6 +950,9 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
}
|
||||
if actions {
|
||||
actionNodes.append(contentsOf: [self.actionsBackgroundNode, self.actionButtonNode, self.actionSeparatorNode])
|
||||
if let startAtTimestampNode = self.startAtTimestampNode {
|
||||
actionNodes.append(startAtTimestampNode)
|
||||
}
|
||||
}
|
||||
updateActionNodesAlpha(actionNodes, alpha: hidden ? 0.0 : 1.0)
|
||||
}
|
||||
@ -1326,6 +1329,9 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
transition.updateAlpha(node: self.inputFieldNode, alpha: 0.0)
|
||||
transition.updateAlpha(node: self.actionSeparatorNode, alpha: 0.0)
|
||||
transition.updateAlpha(node: self.actionsBackgroundNode, alpha: 0.0)
|
||||
if let startAtTimestampNode = self.startAtTimestampNode {
|
||||
transition.updateAlpha(node: startAtTimestampNode, alpha: 0.0)
|
||||
}
|
||||
|
||||
let peerIds: [PeerId]
|
||||
var topicIds: [PeerId: Int64] = [:]
|
||||
@ -1623,6 +1629,9 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
transition.updateAlpha(node: strongSelf.inputFieldNode, alpha: 0.0)
|
||||
transition.updateAlpha(node: strongSelf.actionSeparatorNode, alpha: 0.0)
|
||||
transition.updateAlpha(node: strongSelf.actionsBackgroundNode, alpha: 0.0)
|
||||
if let startAtTimestampNode = strongSelf.startAtTimestampNode {
|
||||
transition.updateAlpha(node: startAtTimestampNode, alpha: 0.0)
|
||||
}
|
||||
strongSelf.transitionToContentNode(ShareLoadingContainerNode(theme: strongSelf.presentationData.theme, forceNativeAppearance: true), fastOut: true)
|
||||
loadingTimestamp = CACurrentMediaTime()
|
||||
if reportReady {
|
||||
@ -1777,6 +1786,9 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
transition.updateAlpha(node: self.inputFieldNode, alpha: 0.0)
|
||||
transition.updateAlpha(node: self.actionSeparatorNode, alpha: 0.0)
|
||||
transition.updateAlpha(node: self.actionsBackgroundNode, alpha: 0.0)
|
||||
if let startAtTimestampNode = self.startAtTimestampNode {
|
||||
transition.updateAlpha(node: startAtTimestampNode, alpha: 0.0)
|
||||
}
|
||||
|
||||
self.transitionToContentNode(ShareProlongedLoadingContainerNode(theme: self.presentationData.theme, strings: self.presentationData.strings, forceNativeAppearance: true, postbox: self.context?.stateManager.postbox, environment: self.environment), fastOut: true)
|
||||
let timestamp = CACurrentMediaTime()
|
||||
@ -1815,6 +1827,9 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
transition.updateAlpha(node: self.inputFieldNode, alpha: 0.0)
|
||||
transition.updateAlpha(node: self.actionSeparatorNode, alpha: 0.0)
|
||||
transition.updateAlpha(node: self.actionsBackgroundNode, alpha: 0.0)
|
||||
if let startAtTimestampNode = self.startAtTimestampNode {
|
||||
transition.updateAlpha(node: startAtTimestampNode, alpha: 0.0)
|
||||
}
|
||||
|
||||
self.transitionToContentNode(ShareLoadingContainerNode(theme: self.presentationData.theme, forceNativeAppearance: true), fastOut: true)
|
||||
|
||||
|
@ -1863,6 +1863,21 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
statusFrame.origin.y = floor(imageFrame.height / 2.0 - statusFrame.height / 2.0)
|
||||
statusNode.frame = statusFrame
|
||||
}
|
||||
|
||||
var videoTimestamp: Int32?
|
||||
var storedVideoTimestamp: Int32?
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ForwardVideoTimestampAttribute {
|
||||
videoTimestamp = attribute.timestamp
|
||||
} else if let attribute = attribute as? DerivedDataMessageAttribute {
|
||||
if let value = attribute.data["mps"]?.get(MediaPlaybackStoredState.self) {
|
||||
storedVideoTimestamp = Int32(value.timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let storedVideoTimestamp {
|
||||
videoTimestamp = storedVideoTimestamp
|
||||
}
|
||||
|
||||
var updatedVideoNodeReadySignal: Signal<Void, NoError>?
|
||||
var updatedPlayerStatusSignal: Signal<MediaPlayerStatus?, NoError>?
|
||||
@ -1920,8 +1935,15 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
}
|
||||
let videoNode = UniversalVideoNode(context: context, postbox: context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: decoration, content: videoContent, priority: .embedded)
|
||||
videoNode.isUserInteractionEnabled = false
|
||||
var firstTime = true
|
||||
videoNode.ownsContentNodeUpdated = { [weak self] owns in
|
||||
if let strongSelf = self {
|
||||
if firstTime {
|
||||
firstTime = false
|
||||
if let videoTimestamp {
|
||||
videoNode.seek(Double(videoTimestamp))
|
||||
}
|
||||
}
|
||||
strongSelf.videoNode?.isHidden = !owns
|
||||
if owns {
|
||||
strongSelf.videoNode?.setBaseRate(1.0)
|
||||
@ -2006,21 +2028,6 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
}
|
||||
}
|
||||
|
||||
var videoTimestamp: Int32?
|
||||
var storedVideoTimestamp: Int32?
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ForwardVideoTimestampAttribute {
|
||||
videoTimestamp = attribute.timestamp
|
||||
} else if let attribute = attribute as? DerivedDataMessageAttribute {
|
||||
if let value = attribute.data["mps"]?.get(MediaPlaybackStoredState.self) {
|
||||
storedVideoTimestamp = Int32(value.timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let storedVideoTimestamp {
|
||||
videoTimestamp = storedVideoTimestamp
|
||||
}
|
||||
|
||||
if let videoTimestamp, let file = media as? TelegramMediaFile, let duration = file.duration, duration > 1.0 {
|
||||
let timestampContainerView: UIView
|
||||
if let current = strongSelf.timestampContainerView {
|
||||
@ -2064,7 +2071,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
videoTimestampBackgroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.5).cgColor
|
||||
videoTimestampForegroundLayer.backgroundColor = message.effectivelyIncoming(context.account.peerId) ? presentationData.theme.theme.chat.message.incoming.accentControlColor.cgColor : presentationData.theme.theme.chat.message.outgoing.accentControlColor.cgColor
|
||||
|
||||
timestampContainerView.frame = imageFrame
|
||||
timestampContainerView.frame = imageFrame.offsetBy(dx: arguments.corners.extendedEdges.left, dy: 0.0)
|
||||
timestampMaskView.frame = imageFrame
|
||||
|
||||
let videoTimestampBackgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: imageFrame.height - 3.0), size: CGSize(width: imageFrame.width, height: 3.0))
|
||||
@ -3093,55 +3100,175 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
}
|
||||
|
||||
public func scrubberTransition() -> GalleryItemScrubberTransition? {
|
||||
if let timestampContainerView = self.timestampContainerView, let timestampMaskView = self.timestampMaskView, let videoTimestampBackgroundLayer = self.videoTimestampBackgroundLayer, let videoTimestampForegroundLayer = self.videoTimestampForegroundLayer {
|
||||
final class TimestampContainerTransitionView: UIView {
|
||||
let containerView: UIView
|
||||
let containerMaskView: UIImageView
|
||||
let backgroundLayer: SimpleLayer
|
||||
let foregroundLayer: SimpleLayer
|
||||
let fraction: CGFloat
|
||||
final class TimestampContainerTransitionView: UIView {
|
||||
let containerView: UIView
|
||||
let containerMaskView: UIImageView
|
||||
let backgroundLayer: SimpleLayer
|
||||
let foregroundLayer: SimpleLayer
|
||||
let fraction: CGFloat
|
||||
|
||||
init(timestampContainerView: UIView?, timestampMaskView: UIImageView?, videoTimestampBackgroundLayer: SimpleLayer?, videoTimestampForegroundLayer: SimpleLayer?) {
|
||||
self.containerView = UIView()
|
||||
self.containerMaskView = UIImageView()
|
||||
self.backgroundLayer = SimpleLayer()
|
||||
self.foregroundLayer = SimpleLayer()
|
||||
|
||||
init(timestampContainerView: UIView?, timestampMaskView: UIImageView?, videoTimestampBackgroundLayer: SimpleLayer?, videoTimestampForegroundLayer: SimpleLayer?) {
|
||||
self.containerView = UIView()
|
||||
self.containerMaskView = UIImageView()
|
||||
self.backgroundLayer = SimpleLayer()
|
||||
self.foregroundLayer = SimpleLayer()
|
||||
|
||||
if let videoTimestampBackgroundLayer, let videoTimestampForegroundLayer {
|
||||
self.fraction = videoTimestampForegroundLayer.bounds.width / videoTimestampBackgroundLayer.bounds.width
|
||||
} else {
|
||||
self.fraction = 0.0
|
||||
}
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addSubview(self.containerView)
|
||||
|
||||
self.containerView.mask = self.containerMaskView
|
||||
self.containerMaskView.image = timestampMaskView?.image
|
||||
|
||||
self.containerView.layer.addSublayer(self.backgroundLayer)
|
||||
self.containerView.layer.addSublayer(self.foregroundLayer)
|
||||
|
||||
self.backgroundLayer.backgroundColor = videoTimestampBackgroundLayer?.backgroundColor
|
||||
self.foregroundLayer.backgroundColor = videoTimestampForegroundLayer?.backgroundColor
|
||||
if let videoTimestampBackgroundLayer, let videoTimestampForegroundLayer {
|
||||
self.fraction = videoTimestampForegroundLayer.bounds.width / videoTimestampBackgroundLayer.bounds.width
|
||||
} else {
|
||||
self.fraction = 0.0
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
super.init(frame: CGRect())
|
||||
|
||||
func update(state: GalleryItemScrubberTransition.TransitionState, transition: ContainedViewLayoutTransition) {
|
||||
let containerFrame = CGRect(origin: CGPoint(), size: state.sourceSize.interpolate(to: state.destinationSize, amount: state.progress))
|
||||
transition.updateFrame(view: self.containerView, frame: containerFrame)
|
||||
transition.updateFrame(view: self.containerMaskView, frame: CGRect(origin: CGPoint(), size: containerFrame.size))
|
||||
|
||||
transition.updateFrame(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: containerFrame.height - 3.0), size: CGSize(width: containerFrame.width, height: 3.0)))
|
||||
transition.updateFrame(layer: self.foregroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: containerFrame.height - 3.0), size: CGSize(width: containerFrame.width * self.fraction, height: 3.0)))
|
||||
}
|
||||
self.addSubview(self.containerView)
|
||||
|
||||
self.containerView.mask = self.containerMaskView
|
||||
self.containerMaskView.image = timestampMaskView?.image
|
||||
|
||||
self.containerView.layer.addSublayer(self.backgroundLayer)
|
||||
self.containerView.layer.addSublayer(self.foregroundLayer)
|
||||
|
||||
self.backgroundLayer.backgroundColor = videoTimestampBackgroundLayer?.backgroundColor
|
||||
self.foregroundLayer.backgroundColor = videoTimestampForegroundLayer?.backgroundColor
|
||||
}
|
||||
|
||||
return GalleryItemScrubberTransition(
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(state: GalleryItemScrubberTransition.Scrubber.TransitionState, transition: ContainedViewLayoutTransition) {
|
||||
let containerFrame = CGRect(origin: CGPoint(), size: state.sourceSize.interpolate(to: state.destinationSize, amount: state.progress))
|
||||
transition.updateFrame(view: self.containerView, frame: containerFrame)
|
||||
transition.updateFrame(view: self.containerMaskView, frame: CGRect(origin: CGPoint(), size: containerFrame.size))
|
||||
|
||||
transition.updateFrame(layer: self.backgroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: containerFrame.height - 3.0), size: CGSize(width: containerFrame.width, height: 3.0)))
|
||||
transition.updateFrame(layer: self.foregroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: containerFrame.height - 3.0), size: CGSize(width: containerFrame.width * self.fraction, height: 3.0)))
|
||||
}
|
||||
}
|
||||
|
||||
final class MediaContentTransitionView: UIView {
|
||||
let backgroundLayer: SimpleLayer
|
||||
let backgroundMaskLayer: SimpleShapeLayer
|
||||
let sourceCorners: ImageCorners
|
||||
|
||||
init(imageNode: TransformImageNode) {
|
||||
self.backgroundLayer = SimpleLayer()
|
||||
self.backgroundLayer.backgroundColor = UIColor.black.cgColor
|
||||
|
||||
self.backgroundMaskLayer = SimpleShapeLayer()
|
||||
self.backgroundMaskLayer.fillColor = UIColor.white.cgColor
|
||||
self.backgroundLayer.mask = self.backgroundMaskLayer
|
||||
|
||||
self.sourceCorners = imageNode.currentArguments?.corners ?? ImageCorners()
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.layer.addSublayer(self.backgroundLayer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(state: GalleryItemScrubberTransition.Content.TransitionState, transition: ContainedViewLayoutTransition) {
|
||||
let sourceCorners: (topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat) = (max(0.1, self.sourceCorners.topLeft.radius), max(0.1, self.sourceCorners.topRight.radius), max(0.1, self.sourceCorners.bottomLeft.radius), max(0.1, self.sourceCorners.bottomRight.radius))
|
||||
let destinationCorners: (topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat) = (max(0.1, state.destinationCornerRadius), max(0.1, state.destinationCornerRadius), max(0.1, state.destinationCornerRadius), max(0.1, state.destinationCornerRadius))
|
||||
|
||||
let currentCornersData = CGRect(x: sourceCorners.topLeft, y: sourceCorners.topRight, width: sourceCorners.bottomLeft, height: sourceCorners.bottomRight).interpolate(to: CGRect(x: destinationCorners.topLeft, y: destinationCorners.topRight, width: destinationCorners.bottomLeft, height: destinationCorners.bottomRight), amount: state.progress)
|
||||
let currentCorners: (topLeft: CGFloat, topRight: CGFloat, bottomLeft: CGFloat, bottomRight: CGFloat) = (currentCornersData.minX, currentCornersData.minY, currentCornersData.width, currentCornersData.height)
|
||||
|
||||
func makeRoundedRectPath(
|
||||
in rect: CGRect,
|
||||
topLeft: CGFloat,
|
||||
topRight: CGFloat,
|
||||
bottomRight: CGFloat,
|
||||
bottomLeft: CGFloat
|
||||
) -> CGPath {
|
||||
let path = CGMutablePath()
|
||||
|
||||
// Move to top-left, offset by its corner radius
|
||||
path.move(to: CGPoint(x: rect.minX + topLeft, y: rect.minY))
|
||||
|
||||
// Top edge (straight line)
|
||||
path.addLine(to: CGPoint(x: rect.maxX - topRight, y: rect.minY))
|
||||
// Top-right corner arc
|
||||
if topRight > 0 {
|
||||
path.addArc(
|
||||
center: CGPoint(x: rect.maxX - topRight, y: rect.minY + topRight),
|
||||
radius: topRight,
|
||||
startAngle: -CGFloat.pi / 2,
|
||||
endAngle: 0,
|
||||
clockwise: false
|
||||
)
|
||||
}
|
||||
|
||||
// Right edge (straight line)
|
||||
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - bottomRight))
|
||||
// Bottom-right corner arc
|
||||
if bottomRight > 0 {
|
||||
path.addArc(
|
||||
center: CGPoint(x: rect.maxX - bottomRight, y: rect.maxY - bottomRight),
|
||||
radius: bottomRight,
|
||||
startAngle: 0,
|
||||
endAngle: CGFloat.pi / 2,
|
||||
clockwise: false
|
||||
)
|
||||
}
|
||||
|
||||
// Bottom edge (straight line)
|
||||
path.addLine(to: CGPoint(x: rect.minX + bottomLeft, y: rect.maxY))
|
||||
// Bottom-left corner arc
|
||||
if bottomLeft > 0 {
|
||||
path.addArc(
|
||||
center: CGPoint(x: rect.minX + bottomLeft, y: rect.maxY - bottomLeft),
|
||||
radius: bottomLeft,
|
||||
startAngle: CGFloat.pi / 2,
|
||||
endAngle: CGFloat.pi,
|
||||
clockwise: false
|
||||
)
|
||||
}
|
||||
|
||||
// Left edge (straight line)
|
||||
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + topLeft))
|
||||
// Top-left corner arc
|
||||
if topLeft > 0 {
|
||||
path.addArc(
|
||||
center: CGPoint(x: rect.minX + topLeft, y: rect.minY + topLeft),
|
||||
radius: topLeft,
|
||||
startAngle: CGFloat.pi,
|
||||
endAngle: 3 * CGFloat.pi / 2,
|
||||
clockwise: false
|
||||
)
|
||||
}
|
||||
|
||||
path.closeSubpath()
|
||||
return path
|
||||
}
|
||||
|
||||
let backgroundFrame = CGRect(origin: CGPoint(), size: state.sourceSize.interpolate(to: state.destinationSize, amount: state.progress))
|
||||
|
||||
transition.updatePath(layer: self.backgroundMaskLayer, path: makeRoundedRectPath(in: CGRect(origin: CGPoint(), size: backgroundFrame.size), topLeft: currentCorners.topLeft, topRight: currentCorners.topRight, bottomRight: currentCorners.bottomLeft, bottomLeft: currentCorners.bottomRight))
|
||||
|
||||
transition.updateFrame(layer: self.backgroundLayer, frame: backgroundFrame)
|
||||
|
||||
transition.updateFrame(layer: self.backgroundMaskLayer, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
|
||||
}
|
||||
}
|
||||
|
||||
guard let currentImageArguments = self.currentImageArguments else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var sourceContentRect = self.imageNode.bounds
|
||||
sourceContentRect.origin.x += currentImageArguments.insets.left
|
||||
sourceContentRect.origin.y += currentImageArguments.insets.top
|
||||
sourceContentRect.size.width -= currentImageArguments.insets.left + currentImageArguments.insets.right
|
||||
sourceContentRect.size.height -= currentImageArguments.insets.top + currentImageArguments.insets.bottom
|
||||
|
||||
var scrubber: GalleryItemScrubberTransition.Scrubber?
|
||||
if let timestampContainerView = self.timestampContainerView, let timestampMaskView = self.timestampMaskView, let videoTimestampBackgroundLayer = self.videoTimestampBackgroundLayer, let videoTimestampForegroundLayer = self.videoTimestampForegroundLayer {
|
||||
scrubber = GalleryItemScrubberTransition.Scrubber(
|
||||
view: timestampContainerView,
|
||||
makeView: { [weak timestampContainerView, weak timestampMaskView, weak videoTimestampBackgroundLayer, weak videoTimestampForegroundLayer] in
|
||||
return TimestampContainerTransitionView(timestampContainerView: timestampContainerView, timestampMaskView: timestampMaskView, videoTimestampBackgroundLayer: videoTimestampBackgroundLayer, videoTimestampForegroundLayer: videoTimestampForegroundLayer)
|
||||
@ -3150,12 +3277,33 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
if let view = view as? TimestampContainerTransitionView {
|
||||
view.update(state: state, transition: transition)
|
||||
}
|
||||
},
|
||||
insertCloneTransitionView: nil
|
||||
}
|
||||
)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var content: GalleryItemScrubberTransition.Content?
|
||||
content = GalleryItemScrubberTransition.Content(
|
||||
sourceView: self.imageNode.view,
|
||||
sourceRect: sourceContentRect,
|
||||
makeView: { [weak self] in
|
||||
guard let self else {
|
||||
return UIView()
|
||||
}
|
||||
|
||||
return MediaContentTransitionView(imageNode: self.imageNode)
|
||||
},
|
||||
updateView: { view, state, transition in
|
||||
guard let view = view as? MediaContentTransitionView else {
|
||||
return
|
||||
}
|
||||
view.update(state: state, transition: transition)
|
||||
}
|
||||
)
|
||||
|
||||
return GalleryItemScrubberTransition(
|
||||
scrubber: scrubber,
|
||||
content: content
|
||||
)
|
||||
}
|
||||
|
||||
public func playMediaWithSound() -> (action: (Double?) -> Void, soundEnabled: Bool, isVideoMessage: Bool, isUnread: Bool, badgeNode: ASDisplayNode?)? {
|
||||
|
@ -1439,7 +1439,8 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
})
|
||||
}
|
||||
|
||||
self.channelsForPublicReactionDisposable = (component.context.engine.peers.channelsForPublicReaction(useLocalCache: false)
|
||||
//TODO:wip-release
|
||||
/*self.channelsForPublicReactionDisposable = (component.context.engine.peers.channelsForPublicReaction(useLocalCache: false)
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] peers in
|
||||
guard let self else {
|
||||
return
|
||||
@ -1448,7 +1449,7 @@ private final class ChatSendStarsScreenComponent: Component {
|
||||
self.channelsForPublicReaction = peers
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
})
|
||||
})*/
|
||||
}
|
||||
|
||||
self.component = component
|
||||
|
@ -51,7 +51,33 @@ extension ChatControllerImpl {
|
||||
return
|
||||
}
|
||||
|
||||
UIPasteboard.general.string = timecode
|
||||
if message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, let addressName = channel.addressName {
|
||||
var timestampSuffix = ""
|
||||
let startAtTimestamp = parseTimeString(timecode)
|
||||
|
||||
var startAtTimestampString = ""
|
||||
let hours = startAtTimestamp / 3600
|
||||
let minutes = startAtTimestamp / 60 % 60
|
||||
let seconds = startAtTimestamp % 60
|
||||
if hours == 0 && minutes == 0 {
|
||||
startAtTimestampString = "\(startAtTimestamp)"
|
||||
} else {
|
||||
if hours != 0 {
|
||||
startAtTimestampString += "\(hours)h"
|
||||
}
|
||||
if minutes != 0 {
|
||||
startAtTimestampString += "\(minutes)m"
|
||||
}
|
||||
if seconds != 0 {
|
||||
startAtTimestampString += "\(seconds)s"
|
||||
}
|
||||
}
|
||||
timestampSuffix = "?t=\(startAtTimestampString)"
|
||||
let inputCopyText = "https://t.me/\(addressName)/\(message.id.id)\(timestampSuffix)"
|
||||
UIPasteboard.general.string = inputCopyText
|
||||
} else {
|
||||
UIPasteboard.general.string = timecode
|
||||
}
|
||||
|
||||
self.present(UndoOverlayController(presentationData: self.presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current)
|
||||
}))
|
||||
@ -67,3 +93,30 @@ extension ChatControllerImpl {
|
||||
self.window?.presentInGlobalOverlay(controller)
|
||||
}
|
||||
}
|
||||
|
||||
private func parseTimeString(_ timeString: String) -> Int {
|
||||
let parts = timeString.split(separator: ":").map(String.init)
|
||||
|
||||
switch parts.count {
|
||||
case 1:
|
||||
// Single component (e.g. "1", "10") => seconds
|
||||
return Int(parts[0]) ?? 0
|
||||
|
||||
case 2:
|
||||
// Two components (e.g. "1:01", "10:30") => minutes:seconds
|
||||
let minutes = Int(parts[0]) ?? 0
|
||||
let seconds = Int(parts[1]) ?? 0
|
||||
return minutes * 60 + seconds
|
||||
|
||||
case 3:
|
||||
// Three components (e.g. "1:01:01", "10:00:00") => hours:minutes:seconds
|
||||
let hours = Int(parts[0]) ?? 0
|
||||
let minutes = Int(parts[1]) ?? 0
|
||||
let seconds = Int(parts[2]) ?? 0
|
||||
return hours * 3600 + minutes * 60 + seconds
|
||||
|
||||
default:
|
||||
// Fallback to 0 or handle invalid format
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
@ -10964,10 +10964,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
public func updatePushedTransition(_ fraction: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
if !transition.isAnimated {
|
||||
self.chatDisplayNode.historyNodeContainer.layer.removeAllAnimations()
|
||||
self.chatDisplayNode.historyNode.layer.removeAnimation(forKey: "sublayerTransform")
|
||||
}
|
||||
let scale: CGFloat = 1.0 - 0.06 * fraction
|
||||
transition.updateTransformScale(node: self.chatDisplayNode.historyNodeContainer, scale: scale)
|
||||
transition.updateSublayerTransformScale(node: self.chatDisplayNode.historyNode, scale: scale)
|
||||
}
|
||||
|
||||
func restrictedSendingContentsText() -> String {
|
||||
|
Loading…
x
Reference in New Issue
Block a user