mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Finalize timestamp sharing
This commit is contained in:
parent
d0b5f8b400
commit
c21ebb06b5
@ -49,11 +49,23 @@ public enum ShareControllerError {
|
||||
}
|
||||
|
||||
public enum ShareControllerSubject {
|
||||
public final class PublicLinkPrefix {
|
||||
public let visibleString: String
|
||||
public let actualString: String
|
||||
|
||||
public init(visibleString: String, actualString: String) {
|
||||
self.visibleString = visibleString
|
||||
self.actualString = actualString
|
||||
}
|
||||
}
|
||||
|
||||
public final class MediaParameters {
|
||||
public let startAtTimestamp: Int32?
|
||||
public let publicLinkPrefix: PublicLinkPrefix?
|
||||
|
||||
public init(startAtTimestamp: Int32?) {
|
||||
public init(startAtTimestamp: Int32?, publicLinkPrefix: PublicLinkPrefix?) {
|
||||
self.startAtTimestamp = startAtTimestamp
|
||||
self.publicLinkPrefix = publicLinkPrefix
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1367,6 +1367,11 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
||||
return panelHeight
|
||||
}
|
||||
|
||||
override func animateIn(transition: ContainedViewLayoutTransition) {
|
||||
self.contentNode.alpha = 0.0
|
||||
transition.updateAlpha(node: self.contentNode, alpha: self.visibilityAlpha)
|
||||
}
|
||||
|
||||
override func animateIn(fromHeight: CGFloat, previousContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition) {
|
||||
if let scrubberView = self.scrubberView, scrubberView.superview == self.view {
|
||||
if let previousContentNode = previousContentNode as? ChatItemGalleryFooterContentNode, previousContentNode.scrubberView != nil {
|
||||
@ -1392,6 +1397,10 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
||||
self.scrollWrapperNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
|
||||
override func animateOut(transition: ContainedViewLayoutTransition) {
|
||||
transition.updateAlpha(node: self.contentNode, alpha: 0.0)
|
||||
}
|
||||
|
||||
override func animateOut(toHeight: CGFloat, nextContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
if let scrubberView = self.scrubberView, scrubberView.superview == self.view {
|
||||
if let nextContentNode = nextContentNode as? ChatItemGalleryFooterContentNode, nextContentNode.scrubberView != nil {
|
||||
@ -1718,6 +1727,30 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
||||
shareController.dismissed = { [weak self] _ in
|
||||
self?.interacting?(false)
|
||||
}
|
||||
shareController.onMediaTimestampLinkCopied = { [weak self] timestamp in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let text: String
|
||||
if let timestamp {
|
||||
//TODO:localize
|
||||
let startTimeString: String
|
||||
let hours = timestamp / (60 * 60)
|
||||
let minutes = timestamp % (60 * 60) / 60
|
||||
let seconds = timestamp % 60
|
||||
if hours != 0 {
|
||||
startTimeString = String(format: "%d:%02d:%02d", hours, minutes, seconds)
|
||||
} else {
|
||||
startTimeString = String(format: "%d:%02d", minutes, seconds)
|
||||
}
|
||||
text = "Link with start time at \(startTimeString) copied to clipboard."
|
||||
} else {
|
||||
text = presentationData.strings.Conversation_LinkCopied
|
||||
}
|
||||
|
||||
self.controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .linkCopied(title: nil, text: text), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return true }), nil)
|
||||
}
|
||||
|
||||
shareController.actionCompleted = { [weak self] in
|
||||
if let strongSelf = self, let actionCompletionText = actionCompletionText {
|
||||
|
@ -42,6 +42,8 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
||||
|
||||
private var currentChapter: MediaPlayerScrubbingChapter?
|
||||
|
||||
private var isAnimatedOut: Bool = false
|
||||
|
||||
var hideWhenDurationIsUnknown = false {
|
||||
didSet {
|
||||
if self.hideWhenDurationIsUnknown {
|
||||
@ -150,6 +152,9 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
||||
}
|
||||
|
||||
func updateTimestampsVisibility(animated: Bool) {
|
||||
if self.isAnimatedOut {
|
||||
return
|
||||
}
|
||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate
|
||||
let alpha: CGFloat = self.isCollapsed == true || self.isLoading ? 0.0 : 1.0
|
||||
transition.updateAlpha(node: self.leftTimestampNode, alpha: alpha)
|
||||
@ -375,4 +380,80 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
||||
}
|
||||
return hitTestRect.contains(point)
|
||||
}
|
||||
|
||||
func animateIn(from scrubberTransition: GalleryItemScrubberTransition?, transition: ContainedViewLayoutTransition) {
|
||||
if let scrubberTransition {
|
||||
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)
|
||||
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)
|
||||
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
|
||||
scrubberTransitionView?.isHidden = false
|
||||
targetCloneView?.removeFromSuperview()
|
||||
})
|
||||
|
||||
let scrubberSourceRect = CGRect(origin: CGPoint(x: fromRect.minX, y: fromRect.maxY - 3.0), size: CGSize(width: fromRect.width, height: 3.0))
|
||||
|
||||
let leftTimestampOffset = CGPoint(x: self.leftTimestampNode.position.x - self.scrubberNode.frame.minX, y: self.leftTimestampNode.position.y - self.scrubberNode.frame.maxY)
|
||||
let rightTimestampOffset = CGPoint(x: self.rightTimestampNode.position.x - self.scrubberNode.frame.maxX, y: self.rightTimestampNode.position.y - self.scrubberNode.frame.maxY)
|
||||
|
||||
transition.animatePosition(node: self.scrubberNode, from: scrubberSourceRect.center)
|
||||
self.scrubberNode.animateWidth(from: scrubberSourceRect.width, transition: transition)
|
||||
|
||||
transition.animatePosition(node: self.leftTimestampNode, from: CGPoint(x: leftTimestampOffset.x + scrubberSourceRect.minX, y: leftTimestampOffset.y + scrubberSourceRect.maxY))
|
||||
transition.animatePosition(node: self.rightTimestampNode, from: CGPoint(x: rightTimestampOffset.x + scrubberSourceRect.maxX, y: rightTimestampOffset.y + scrubberSourceRect.maxY))
|
||||
}
|
||||
|
||||
self.scrubberNode.layer.animateAlpha(from: 0.0, to: self.leftTimestampNode.alpha, duration: 0.25)
|
||||
self.leftTimestampNode.layer.animateAlpha(from: 0.0, to: self.leftTimestampNode.alpha, duration: 0.25)
|
||||
self.rightTimestampNode.layer.animateAlpha(from: 0.0, to: self.leftTimestampNode.alpha, duration: 0.25)
|
||||
self.infoNode.layer.animateAlpha(from: 0.0, to: self.leftTimestampNode.alpha, duration: 0.25)
|
||||
}
|
||||
|
||||
func animateOut(to scrubberTransition: GalleryItemScrubberTransition?, transition: ContainedViewLayoutTransition) {
|
||||
self.isAnimatedOut = true
|
||||
|
||||
if let scrubberTransition {
|
||||
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)
|
||||
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)
|
||||
let scrubberTransitionView = scrubberTransition.view
|
||||
scrubberTransitionView.isHidden = true
|
||||
transition.updateAlpha(layer: targetCloneView.layer, alpha: 1.0, completion: { [weak scrubberTransitionView] _ in
|
||||
scrubberTransitionView?.isHidden = false
|
||||
})
|
||||
|
||||
let leftTimestampOffset = CGPoint(x: self.leftTimestampNode.position.x - self.scrubberNode.frame.minX, y: self.leftTimestampNode.position.y - self.scrubberNode.frame.maxY)
|
||||
let rightTimestampOffset = CGPoint(x: self.rightTimestampNode.position.x - self.scrubberNode.frame.maxX, y: self.rightTimestampNode.position.y - self.scrubberNode.frame.maxY)
|
||||
|
||||
transition.animatePositionAdditive(layer: self.scrubberNode.layer, offset: CGPoint(), to: CGPoint(x: scrubberDestinationRect.midX - self.scrubberNode.position.x, y: scrubberDestinationRect.midY - self.scrubberNode.position.y), removeOnCompletion: false)
|
||||
|
||||
self.scrubberNode.animateWidth(to: scrubberDestinationRect.width, transition: transition)
|
||||
|
||||
transition.animatePositionAdditive(layer: self.leftTimestampNode.layer, offset: CGPoint(), to: CGPoint(x: -self.leftTimestampNode.position.x + (leftTimestampOffset.x + scrubberDestinationRect.minX), y: -self.leftTimestampNode.position.y + (leftTimestampOffset.y + scrubberDestinationRect.maxY)), removeOnCompletion: false)
|
||||
|
||||
transition.animatePositionAdditive(layer: self.rightTimestampNode.layer, offset: CGPoint(), to: CGPoint(x: -self.rightTimestampNode.position.x + (rightTimestampOffset.x + scrubberDestinationRect.maxX), y: -self.rightTimestampNode.position.y + (rightTimestampOffset.y + scrubberDestinationRect.maxY)), removeOnCompletion: false)
|
||||
}
|
||||
|
||||
transition.updateAlpha(layer: self.scrubberNode.layer, alpha: 0.0)
|
||||
transition.updateAlpha(layer: self.leftTimestampNode.layer, alpha: 0.0)
|
||||
transition.updateAlpha(layer: self.rightTimestampNode.layer, alpha: 0.0)
|
||||
transition.updateAlpha(layer: self.infoNode.layer, alpha: 0.0)
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +83,13 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
|
||||
}
|
||||
}
|
||||
|
||||
self.pager.controlsVisibility = { [weak self] in
|
||||
guard let self else {
|
||||
return true
|
||||
}
|
||||
return !self.areControlsHidden && self.footerNode.alpha != 0.0
|
||||
}
|
||||
|
||||
self.pager.updateOrientation = { [weak self] orientation in
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateOrientation?(orientation)
|
||||
@ -364,11 +371,15 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
|
||||
if !self.areControlsHidden {
|
||||
self.statusBar?.alpha = 1.0
|
||||
self.navigationBar?.alpha = 1.0
|
||||
self.footerNode.alpha = 1.0
|
||||
self.updateThumbnailContainerNodeAlpha(.immediate)
|
||||
}
|
||||
})
|
||||
|
||||
if !self.areControlsHidden {
|
||||
self.footerNode.alpha = 1.0
|
||||
self.footerNode.animateIn(transition: .animated(duration: 0.15, curve: .linear))
|
||||
}
|
||||
|
||||
if animateContent {
|
||||
self.scrollView.layer.animateBounds(from: self.scrollView.layer.bounds.offsetBy(dx: 0.0, dy: -self.scrollView.layer.bounds.size.height), to: self.scrollView.layer.bounds, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
} else if useSimpleAnimation {
|
||||
@ -402,13 +413,14 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
|
||||
UIView.animate(withDuration: 0.1, animations: {
|
||||
self.statusBar?.alpha = 0.0
|
||||
self.navigationBar?.alpha = 0.0
|
||||
self.footerNode.alpha = 0.0
|
||||
self.currentThumbnailContainerNode?.alpha = 0.0
|
||||
}, completion: { _ in
|
||||
interfaceAnimationCompleted = true
|
||||
intermediateCompletion()
|
||||
})
|
||||
|
||||
self.footerNode.animateOut(transition: .animated(duration: 0.1, curve: .easeInOut))
|
||||
|
||||
if animateContent {
|
||||
contentAnimationCompleted = false
|
||||
self.scrollView.layer.animateBounds(from: self.scrollView.layer.bounds, to: self.scrollView.layer.bounds.offsetBy(dx: 0.0, dy: -self.scrollView.layer.bounds.size.height), duration: 0.25, timingFunction: CAMediaTimingFunctionName.linear.rawValue, removeOnCompletion: false, completion: { _ in
|
||||
|
@ -37,9 +37,18 @@ open class GalleryFooterContentNode: ASDisplayNode {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
open func animateIn(transition: ContainedViewLayoutTransition) {
|
||||
self.alpha = 0.0
|
||||
transition.updateAlpha(node: self, alpha: 1.0)
|
||||
}
|
||||
|
||||
open func animateIn(fromHeight: CGFloat, previousContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition) {
|
||||
}
|
||||
|
||||
open func animateOut(transition: ContainedViewLayoutTransition) {
|
||||
transition.updateAlpha(node: self, alpha: 0.0)
|
||||
}
|
||||
|
||||
open func animateOut(toHeight: CGFloat, nextContentNode: GalleryFooterContentNode, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
completion()
|
||||
}
|
||||
|
@ -32,6 +32,32 @@ public final class GalleryFooterNode: ASDisplayNode {
|
||||
self.currentOverlayContentNode?.setVisibilityAlpha(alpha)
|
||||
}
|
||||
|
||||
func animateIn(transition: ContainedViewLayoutTransition) {
|
||||
self.backgroundNode.alpha = 0.0
|
||||
transition.updateAlpha(node: self.backgroundNode, alpha: 1.0)
|
||||
|
||||
if let currentFooterContentNode = self.currentFooterContentNode {
|
||||
currentFooterContentNode.animateIn(transition: transition)
|
||||
}
|
||||
|
||||
if let currentOverlayContentNode = self.currentOverlayContentNode {
|
||||
currentOverlayContentNode.alpha = 0.0
|
||||
transition.updateAlpha(node: currentOverlayContentNode, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(transition: ContainedViewLayoutTransition) {
|
||||
transition.updateAlpha(node: self.backgroundNode, alpha: 0.0)
|
||||
|
||||
if let currentFooterContentNode = self.currentFooterContentNode {
|
||||
currentFooterContentNode.animateOut(transition: transition)
|
||||
}
|
||||
|
||||
if let currentOverlayContentNode = self.currentOverlayContentNode {
|
||||
transition.updateAlpha(node: currentOverlayContentNode, alpha: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, footerContentNode: GalleryFooterContentNode?, overlayContentNode: GalleryOverlayContentNode?, thumbnailPanelHeight: CGFloat, isHidden: Bool, transition: ContainedViewLayoutTransition) {
|
||||
self.currentLayout = (layout, navigationBarHeight, thumbnailPanelHeight, isHidden)
|
||||
let cleanInsets = layout.insets(options: [])
|
||||
|
@ -27,6 +27,7 @@ open class GalleryItemNode: ASDisplayNode {
|
||||
|
||||
public var toggleControlsVisibility: () -> Void = { }
|
||||
public var updateControlsVisibility: (Bool) -> Void = { _ in }
|
||||
public var controlsVisibility: () -> Bool = { return true }
|
||||
public var updateOrientation: (UIInterfaceOrientation) -> Void = { _ in }
|
||||
public var dismiss: () -> Void = { }
|
||||
public var beginCustomDismiss: (Bool) -> Void = { _ in }
|
||||
|
@ -1,8 +1,42 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
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 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 let insertCloneTransitionView: ((UIView) -> Void)?
|
||||
|
||||
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 protocol GalleryItemTransitionNode: AnyObject {
|
||||
func isAvailableForGalleryTransition() -> Bool
|
||||
func isAvailableForInstantPageTransition() -> Bool
|
||||
var decoration: UniversalVideoDecoration? { get }
|
||||
|
||||
func scrubberTransition() -> GalleryItemScrubberTransition?
|
||||
}
|
||||
|
@ -114,6 +114,7 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
|
||||
public var centralItemIndexOffsetUpdated: (([GalleryItem]?, Int, CGFloat)?) -> Void = { _ in }
|
||||
public var toggleControlsVisibility: () -> Void = { }
|
||||
public var updateControlsVisibility: (Bool) -> Void = { _ in }
|
||||
public var controlsVisibility: () -> Bool = { return true }
|
||||
public var updateOrientation: (UIInterfaceOrientation) -> Void = { _ in }
|
||||
public var dismiss: () -> Void = { }
|
||||
public var beginCustomDismiss: (Bool) -> Void = { _ in }
|
||||
@ -595,6 +596,7 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
|
||||
let node = self.items[index].node(synchronous: synchronous)
|
||||
node.toggleControlsVisibility = self.toggleControlsVisibility
|
||||
node.updateControlsVisibility = self.updateControlsVisibility
|
||||
node.controlsVisibility = self.controlsVisibility
|
||||
node.updateOrientation = self.updateOrientation
|
||||
node.dismiss = self.dismiss
|
||||
node.beginCustomDismiss = self.beginCustomDismiss
|
||||
|
@ -1404,17 +1404,29 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
||||
//TODO:wip-release
|
||||
/*self.footerContentNode.shareMediaParameters = { [weak self] in
|
||||
self.footerContentNode.shareMediaParameters = { [weak self] in
|
||||
guard let self, let playerStatusValue = self.playerStatusValue else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if playerStatusValue.duration >= 60.0 * 10.0 {
|
||||
return ShareControllerSubject.MediaParameters(startAtTimestamp: Int32(playerStatusValue.timestamp))
|
||||
var publicLinkPrefix: ShareControllerSubject.PublicLinkPrefix?
|
||||
if case let .message(message, _) = self.item?.contentInfo, message.id.namespace == Namespaces.Message.Cloud, let peer = message.peers[message.id.peerId] as? TelegramChannel, let username = peer.username {
|
||||
let visibleString = "t.me/\(username)/\(message.id.id)"
|
||||
publicLinkPrefix = ShareControllerSubject.PublicLinkPrefix(
|
||||
visibleString: visibleString,
|
||||
actualString: "https://\(visibleString)"
|
||||
)
|
||||
}
|
||||
|
||||
return ShareControllerSubject.MediaParameters(
|
||||
startAtTimestamp: Int32(playerStatusValue.timestamp),
|
||||
publicLinkPrefix: publicLinkPrefix
|
||||
)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.settingsBarButton.addTarget(self, action: #selector(self.settingsButtonPressed), forControlEvents: .touchUpInside)
|
||||
@ -2456,6 +2468,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
|
||||
if let node = node.0 as? OverlayMediaItemNode, self.context.sharedContext.mediaManager.hasOverlayVideoNode(node) {
|
||||
if let scrubberView = self.scrubberView {
|
||||
scrubberView.animateIn(from: nil, transition: .animated(duration: 0.25, curve: .spring))
|
||||
}
|
||||
|
||||
var transformedFrame = node.view.convert(node.view.bounds, to: videoNode.view)
|
||||
let transformedSuperFrame = node.view.convert(node.view.bounds, to: videoNode.view.superview)
|
||||
|
||||
@ -2471,6 +2487,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
|
||||
self.context.sharedContext.mediaManager.setOverlayVideoNode(nil)
|
||||
} else {
|
||||
if let scrubberView = self.scrubberView {
|
||||
let scrubberTransition = (node.0 as? GalleryItemTransitionNode)?.scrubberTransition()
|
||||
scrubberView.animateIn(from: scrubberTransition, transition: .animated(duration: 0.25, curve: .spring))
|
||||
}
|
||||
|
||||
var transformedFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view)
|
||||
var transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view.superview)
|
||||
var transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view)
|
||||
@ -2570,6 +2591,14 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
return
|
||||
}
|
||||
|
||||
if let scrubberView = self.scrubberView {
|
||||
var scrubberTransition = (node.0 as? GalleryItemTransitionNode)?.scrubberTransition()
|
||||
if !self.controlsVisibility() {
|
||||
scrubberTransition = nil
|
||||
}
|
||||
scrubberView.animateOut(to: scrubberTransition, transition: .animated(duration: 0.25, curve: .spring))
|
||||
}
|
||||
|
||||
let transformedFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view)
|
||||
var transformedSuperFrame = node.0.view.convert(node.0.view.bounds, to: videoNode.view.superview)
|
||||
let transformedSelfFrame = node.0.view.convert(node.0.view.bounds, to: self.view)
|
||||
|
@ -177,4 +177,8 @@ final class InstantPagePlayableVideoNode: ASDisplayNode, InstantPageNode, Galler
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func scrubberTransition() -> GalleryItemScrubberTransition? {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -1091,4 +1091,12 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func animateWidth(from: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
transition.animateTransformScale(layer: self.layer, from: CGPoint(x: from / self.bounds.width, y: 1.0))
|
||||
}
|
||||
|
||||
public func animateWidth(to: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
transition.updateTransformScale(node: self, scale: CGPoint(x: to / self.bounds.width, y: 1.0))
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
"//submodules/TelegramUI/Components/MessageInputPanelComponent",
|
||||
|
@ -132,6 +132,8 @@ public final class ShareStartAtTimestampNode: HighlightTrackingButtonNode {
|
||||
return self.checkNode.selected
|
||||
}
|
||||
|
||||
public var updated: (() -> Void)?
|
||||
|
||||
public init(titleText: String, titleTextColor: UIColor, checkNodeTheme: CheckNodeTheme) {
|
||||
self.titleText = titleText
|
||||
self.titleTextColor = titleTextColor
|
||||
@ -154,6 +156,7 @@ public final class ShareStartAtTimestampNode: HighlightTrackingButtonNode {
|
||||
|
||||
@objc private func pressed() {
|
||||
self.checkNode.setSelected(!self.checkNode.selected, animated: true)
|
||||
self.updated?()
|
||||
}
|
||||
|
||||
override public func layout() {
|
||||
|
@ -436,6 +436,8 @@ public final class ShareController: ViewController {
|
||||
|
||||
public var debugAction: (() -> Void)?
|
||||
|
||||
public var onMediaTimestampLinkCopied: ((Int32?) -> Void)?
|
||||
|
||||
public var parentNavigationController: NavigationController?
|
||||
|
||||
public convenience init(context: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, fromForeignApp: Bool = false, segmentedValues: [ShareControllerSegmentedValue]? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, forceTheme: PresentationTheme? = nil, forcedActionTitle: String? = nil, shareAsLink: Bool = false, collectibleItemInfo: TelegramCollectibleItemInfo? = nil) {
|
||||
@ -1210,6 +1212,12 @@ public final class ShareController: ViewController {
|
||||
return false
|
||||
}), in: .current)
|
||||
}
|
||||
self.controllerNode.onMediaTimestampLinkCopied = { [weak self] timestamp in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.onMediaTimestampLinkCopied?(timestamp)
|
||||
}
|
||||
self.controllerNode.debugAction = { [weak self] in
|
||||
self?.debugAction?()
|
||||
}
|
||||
|
@ -326,6 +326,8 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
private let fromPublicChannel: Bool
|
||||
private let segmentedValues: [ShareControllerSegmentedValue]?
|
||||
private let collectibleItemInfo: TelegramCollectibleItemInfo?
|
||||
private let mediaParameters: ShareControllerSubject.MediaParameters?
|
||||
|
||||
var selectedSegmentedIndex: Int = 0
|
||||
|
||||
private let defaultAction: ShareControllerAction?
|
||||
@ -365,6 +367,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
var enqueued: (([PeerId], [Int64]) -> Void)?
|
||||
var present: ((ViewController) -> Void)?
|
||||
var disabledPeerSelected: ((EnginePeer) -> Void)?
|
||||
var onMediaTimestampLinkCopied: ((Int32?) -> Void)?
|
||||
|
||||
let ready = Promise<Bool>()
|
||||
|
||||
@ -397,6 +400,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
self.fromPublicChannel = fromPublicChannel
|
||||
self.segmentedValues = segmentedValues
|
||||
self.collectibleItemInfo = collectibleItemInfo
|
||||
self.mediaParameters = mediaParameters
|
||||
|
||||
self.presetText = presetText
|
||||
|
||||
@ -471,7 +475,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
self.startAtTimestampNode = nil
|
||||
}
|
||||
|
||||
self.inputFieldNode = ShareInputFieldNode(theme: ShareInputFieldNodeTheme(presentationTheme: self.presentationData.theme), placeholder: self.presentationData.strings.ShareMenu_Comment)
|
||||
self.inputFieldNode = ShareInputFieldNode(theme: ShareInputFieldNodeTheme(presentationTheme: self.presentationData.theme), strings: self.presentationData.strings, placeholder: self.presentationData.strings.ShareMenu_Comment)
|
||||
self.inputFieldNode.text = presetText ?? ""
|
||||
self.inputFieldNode.preselectText()
|
||||
self.inputFieldNode.alpha = 0.0
|
||||
@ -491,6 +495,15 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
|
||||
self.isHidden = true
|
||||
|
||||
self.startAtTimestampNode?.updated = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let (layout, navigationBarHeight, _) = self.containerLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
self.actionButtonNode.shouldBegin = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
return !strongSelf.controllerInteraction!.selectedPeers.isEmpty
|
||||
@ -593,7 +606,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
}
|
||||
|
||||
if !openedTopicList {
|
||||
strongSelf.setActionNodesHidden(strongSelf.controllerInteraction!.selectedPeers.isEmpty && strongSelf.presetText == nil, inputField: true, actions: strongSelf.defaultAction == nil)
|
||||
strongSelf.setActionNodesHidden(strongSelf.controllerInteraction!.selectedPeers.isEmpty && strongSelf.presetText == nil && strongSelf.mediaParameters?.publicLinkPrefix == nil, inputField: true, actions: strongSelf.defaultAction == nil)
|
||||
|
||||
strongSelf.updateButton()
|
||||
|
||||
@ -686,10 +699,44 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
self.inputFieldNode.onInputCopyText = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let publicLinkPrefix = self.mediaParameters?.publicLinkPrefix {
|
||||
var timestampSuffix = ""
|
||||
var effectiveStartTimestamp: Int32?
|
||||
if let startAtTimestamp = self.mediaParameters?.startAtTimestamp, let startAtTimestampNode = self.startAtTimestampNode, startAtTimestampNode.value {
|
||||
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)"
|
||||
effectiveStartTimestamp = startAtTimestamp
|
||||
}
|
||||
let inputCopyText = "\(publicLinkPrefix.actualString)\(timestampSuffix)"
|
||||
UIPasteboard.general.string = inputCopyText
|
||||
self.onMediaTimestampLinkCopied?(effectiveStartTimestamp)
|
||||
}
|
||||
self.cancel?()
|
||||
}
|
||||
|
||||
self.updateButton()
|
||||
|
||||
if self.presetText != nil {
|
||||
if self.presetText != nil || self.mediaParameters?.publicLinkPrefix != nil {
|
||||
self.setActionNodesHidden(false, inputField: true, actions: true, animated: false)
|
||||
}
|
||||
}
|
||||
@ -971,7 +1018,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
if contentNode is ShareSearchContainerNode {
|
||||
self.setActionNodesHidden(true, inputField: true, actions: true)
|
||||
} else if !(contentNode is ShareLoadingContainer) {
|
||||
self.setActionNodesHidden(false, inputField: !self.controllerInteraction!.selectedPeers.isEmpty || self.presetText != nil, actions: true)
|
||||
self.setActionNodesHidden(false, inputField: !self.controllerInteraction!.selectedPeers.isEmpty || self.presetText != nil || self.mediaParameters?.publicLinkPrefix != nil, actions: true)
|
||||
}
|
||||
} else {
|
||||
if let contentNode = self.contentNode {
|
||||
@ -1021,7 +1068,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
var bottomGridInset: CGFloat = 0
|
||||
|
||||
var actionButtonHeight: CGFloat = 0
|
||||
if self.defaultAction != nil || !self.controllerInteraction!.selectedPeers.isEmpty || self.presetText != nil {
|
||||
if self.defaultAction != nil || !self.controllerInteraction!.selectedPeers.isEmpty || self.presetText != nil || self.mediaParameters?.publicLinkPrefix != nil {
|
||||
actionButtonHeight = buttonHeight
|
||||
bottomGridInset += actionButtonHeight
|
||||
}
|
||||
@ -1029,8 +1076,37 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
|
||||
bottomGridInset += buttonHeight
|
||||
}
|
||||
|
||||
let inputHeight = self.inputFieldNode.updateLayout(width: contentContainerFrame.size.width, transition: transition)
|
||||
var inputCopyText: String?
|
||||
if !self.controllerInteraction!.selectedPeers.isEmpty || self.presetText != nil {
|
||||
} else {
|
||||
if let publicLinkPrefix = self.mediaParameters?.publicLinkPrefix {
|
||||
var timestampSuffix = ""
|
||||
if let startAtTimestamp = self.mediaParameters?.startAtTimestamp, let startAtTimestampNode = self.startAtTimestampNode, startAtTimestampNode.value {
|
||||
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)"
|
||||
}
|
||||
inputCopyText = "\(publicLinkPrefix.visibleString)\(timestampSuffix)"
|
||||
}
|
||||
}
|
||||
|
||||
let inputHeight = self.inputFieldNode.updateLayout(width: contentContainerFrame.size.width, inputCopyText: inputCopyText, transition: transition)
|
||||
if !self.controllerInteraction!.selectedPeers.isEmpty || self.presetText != nil || self.mediaParameters?.publicLinkPrefix != nil {
|
||||
bottomGridInset += inputHeight
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,9 @@ import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramPresentationData
|
||||
import AppBundle
|
||||
import ComponentFlow
|
||||
import MultilineTextComponent
|
||||
import AnimatedTextComponent
|
||||
|
||||
private func generateClearIcon(color: UIColor) -> UIImage? {
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: color)
|
||||
@ -55,19 +58,154 @@ public extension ShareInputFieldNodeTheme {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ShareInputCopyComponent: Component {
|
||||
let theme: ShareInputFieldNodeTheme
|
||||
let strings: PresentationStrings
|
||||
let text: String
|
||||
let action: () -> Void
|
||||
|
||||
init(
|
||||
theme: ShareInputFieldNodeTheme,
|
||||
strings: PresentationStrings,
|
||||
text: String,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.text = text
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: ShareInputCopyComponent, rhs: ShareInputCopyComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
let text = ComponentView<Empty>()
|
||||
let button = ComponentView<Empty>()
|
||||
let textMask = UIImageView()
|
||||
|
||||
var component: ShareInputCopyComponent?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: ShareInputCopyComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let textChanged = self.component != nil && self.component?.text != component.text
|
||||
self.component = component
|
||||
|
||||
var textItems: [AnimatedTextComponent.Item] = []
|
||||
if let range = component.text.range(of: "?", options: .backwards) {
|
||||
textItems.append(AnimatedTextComponent.Item(id: 0, isUnbreakable: true, content: .text(String(component.text[component.text.startIndex ..< range.lowerBound]))))
|
||||
textItems.append(AnimatedTextComponent.Item(id: 1, isUnbreakable: true, content: .text(String(component.text[range.lowerBound...]))))
|
||||
} else {
|
||||
textItems.append(AnimatedTextComponent.Item(id: 0, isUnbreakable: true, content: .text(component.text)))
|
||||
}
|
||||
|
||||
let sideInset: CGFloat = 12.0
|
||||
let textSize = self.text.update(
|
||||
transition: textChanged ? .spring(duration: 0.4) : .immediate,
|
||||
component: AnyComponent(AnimatedTextComponent(
|
||||
font: Font.regular(17.0),
|
||||
color: component.theme.textColor,
|
||||
items: textItems,
|
||||
animateScale: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
|
||||
)
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: floor((availableSize.height - textSize.height) * 0.5)), size: textSize)
|
||||
if let textView = self.text.view {
|
||||
if textView.superview == nil {
|
||||
self.addSubview(textView)
|
||||
textView.mask = self.textMask
|
||||
}
|
||||
textView.frame = textFrame
|
||||
}
|
||||
|
||||
let buttonSize = self.button.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.strings.Conversation_LinkDialogCopy, font: Font.regular(17.0), textColor: component.theme.accentColor))
|
||||
)),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.component?.action()
|
||||
}
|
||||
).minSize(CGSize(width: 0.0, height: availableSize.height))),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - 40.0, height: 1000.0)
|
||||
)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - buttonSize.width, y: floor((availableSize.height - buttonSize.height) * 0.5)), size: buttonSize)
|
||||
if let buttonView = self.button.view {
|
||||
if buttonView.superview == nil {
|
||||
self.addSubview(buttonView)
|
||||
}
|
||||
buttonView.frame = buttonFrame
|
||||
}
|
||||
|
||||
if self.textMask.image == nil {
|
||||
let gradientWidth: CGFloat = 26.0
|
||||
self.textMask.image = generateGradientImage(size: CGSize(width: gradientWidth, height: 8.0), colors: [
|
||||
UIColor(white: 1.0, alpha: 1.0),
|
||||
UIColor(white: 1.0, alpha: 1.0),
|
||||
UIColor(white: 1.0, alpha: 0.0)
|
||||
], locations: [
|
||||
0.0,
|
||||
1.0 / gradientWidth,
|
||||
1.0
|
||||
], direction: .horizontal)?.resizableImage(withCapInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: gradientWidth - 1.0), resizingMode: .stretch)
|
||||
self.textMask.frame = CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, buttonFrame.minX - 4.0 - textFrame.minX), height: textFrame.height))
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate {
|
||||
private let theme: ShareInputFieldNodeTheme
|
||||
private let strings: PresentationStrings
|
||||
private let backgroundNode: ASImageNode
|
||||
private let textInputNode: EditableTextNode
|
||||
private let placeholderNode: ASTextNode
|
||||
private let clearButton: HighlightableButtonNode
|
||||
|
||||
private var copyView: ComponentView<Empty>?
|
||||
|
||||
public var updateHeight: (() -> Void)?
|
||||
public var updateText: ((String) -> Void)?
|
||||
|
||||
private let backgroundInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 1.0, right: 16.0)
|
||||
private let inputInsets = UIEdgeInsets(top: 10.0, left: 8.0, bottom: 10.0, right: 22.0)
|
||||
private let accessoryButtonsWidth: CGFloat = 10.0
|
||||
private var inputCopyText: String?
|
||||
public var onInputCopyText: (() -> Void)?
|
||||
|
||||
private var selectTextOnce: Bool = false
|
||||
|
||||
@ -77,7 +215,7 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
|
||||
}
|
||||
set {
|
||||
self.textInputNode.attributedText = NSAttributedString(string: newValue, font: Font.regular(17.0), textColor: self.theme.textColor)
|
||||
self.placeholderNode.isHidden = !newValue.isEmpty
|
||||
self.placeholderNode.isHidden = !newValue.isEmpty || self.inputCopyText != nil
|
||||
self.clearButton.isHidden = newValue.isEmpty
|
||||
}
|
||||
}
|
||||
@ -88,8 +226,9 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
|
||||
}
|
||||
}
|
||||
|
||||
public init(theme: ShareInputFieldNodeTheme, placeholder: String) {
|
||||
public init(theme: ShareInputFieldNodeTheme, strings: PresentationStrings, placeholder: String) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
@ -136,10 +275,11 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
|
||||
self.selectTextOnce = true
|
||||
}
|
||||
|
||||
public func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
public func updateLayout(width: CGFloat, inputCopyText: String?, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let backgroundInsets = self.backgroundInsets
|
||||
let inputInsets = self.inputInsets
|
||||
let accessoryButtonsWidth = self.accessoryButtonsWidth
|
||||
self.inputCopyText = inputCopyText
|
||||
|
||||
let textFieldHeight = self.calculateTextFieldMetrics(width: width)
|
||||
let panelHeight = textFieldHeight + backgroundInsets.top + backgroundInsets.bottom
|
||||
@ -156,6 +296,43 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
|
||||
|
||||
transition.updateFrame(node: self.textInputNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + inputInsets.left, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.size.width - inputInsets.left - inputInsets.right - accessoryButtonsWidth, height: backgroundFrame.size.height)))
|
||||
|
||||
self.textInputNode.isUserInteractionEnabled = inputCopyText == nil
|
||||
self.textInputNode.isHidden = inputCopyText != nil
|
||||
self.placeholderNode.isHidden = !(self.textInputNode.textView.text ?? "").isEmpty || self.inputCopyText != nil
|
||||
|
||||
if let inputCopyText {
|
||||
let copyView: ComponentView<Empty>
|
||||
if let current = self.copyView {
|
||||
copyView = current
|
||||
} else {
|
||||
copyView = ComponentView()
|
||||
self.copyView = copyView
|
||||
}
|
||||
let copyViewSize = copyView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(ShareInputCopyComponent(
|
||||
theme: self.theme,
|
||||
strings: self.strings,
|
||||
text: inputCopyText,
|
||||
action: {
|
||||
self.onInputCopyText?()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: backgroundFrame.size
|
||||
)
|
||||
let copyViewFrame = CGRect(origin: backgroundFrame.origin, size: copyViewSize)
|
||||
if let copyComponentView = copyView.view {
|
||||
if copyComponentView.superview == nil {
|
||||
self.view.addSubview(copyComponentView)
|
||||
}
|
||||
copyComponentView.frame = copyViewFrame
|
||||
}
|
||||
} else if let copyView = self.copyView {
|
||||
self.copyView = nil
|
||||
copyView.view?.removeFromSuperview()
|
||||
}
|
||||
|
||||
return panelHeight
|
||||
}
|
||||
|
||||
@ -170,7 +347,7 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
|
||||
@objc public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditableTextNode) {
|
||||
self.updateTextNodeText(animated: true)
|
||||
self.updateText?(editableTextNode.attributedText?.string ?? "")
|
||||
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty
|
||||
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty || self.inputCopyText != nil
|
||||
}
|
||||
|
||||
public func editableTextNodeDidBeginEditing(_ editableTextNode: ASEditableTextNode) {
|
||||
@ -185,7 +362,7 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
|
||||
}
|
||||
|
||||
public func editableTextNodeDidFinishEditing(_ editableTextNode: ASEditableTextNode) {
|
||||
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty
|
||||
self.placeholderNode.isHidden = !(editableTextNode.textView.text ?? "").isEmpty || self.inputCopyText != nil
|
||||
self.clearButton.isHidden = true
|
||||
}
|
||||
|
||||
@ -194,10 +371,14 @@ public final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegat
|
||||
let inputInsets = self.inputInsets
|
||||
let accessoryButtonsWidth = self.accessoryButtonsWidth
|
||||
|
||||
if self.inputCopyText != nil {
|
||||
return 41.0
|
||||
} else {
|
||||
let unboundTextFieldHeight = max(33.0, ceil(self.textInputNode.measure(CGSize(width: width - backgroundInsets.left - backgroundInsets.right - inputInsets.left - inputInsets.right - accessoryButtonsWidth, height: CGFloat.greatestFiniteMagnitude)).height))
|
||||
|
||||
return min(61.0, max(41.0, unboundTextFieldHeight))
|
||||
}
|
||||
}
|
||||
|
||||
private func updateTextNodeText(animated: Bool) {
|
||||
let backgroundInsets = self.backgroundInsets
|
||||
|
@ -26,17 +26,20 @@ public final class AnimatedTextComponent: Component {
|
||||
public let color: UIColor
|
||||
public let items: [Item]
|
||||
public let noDelay: Bool
|
||||
public let animateScale: Bool
|
||||
|
||||
public init(
|
||||
font: UIFont,
|
||||
color: UIColor,
|
||||
items: [Item],
|
||||
noDelay: Bool = false
|
||||
noDelay: Bool = false,
|
||||
animateScale: Bool = true
|
||||
) {
|
||||
self.font = font
|
||||
self.color = color
|
||||
self.items = items
|
||||
self.noDelay = noDelay
|
||||
self.animateScale = animateScale
|
||||
}
|
||||
|
||||
public static func ==(lhs: AnimatedTextComponent, rhs: AnimatedTextComponent) -> Bool {
|
||||
@ -52,6 +55,9 @@ public final class AnimatedTextComponent: Component {
|
||||
if lhs.noDelay != rhs.noDelay {
|
||||
return false
|
||||
}
|
||||
if lhs.animateScale != rhs.animateScale {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -172,7 +178,9 @@ public final class AnimatedTextComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
if component.animateScale {
|
||||
characterComponentView.layer.animateScale(from: 0.001, to: 1.0, duration: 0.4, delay: delayNorm * delayWidth, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
characterComponentView.layer.animatePosition(from: CGPoint(x: 0.0, y: characterSize.height * 0.5), to: CGPoint(), duration: 0.4, delay: delayNorm * delayWidth, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
characterComponentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.18, delay: delayNorm * delayWidth)
|
||||
}
|
||||
@ -202,7 +210,9 @@ public final class AnimatedTextComponent: Component {
|
||||
outFirstDelayWidth = characterComponentView.frame.minX
|
||||
}
|
||||
|
||||
if component.animateScale {
|
||||
outScaleTransition.setScale(view: characterComponentView, scale: 0.01, delay: delayNorm * delayWidth)
|
||||
}
|
||||
outScaleTransition.setPosition(view: characterComponentView, position: CGPoint(x: characterComponentView.center.x, y: characterComponentView.center.y - characterComponentView.bounds.height * 0.4), delay: delayNorm * delayWidth)
|
||||
outAlphaTransition.setAlpha(view: characterComponentView, alpha: 0.0, delay: delayNorm * delayWidth, completion: { [weak characterComponentView] _ in
|
||||
characterComponentView?.removeFromSuperview()
|
||||
|
@ -626,13 +626,6 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
transition.updateAlpha(node: statusNode, alpha: 1.0 - factor)
|
||||
}
|
||||
}
|
||||
|
||||
self.imageNode.imageUpdated = { [weak self] image in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.timestampMaskView?.image = image
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -794,6 +787,66 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
}
|
||||
}
|
||||
|
||||
private struct MaskImageCornerKey: Hashable {
|
||||
var bottomLeft: CGFloat
|
||||
var bottomRight: CGFloat
|
||||
var leftInset: CGFloat
|
||||
var rightInset: CGFloat
|
||||
|
||||
init(bottomLeft: CGFloat, bottomRight: CGFloat, leftInset: CGFloat, rightInset: CGFloat) {
|
||||
self.bottomLeft = bottomLeft
|
||||
self.bottomRight = bottomRight
|
||||
self.leftInset = leftInset
|
||||
self.rightInset = rightInset
|
||||
}
|
||||
}
|
||||
private static var timestampMaskImageCache: [MaskImageCornerKey: UIImage] = [:]
|
||||
|
||||
private func generateTimestampMaskImage(corners: ImageCorners) -> UIImage? {
|
||||
var insets = corners.extendedEdges
|
||||
insets.top = 0.0
|
||||
insets.bottom = 0.0
|
||||
|
||||
let cacheKey = MaskImageCornerKey(bottomLeft: corners.bottomLeft.radius, bottomRight: corners.bottomRight.radius, leftInset: insets.left, rightInset: insets.right)
|
||||
if let image = ChatMessageInteractiveMediaNode.timestampMaskImageCache[cacheKey] {
|
||||
return image
|
||||
}
|
||||
|
||||
let imageSize = CGSize(width: corners.bottomLeft.radius + corners.bottomRight.radius + insets.left + insets.right + 1.0, height: 1.0 + max(corners.bottomLeft.radius, corners.bottomRight.radius))
|
||||
|
||||
guard let context = DrawingContext(size: imageSize, clear: true) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
context.withContext { c in
|
||||
c.setFillColor(UIColor.white.cgColor)
|
||||
c.move(to: CGPoint(x: insets.left, y: insets.top))
|
||||
c.addLine(to: CGPoint(x: insets.left, y: imageSize.height - insets.bottom - corners.bottomLeft.radius))
|
||||
c.addArc(tangent1End: CGPoint(x: insets.left, y: imageSize.height - insets.bottom), tangent2End: CGPoint(x: insets.left + corners.bottomLeft.radius, y: imageSize.height - insets.bottom), radius: corners.bottomLeft.radius)
|
||||
c.addLine(to: CGPoint(x: imageSize.width - insets.right - corners.bottomRight.radius, y: imageSize.height - insets.bottom))
|
||||
c.addArc(tangent1End: CGPoint(x: imageSize.width - insets.right, y: imageSize.height - insets.bottom), tangent2End: CGPoint(x: imageSize.width - insets.right, y: imageSize.height - insets.bottom - corners.bottomRight.radius), radius: corners.bottomRight.radius)
|
||||
c.addLine(to: CGPoint(x: imageSize.width - insets.right, y: insets.top))
|
||||
c.closePath()
|
||||
c.fillPath()
|
||||
}
|
||||
|
||||
let image = context.generateImage()?.resizableImage(
|
||||
withCapInsets: UIEdgeInsets(
|
||||
top: 0,
|
||||
left: corners.bottomLeft.radius + insets.left,
|
||||
bottom: imageSize.height - 1.0,
|
||||
right: corners.bottomRight.radius + insets.right
|
||||
),
|
||||
resizingMode: .stretch
|
||||
)
|
||||
|
||||
if let image {
|
||||
ChatMessageInteractiveMediaNode.timestampMaskImageCache[cacheKey] = image
|
||||
}
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (_ context: AccountContext, _ presentationData: ChatPresentationData, _ dateTimeFormat: PresentationDateTimeFormat, _ message: Message, _ associatedData: ChatMessageItemAssociatedData, _ attributes: ChatMessageEntryAttributes, _ media: Media, _ mediaIndex: Int?, _ dateAndStatus: ChatMessageDateAndStatus?, _ automaticDownload: InteractiveMediaNodeAutodownloadMode, _ peerType: MediaAutoDownloadPeerType, _ peerId: EnginePeer.Id?, _ sizeCalculation: InteractiveMediaNodeSizeCalculation, _ layoutConstants: ChatMessageItemLayoutConstants, _ contentMode: InteractiveMediaNodeContentMode, _ presentationContext: ChatPresentationContext) -> (CGSize, CGFloat, (CGSize, Bool, Bool, ImageCorners) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool) -> Void))) {
|
||||
let currentMessage = self.message
|
||||
let currentMedia = self.media
|
||||
@ -1771,6 +1824,9 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
strongSelf.pinchContainerNode.update(size: imageFrame.size, transition: .immediate)
|
||||
strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: imageFrame.size)
|
||||
}
|
||||
if strongSelf.currentImageArguments?.corners != arguments.corners, let timestampMaskView = strongSelf.timestampMaskView {
|
||||
timestampMaskView.image = strongSelf.generateTimestampMaskImage(corners: arguments.corners)
|
||||
}
|
||||
strongSelf.currentImageArguments = arguments
|
||||
imageApply()
|
||||
|
||||
@ -1950,8 +2006,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:wip-release
|
||||
/*var videoTimestamp: Int32?
|
||||
var videoTimestamp: Int32?
|
||||
var storedVideoTimestamp: Int32?
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? ForwardVideoTimestampAttribute {
|
||||
@ -1985,7 +2040,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
strongSelf.timestampMaskView = timestampMaskView
|
||||
timestampContainerView.mask = timestampMaskView
|
||||
|
||||
timestampMaskView.image = strongSelf.imageNode.image
|
||||
timestampMaskView.image = strongSelf.generateTimestampMaskImage(corners: arguments.corners)
|
||||
}
|
||||
|
||||
let videoTimestampBackgroundLayer: SimpleLayer
|
||||
@ -2038,7 +2093,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
||||
strongSelf.videoTimestampForegroundLayer = nil
|
||||
videoTimestampForegroundLayer.removeFromSuperlayer()
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
if let animatedStickerNode = strongSelf.animatedStickerNode {
|
||||
animatedStickerNode.frame = imageFrame
|
||||
@ -3037,6 +3092,72 @@ 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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
}
|
||||
|
||||
return GalleryItemScrubberTransition(
|
||||
view: timestampContainerView,
|
||||
makeView: { [weak timestampContainerView, weak timestampMaskView, weak videoTimestampBackgroundLayer, weak videoTimestampForegroundLayer] in
|
||||
return TimestampContainerTransitionView(timestampContainerView: timestampContainerView, timestampMaskView: timestampMaskView, videoTimestampBackgroundLayer: videoTimestampBackgroundLayer, videoTimestampForegroundLayer: videoTimestampForegroundLayer)
|
||||
},
|
||||
updateView: { view, state, transition in
|
||||
if let view = view as? TimestampContainerTransitionView {
|
||||
view.update(state: state, transition: transition)
|
||||
}
|
||||
},
|
||||
insertCloneTransitionView: nil
|
||||
)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func playMediaWithSound() -> (action: (Double?) -> Void, soundEnabled: Bool, isVideoMessage: Bool, isUnread: Bool, badgeNode: ASDisplayNode?)? {
|
||||
var isAnimated = false
|
||||
if let file = self.media as? TelegramMediaFile {
|
||||
|
@ -176,7 +176,7 @@ public func presentPeerReportOptions(
|
||||
var message = ""
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(ReportPeerHeaderActionSheetItem(context: context, text: presentationData.strings.Report_AdditionalDetailsText))
|
||||
items.append(ReportPeerDetailsActionSheetItem(context: context, theme: presentationData.theme, placeholderText: presentationData.strings.Report_AdditionalDetailsPlaceholder, textUpdated: { text in
|
||||
items.append(ReportPeerDetailsActionSheetItem(context: context, theme: presentationData.theme, strings: presentationData.strings, placeholderText: presentationData.strings.Report_AdditionalDetailsPlaceholder, textUpdated: { text in
|
||||
message = text
|
||||
}))
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Report_Report, color: .accent, font: .bold, enabled: true, action: {
|
||||
@ -309,7 +309,7 @@ public func peerReportOptionsController(context: AccountContext, subject: PeerRe
|
||||
var message = ""
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(ReportPeerHeaderActionSheetItem(context: context, text: presentationData.strings.Report_AdditionalDetailsText))
|
||||
items.append(ReportPeerDetailsActionSheetItem(context: context, theme: presentationData.theme, placeholderText: presentationData.strings.Report_AdditionalDetailsPlaceholder, textUpdated: { text in
|
||||
items.append(ReportPeerDetailsActionSheetItem(context: context, theme: presentationData.theme, strings: presentationData.strings, placeholderText: presentationData.strings.Report_AdditionalDetailsPlaceholder, textUpdated: { text in
|
||||
message = text
|
||||
}))
|
||||
items.append(ActionSheetButtonItem(title: presentationData.strings.Report_Report, color: .accent, font: .bold, enabled: true, action: {
|
||||
|
@ -11,18 +11,20 @@ import AppBundle
|
||||
public final class ReportPeerDetailsActionSheetItem: ActionSheetItem {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let placeholderText: String
|
||||
let textUpdated: (String) -> Void
|
||||
|
||||
public init(context: AccountContext, theme: PresentationTheme, placeholderText: String, textUpdated: @escaping (String) -> Void) {
|
||||
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, placeholderText: String, textUpdated: @escaping (String) -> Void) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.placeholderText = placeholderText
|
||||
self.textUpdated = textUpdated
|
||||
}
|
||||
|
||||
public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
||||
return ReportPeerDetailsActionSheetItemNode(theme: theme, presentationTheme: self.theme, context: self.context, placeholderText: self.placeholderText, textUpdated: self.textUpdated)
|
||||
return ReportPeerDetailsActionSheetItemNode(theme: theme, presentationTheme: self.theme, strings: self.strings, context: self.context, placeholderText: self.placeholderText, textUpdated: self.textUpdated)
|
||||
}
|
||||
|
||||
public func updateNode(_ node: ActionSheetItemNode) {
|
||||
@ -36,10 +38,10 @@ private final class ReportPeerDetailsActionSheetItemNode: ActionSheetItemNode {
|
||||
|
||||
private let accessibilityArea: AccessibilityAreaNode
|
||||
|
||||
init(theme: ActionSheetControllerTheme, presentationTheme: PresentationTheme, context: AccountContext, placeholderText: String, textUpdated: @escaping (String) -> Void) {
|
||||
init(theme: ActionSheetControllerTheme, presentationTheme: PresentationTheme, strings: PresentationStrings, context: AccountContext, placeholderText: String, textUpdated: @escaping (String) -> Void) {
|
||||
self.theme = theme
|
||||
|
||||
self.inputFieldNode = ShareInputFieldNode(theme: ShareInputFieldNodeTheme(presentationTheme: presentationTheme), placeholder: placeholderText)
|
||||
self.inputFieldNode = ShareInputFieldNode(theme: ShareInputFieldNodeTheme(presentationTheme: presentationTheme), strings: strings, placeholder: placeholderText)
|
||||
|
||||
self.accessibilityArea = AccessibilityAreaNode()
|
||||
|
||||
@ -58,7 +60,7 @@ private final class ReportPeerDetailsActionSheetItemNode: ActionSheetItemNode {
|
||||
}
|
||||
|
||||
public override func updateLayout(constrainedSize: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let inputHeight = self.inputFieldNode.updateLayout(width: constrainedSize.width, transition: .immediate)
|
||||
let inputHeight = self.inputFieldNode.updateLayout(width: constrainedSize.width, inputCopyText: nil, transition: .immediate)
|
||||
self.inputFieldNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: constrainedSize.width, height: inputHeight))
|
||||
|
||||
let size = CGSize(width: constrainedSize.width, height: inputHeight)
|
||||
|
Loading…
x
Reference in New Issue
Block a user