mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
GIF fixes
This commit is contained in:
parent
5ecbcc0f19
commit
83952f399c
@ -1305,14 +1305,14 @@ public func gifPaneVideoThumbnail(account: Account, videoReference: FileMediaRef
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func mediaGridMessageVideo(postbox: Postbox, videoReference: FileMediaReference, onlyFullSize: Bool = false, synchronousLoad: Bool = false, autoFetchFullSizeThumbnail: Bool = false, overlayColor: UIColor? = nil) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
public func mediaGridMessageVideo(postbox: Postbox, videoReference: FileMediaReference, onlyFullSize: Bool = false, synchronousLoad: Bool = false, autoFetchFullSizeThumbnail: Bool = false, overlayColor: UIColor? = nil, nilForEmptyResult: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||||
return internalMediaGridMessageVideo(postbox: postbox, videoReference: videoReference, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: autoFetchFullSizeThumbnail, overlayColor: overlayColor)
|
return internalMediaGridMessageVideo(postbox: postbox, videoReference: videoReference, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: autoFetchFullSizeThumbnail, overlayColor: overlayColor, nilForEmptyResult: nilForEmptyResult)
|
||||||
|> map {
|
|> map {
|
||||||
return $0.1
|
return $0.1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func internalMediaGridMessageVideo(postbox: Postbox, videoReference: FileMediaReference, imageReference: ImageMediaReference? = nil, onlyFullSize: Bool = false, synchronousLoad: Bool = false, autoFetchFullSizeThumbnail: Bool = false, overlayColor: UIColor? = nil) -> Signal<(() -> CGSize?, (TransformImageArguments) -> DrawingContext?), NoError> {
|
public func internalMediaGridMessageVideo(postbox: Postbox, videoReference: FileMediaReference, imageReference: ImageMediaReference? = nil, onlyFullSize: Bool = false, synchronousLoad: Bool = false, autoFetchFullSizeThumbnail: Bool = false, overlayColor: UIColor? = nil, nilForEmptyResult: Bool = false) -> Signal<(() -> CGSize?, (TransformImageArguments) -> DrawingContext?), NoError> {
|
||||||
let signal: Signal<Tuple3<Data?, Tuple2<Data, String>?, Bool>, NoError>
|
let signal: Signal<Tuple3<Data?, Tuple2<Data, String>?, Bool>, NoError>
|
||||||
if let imageReference = imageReference {
|
if let imageReference = imageReference {
|
||||||
signal = chatMessagePhotoDatas(postbox: postbox, photoReference: imageReference, tryAdditionalRepresentations: true, synchronousLoad: synchronousLoad)
|
signal = chatMessagePhotoDatas(postbox: postbox, photoReference: imageReference, tryAdditionalRepresentations: true, synchronousLoad: synchronousLoad)
|
||||||
@ -1346,6 +1346,12 @@ public func internalMediaGridMessageVideo(postbox: Postbox, videoReference: File
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, { arguments in
|
}, { arguments in
|
||||||
|
if nilForEmptyResult {
|
||||||
|
if thumbnailData == nil && fullSizeData == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let context = DrawingContext(size: arguments.drawingSize, clear: true)
|
let context = DrawingContext(size: arguments.drawingSize, clear: true)
|
||||||
|
|
||||||
let drawingRect = arguments.drawingRect
|
let drawingRect = arguments.drawingRect
|
||||||
|
@ -131,7 +131,7 @@ private final class MediaBoxFileMap {
|
|||||||
self.progress = nil
|
self.progress = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func contains(_ range: Range<Int32>) -> Bool {
|
fileprivate func contains(_ range: Range<Int32>) -> Range<Int32>? {
|
||||||
let maxValue: Int
|
let maxValue: Int
|
||||||
if let truncationSize = self.truncationSize {
|
if let truncationSize = self.truncationSize {
|
||||||
maxValue = Int(truncationSize)
|
maxValue = Int(truncationSize)
|
||||||
@ -139,7 +139,11 @@ private final class MediaBoxFileMap {
|
|||||||
maxValue = Int.max
|
maxValue = Int.max
|
||||||
}
|
}
|
||||||
let intRange: Range<Int> = Int(range.lowerBound) ..< min(maxValue, Int(range.upperBound))
|
let intRange: Range<Int> = Int(range.lowerBound) ..< min(maxValue, Int(range.upperBound))
|
||||||
return self.ranges.contains(integersIn: intRange)
|
if self.ranges.contains(integersIn: intRange) {
|
||||||
|
return Int32(intRange.lowerBound) ..< Int32(intRange.upperBound)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,7 +389,7 @@ final class MediaBoxPartialFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var isCompleted = false
|
var isCompleted = false
|
||||||
if let truncationSize = self.fileMap.truncationSize, self.fileMap.contains(0 ..< truncationSize) {
|
if let truncationSize = self.fileMap.truncationSize, let _ = self.fileMap.contains(0 ..< truncationSize) {
|
||||||
isCompleted = true
|
isCompleted = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -432,9 +436,9 @@ final class MediaBoxPartialFile {
|
|||||||
func read(range: Range<Int32>) -> Data? {
|
func read(range: Range<Int32>) -> Data? {
|
||||||
assert(self.queue.isCurrent())
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
if self.fileMap.contains(range) {
|
if let actualRange = self.fileMap.contains(range) {
|
||||||
self.fd.seek(position: Int64(range.lowerBound))
|
self.fd.seek(position: Int64(actualRange.lowerBound))
|
||||||
var data = Data(count: range.count)
|
var data = Data(count: actualRange.count)
|
||||||
let dataCount = data.count
|
let dataCount = data.count
|
||||||
let readBytes = data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Int in
|
let readBytes = data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Int in
|
||||||
return self.fd.read(bytes, dataCount)
|
return self.fd.read(bytes, dataCount)
|
||||||
@ -452,8 +456,8 @@ final class MediaBoxPartialFile {
|
|||||||
func data(range: Range<Int32>, waitUntilAfterInitialFetch: Bool, next: @escaping (MediaResourceData) -> Void) -> Disposable {
|
func data(range: Range<Int32>, waitUntilAfterInitialFetch: Bool, next: @escaping (MediaResourceData) -> Void) -> Disposable {
|
||||||
assert(self.queue.isCurrent())
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
if self.fileMap.contains(range) {
|
if let actualRange = self.fileMap.contains(range) {
|
||||||
next(MediaResourceData(path: self.path, offset: Int(range.lowerBound), size: range.count, complete: true))
|
next(MediaResourceData(path: self.path, offset: Int(actualRange.lowerBound), size: actualRange.count, complete: true))
|
||||||
return EmptyDisposable
|
return EmptyDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,7 +485,7 @@ final class MediaBoxPartialFile {
|
|||||||
func fetched(range: Range<Int32>, priority: MediaBoxFetchPriority, fetch: @escaping (Signal<[(Range<Int>, MediaBoxFetchPriority)], NoError>) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>, error: @escaping (MediaResourceDataFetchError) -> Void, completed: @escaping () -> Void) -> Disposable {
|
func fetched(range: Range<Int32>, priority: MediaBoxFetchPriority, fetch: @escaping (Signal<[(Range<Int>, MediaBoxFetchPriority)], NoError>) -> Signal<MediaResourceDataFetchResult, MediaResourceDataFetchError>, error: @escaping (MediaResourceDataFetchError) -> Void, completed: @escaping () -> Void) -> Disposable {
|
||||||
assert(self.queue.isCurrent())
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
if self.fileMap.contains(range) {
|
if let _ = self.fileMap.contains(range) {
|
||||||
completed()
|
completed()
|
||||||
return EmptyDisposable
|
return EmptyDisposable
|
||||||
}
|
}
|
||||||
@ -559,7 +563,7 @@ final class MediaBoxPartialFile {
|
|||||||
assert(self.queue.isCurrent())
|
assert(self.queue.isCurrent())
|
||||||
|
|
||||||
next(self.fileMap.ranges)
|
next(self.fileMap.ranges)
|
||||||
if let truncationSize = self.fileMap.truncationSize, self.fileMap.contains(0 ..< truncationSize) {
|
if let truncationSize = self.fileMap.truncationSize, let _ = self.fileMap.contains(0 ..< truncationSize) {
|
||||||
completed()
|
completed()
|
||||||
return EmptyDisposable
|
return EmptyDisposable
|
||||||
}
|
}
|
||||||
@ -676,8 +680,8 @@ final class MediaBoxPartialFile {
|
|||||||
if request.waitingUntilAfterInitialFetch {
|
if request.waitingUntilAfterInitialFetch {
|
||||||
request.waitingUntilAfterInitialFetch = false
|
request.waitingUntilAfterInitialFetch = false
|
||||||
|
|
||||||
if strongSelf.fileMap.contains(request.range) {
|
if let actualRange = strongSelf.fileMap.contains(request.range) {
|
||||||
request.completion(MediaResourceData(path: strongSelf.path, offset: Int(request.range.lowerBound), size: request.range.count, complete: true))
|
request.completion(MediaResourceData(path: strongSelf.path, offset: Int(actualRange.lowerBound), size: actualRange.count, complete: true))
|
||||||
} else {
|
} else {
|
||||||
request.completion(MediaResourceData(path: strongSelf.path, offset: Int(request.range.lowerBound), size: 0, complete: false))
|
request.completion(MediaResourceData(path: strongSelf.path, offset: Int(request.range.lowerBound), size: 0, complete: false))
|
||||||
}
|
}
|
||||||
|
@ -123,6 +123,7 @@ public final class ShimmerEffectNode: ASDisplayNode {
|
|||||||
case circle(CGRect)
|
case circle(CGRect)
|
||||||
case roundedRectLine(startPoint: CGPoint, width: CGFloat, diameter: CGFloat)
|
case roundedRectLine(startPoint: CGPoint, width: CGFloat, diameter: CGFloat)
|
||||||
case roundedRect(rect: CGRect, cornerRadius: CGFloat)
|
case roundedRect(rect: CGRect, cornerRadius: CGFloat)
|
||||||
|
case rect(rect: CGRect)
|
||||||
}
|
}
|
||||||
|
|
||||||
private let backgroundNode: ASDisplayNode
|
private let backgroundNode: ASDisplayNode
|
||||||
@ -189,6 +190,8 @@ public final class ShimmerEffectNode: ASDisplayNode {
|
|||||||
UIGraphicsPushContext(context)
|
UIGraphicsPushContext(context)
|
||||||
path.fill()
|
path.fill()
|
||||||
UIGraphicsPopContext()
|
UIGraphicsPopContext()
|
||||||
|
case let .rect(rect):
|
||||||
|
context.fill(rect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -334,12 +334,18 @@ public func chatMessageSticker(account: Account, file: TelegramMediaFile, small:
|
|||||||
return chatMessageSticker(postbox: account.postbox, file: file, small: small, fetched: fetched, onlyFullSize: onlyFullSize, thumbnail: thumbnail, synchronousLoad: synchronousLoad)
|
return chatMessageSticker(postbox: account.postbox, file: file, small: small, fetched: fetched, onlyFullSize: onlyFullSize, thumbnail: thumbnail, synchronousLoad: synchronousLoad)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func chatMessageStickerPackThumbnail(postbox: Postbox, resource: MediaResource, animated: Bool = false, synchronousLoad: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
public func chatMessageStickerPackThumbnail(postbox: Postbox, resource: MediaResource, animated: Bool = false, synchronousLoad: Bool = false, nilIfEmpty: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||||
let signal = chatMessageStickerPackThumbnailData(postbox: postbox, resource: resource, animated: animated, synchronousLoad: synchronousLoad)
|
let signal = chatMessageStickerPackThumbnailData(postbox: postbox, resource: resource, animated: animated, synchronousLoad: synchronousLoad)
|
||||||
|
|
||||||
return signal
|
return signal
|
||||||
|> map { fullSizeData in
|
|> map { fullSizeData in
|
||||||
return { arguments in
|
return { arguments in
|
||||||
|
if nilIfEmpty {
|
||||||
|
if fullSizeData == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil)
|
let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: arguments.emptyColor == nil)
|
||||||
|
|
||||||
let drawingRect = arguments.drawingRect
|
let drawingRect = arguments.drawingRect
|
||||||
|
@ -111,7 +111,7 @@ public func requestChatContextResults(account: Account, botId: PeerId, peerId: P
|
|||||||
}
|
}
|
||||||
|
|
||||||
return account.postbox.transaction { transaction -> ChatContextResultCollection? in
|
return account.postbox.transaction { transaction -> ChatContextResultCollection? in
|
||||||
if result.cacheTimeout > 10 && offset.isEmpty {
|
if result.cacheTimeout > 10 {
|
||||||
if let resultData = try? JSONEncoder().encode(result) {
|
if let resultData = try? JSONEncoder().encode(result) {
|
||||||
let requestData = RequestData(version: requestVersion, botId: botId, peerId: peerId, query: query)
|
let requestData = RequestData(version: requestVersion, botId: botId, peerId: peerId, query: query)
|
||||||
if let keyData = try? JSONEncoder().encode(requestData) {
|
if let keyData = try? JSONEncoder().encode(requestData) {
|
||||||
|
@ -61,7 +61,7 @@ private final class ChatContextResultPeekNode: ASDisplayNode, PeekControllerCont
|
|||||||
|
|
||||||
private let imageNodeBackground: ASDisplayNode
|
private let imageNodeBackground: ASDisplayNode
|
||||||
private let imageNode: TransformImageNode
|
private let imageNode: TransformImageNode
|
||||||
private var videoLayer: (SoftwareVideoThumbnailLayer, SoftwareVideoLayerFrameManager, SampleBufferLayer)?
|
private var videoLayer: (SoftwareVideoThumbnailNode, SoftwareVideoLayerFrameManager, SampleBufferLayer)?
|
||||||
|
|
||||||
private var currentImageResource: TelegramMediaResource?
|
private var currentImageResource: TelegramMediaResource?
|
||||||
private var currentVideoFile: TelegramMediaFile?
|
private var currentVideoFile: TelegramMediaFile?
|
||||||
@ -248,13 +248,13 @@ private final class ChatContextResultPeekNode: ASDisplayNode, PeekControllerCont
|
|||||||
if updatedVideoFile {
|
if updatedVideoFile {
|
||||||
if let (thumbnailLayer, _, layer) = self.videoLayer {
|
if let (thumbnailLayer, _, layer) = self.videoLayer {
|
||||||
self.videoLayer = nil
|
self.videoLayer = nil
|
||||||
thumbnailLayer.removeFromSuperlayer()
|
thumbnailLayer.removeFromSupernode()
|
||||||
layer.layer.removeFromSuperlayer()
|
layer.layer.removeFromSuperlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let videoFileReference = videoFileReference {
|
if let videoFileReference = videoFileReference {
|
||||||
let thumbnailLayer = SoftwareVideoThumbnailLayer(account: self.account, fileReference: videoFileReference, synchronousLoad: false)
|
let thumbnailLayer = SoftwareVideoThumbnailNode(account: self.account, fileReference: videoFileReference, synchronousLoad: false)
|
||||||
self.layer.addSublayer(thumbnailLayer)
|
self.addSubnode(thumbnailLayer)
|
||||||
let layerHolder = takeSampleBufferLayer()
|
let layerHolder = takeSampleBufferLayer()
|
||||||
layerHolder.layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
layerHolder.layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||||
self.layer.addSublayer(layerHolder.layer)
|
self.layer.addSublayer(layerHolder.layer)
|
||||||
|
@ -24,6 +24,16 @@ private func fixListScrolling(_ multiplexedNode: MultiplexedVideoNode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class ChatMediaInputGifPaneTrendingState {
|
||||||
|
let files: [MultiplexedVideoNodeFile]
|
||||||
|
let nextOffset: String?
|
||||||
|
|
||||||
|
init(files: [MultiplexedVideoNodeFile], nextOffset: String?) {
|
||||||
|
self.files = files
|
||||||
|
self.nextOffset = nextOffset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private var theme: PresentationTheme
|
private var theme: PresentationTheme
|
||||||
@ -49,7 +59,7 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
private let emptyNode: ImmediateTextNode
|
private let emptyNode: ImmediateTextNode
|
||||||
|
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
let trendingPromise = Promise<[MultiplexedVideoNodeFile]?>(nil)
|
let trendingPromise = Promise<ChatMediaInputGifPaneTrendingState?>(nil)
|
||||||
|
|
||||||
private var validLayout: (CGSize, CGFloat, CGFloat, Bool, Bool, DeviceMetrics)?
|
private var validLayout: (CGSize, CGFloat, CGFloat, Bool, Bool, DeviceMetrics)?
|
||||||
private var didScrollPreviousOffset: CGFloat?
|
private var didScrollPreviousOffset: CGFloat?
|
||||||
@ -57,6 +67,8 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
private var didScrollPreviousState: ChatMediaInputPaneScrollState?
|
private var didScrollPreviousState: ChatMediaInputPaneScrollState?
|
||||||
|
|
||||||
private(set) var mode: ChatMediaInputGifMode = .recent
|
private(set) var mode: ChatMediaInputGifMode = .recent
|
||||||
|
private var isLoadingMore: Bool = false
|
||||||
|
private var nextOffset: String?
|
||||||
|
|
||||||
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, paneDidScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void, fixPaneScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void, openGifContextMenu: @escaping (MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void) {
|
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, paneDidScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void, fixPaneScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void, openGifContextMenu: @escaping (MultiplexedVideoNodeFile, ASDisplayNode, CGRect, ContextGesture, Bool) -> Void) {
|
||||||
self.account = account
|
self.account = account
|
||||||
@ -132,7 +144,7 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.resetMode(synchronous: true)
|
self.resetMode(synchronous: true, searchOffset: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override var isEmpty: Bool {
|
override var isEmpty: Bool {
|
||||||
@ -160,7 +172,7 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
let displaySearch: Bool
|
let displaySearch: Bool
|
||||||
|
|
||||||
switch self.mode {
|
switch self.mode {
|
||||||
case .recent:
|
case .recent, .trending:
|
||||||
displaySearch = true
|
displaySearch = true
|
||||||
default:
|
default:
|
||||||
displaySearch = false
|
displaySearch = false
|
||||||
@ -194,9 +206,9 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
func initializeIfNeeded() {
|
func initializeIfNeeded() {
|
||||||
if self.multiplexedNode == nil {
|
if self.multiplexedNode == nil {
|
||||||
self.trendingPromise.set(paneGifSearchForQuery(account: account, query: "", offset: nil, incompleteResults: true, delayRequest: false, updateActivity: nil)
|
self.trendingPromise.set(paneGifSearchForQuery(account: account, query: "", offset: nil, incompleteResults: true, delayRequest: false, updateActivity: nil)
|
||||||
|> map { items -> [MultiplexedVideoNodeFile]? in
|
|> map { items -> ChatMediaInputGifPaneTrendingState? in
|
||||||
if let (items, _) = items {
|
if let items = items {
|
||||||
return items
|
return ChatMediaInputGifPaneTrendingState(files: items.files, nextOffset: items.nextOffset)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -243,6 +255,10 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
let state = ChatMediaInputPaneScrollState(absoluteOffset: absoluteOffset, relativeChange: delta)
|
let state = ChatMediaInputPaneScrollState(absoluteOffset: absoluteOffset, relativeChange: delta)
|
||||||
strongSelf.didScrollPreviousState = state
|
strongSelf.didScrollPreviousState = state
|
||||||
strongSelf.paneDidScroll(strongSelf, state, .immediate)
|
strongSelf.paneDidScroll(strongSelf, state, .immediate)
|
||||||
|
|
||||||
|
if offset >= height - multiplexedNode.bounds.height - 200.0 {
|
||||||
|
strongSelf.loadMore()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
multiplexedNode.didEndScrolling = { [weak self] in
|
multiplexedNode.didEndScrolling = { [weak self] in
|
||||||
@ -253,23 +269,25 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
strongSelf.fixPaneScroll(strongSelf, didScrollPreviousState)
|
strongSelf.fixPaneScroll(strongSelf, didScrollPreviousState)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let multiplexedNode = strongSelf.multiplexedNode {
|
if let _ = strongSelf.multiplexedNode {
|
||||||
//fixListScrolling(multiplexedNode)
|
//fixListScrolling(multiplexedNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateMultiplexedNodeLayout(changedIsExpanded: false, transition: .immediate)
|
self.updateMultiplexedNodeLayout(changedIsExpanded: false, transition: .immediate)
|
||||||
|
|
||||||
self.resetMode(synchronous: false)
|
self.resetMode(synchronous: false, searchOffset: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func resetMode(synchronous: Bool) {
|
private func resetMode(synchronous: Bool, searchOffset: String?) {
|
||||||
let filesSignal: Signal<MultiplexedVideoNodeFiles, NoError>
|
self.isLoadingMore = true
|
||||||
|
|
||||||
|
let filesSignal: Signal<(MultiplexedVideoNodeFiles, String?), NoError>
|
||||||
switch self.mode {
|
switch self.mode {
|
||||||
case .recent:
|
case .recent:
|
||||||
filesSignal = combineLatest(self.trendingPromise.get(), self.account.postbox.combinedView(keys: [.orderedItemList(id: Namespaces.OrderedItemList.CloudRecentGifs)]))
|
filesSignal = combineLatest(self.trendingPromise.get(), self.account.postbox.combinedView(keys: [.orderedItemList(id: Namespaces.OrderedItemList.CloudRecentGifs)]))
|
||||||
|> map { trending, view -> MultiplexedVideoNodeFiles in
|
|> map { trending, view -> (MultiplexedVideoNodeFiles, String?) in
|
||||||
var recentGifs: OrderedItemListView?
|
var recentGifs: OrderedItemListView?
|
||||||
if let orderedView = view.views[.orderedItemList(id: Namespaces.OrderedItemList.CloudRecentGifs)] {
|
if let orderedView = view.views[.orderedItemList(id: Namespaces.OrderedItemList.CloudRecentGifs)] {
|
||||||
recentGifs = orderedView as? OrderedItemListView
|
recentGifs = orderedView as? OrderedItemListView
|
||||||
@ -286,36 +304,58 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
saved = []
|
saved = []
|
||||||
}
|
}
|
||||||
|
|
||||||
return MultiplexedVideoNodeFiles(saved: saved, trending: trending ?? [], isSearch: false)
|
return (MultiplexedVideoNodeFiles(saved: saved, trending: trending?.files ?? [], isSearch: false, canLoadMore: false), nil)
|
||||||
}
|
}
|
||||||
case .trending:
|
case .trending:
|
||||||
|
if let searchOffset = searchOffset {
|
||||||
|
filesSignal = paneGifSearchForQuery(account: self.account, query: "", offset: searchOffset, incompleteResults: true, delayRequest: false, updateActivity: nil)
|
||||||
|
|> map { result -> (MultiplexedVideoNodeFiles, String?) in
|
||||||
|
let canLoadMore: Bool
|
||||||
|
if let result = result {
|
||||||
|
canLoadMore = !result.isComplete
|
||||||
|
} else {
|
||||||
|
canLoadMore = true
|
||||||
|
}
|
||||||
|
return (MultiplexedVideoNodeFiles(saved: [], trending: result?.files ?? [], isSearch: true, canLoadMore: canLoadMore), result?.nextOffset)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
filesSignal = self.trendingPromise.get()
|
filesSignal = self.trendingPromise.get()
|
||||||
|> map { trending -> MultiplexedVideoNodeFiles in
|
|> map { trending -> (MultiplexedVideoNodeFiles, String?) in
|
||||||
return MultiplexedVideoNodeFiles(saved: [], trending: trending ?? [], isSearch: true)
|
return (MultiplexedVideoNodeFiles(saved: [], trending: trending?.files ?? [], isSearch: true, canLoadMore: false), trending?.nextOffset)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case let .emojiSearch(emoji):
|
case let .emojiSearch(emoji):
|
||||||
filesSignal = paneGifSearchForQuery(account: self.account, query: emoji, offset: nil, incompleteResults: true, delayRequest: false, updateActivity: nil)
|
filesSignal = paneGifSearchForQuery(account: self.account, query: emoji, offset: searchOffset, incompleteResults: true, delayRequest: false, updateActivity: nil)
|
||||||
|> map { trending -> MultiplexedVideoNodeFiles in
|
|> map { result -> (MultiplexedVideoNodeFiles, String?) in
|
||||||
return MultiplexedVideoNodeFiles(saved: [], trending: trending?.0 ?? [], isSearch: true)
|
let canLoadMore: Bool
|
||||||
|
if let result = result {
|
||||||
|
canLoadMore = !result.isComplete
|
||||||
|
} else {
|
||||||
|
canLoadMore = true
|
||||||
|
}
|
||||||
|
return (MultiplexedVideoNodeFiles(saved: [], trending: result?.files ?? [], isSearch: true, canLoadMore: canLoadMore), result?.nextOffset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstTime = true
|
var firstTime = true
|
||||||
|
|
||||||
self.disposable.set((filesSignal
|
self.disposable.set((filesSignal
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] files in
|
|> deliverOnMainQueue).start(next: { [weak self] addedFiles, nextOffset in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
//let previousFiles = strongSelf.multiplexedNode?.files
|
|
||||||
var resetScrollingToOffset: CGFloat?
|
var resetScrollingToOffset: CGFloat?
|
||||||
if firstTime {
|
if firstTime {
|
||||||
firstTime = false
|
firstTime = false
|
||||||
|
if searchOffset == nil {
|
||||||
resetScrollingToOffset = 0.0
|
resetScrollingToOffset = 0.0
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.isLoadingMore = false
|
||||||
|
|
||||||
let displaySearch: Bool
|
let displaySearch: Bool
|
||||||
|
|
||||||
switch strongSelf.mode {
|
switch strongSelf.mode {
|
||||||
case .recent:
|
case .recent, .trending:
|
||||||
displaySearch = true
|
displaySearch = true
|
||||||
default:
|
default:
|
||||||
displaySearch = false
|
displaySearch = false
|
||||||
@ -327,17 +367,41 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
strongSelf.multiplexedNode?.topInset = topInset + (displaySearch ? 60.0 : 0.0)
|
strongSelf.multiplexedNode?.topInset = topInset + (displaySearch ? 60.0 : 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.multiplexedNode?.setFiles(files: files, synchronous: synchronous, resetScrollingToOffset: resetScrollingToOffset)
|
var files = addedFiles
|
||||||
|
if let _ = searchOffset {
|
||||||
/*let wasEmpty: Bool
|
var resultFiles: [MultiplexedVideoNodeFile] = []
|
||||||
if let previousFiles = previousFiles {
|
if let currentFiles = strongSelf.multiplexedNode?.files.trending {
|
||||||
wasEmpty = previousFiles.trending.isEmpty && previousFiles.saved.isEmpty
|
resultFiles = currentFiles
|
||||||
} else {
|
|
||||||
wasEmpty = true
|
|
||||||
}
|
}
|
||||||
let isEmpty = files.trending.isEmpty && files.saved.isEmpty
|
var existingFileIds = Set(resultFiles.map { $0.file.media.fileId })
|
||||||
strongSelf.emptyNode.isHidden = !isEmpty*/
|
for file in addedFiles.trending {
|
||||||
|
if existingFileIds.contains(file.file.media.fileId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
existingFileIds.insert(file.file.media.fileId)
|
||||||
|
resultFiles.append(file)
|
||||||
|
}
|
||||||
|
files = MultiplexedVideoNodeFiles(saved: [], trending: resultFiles, isSearch: true, canLoadMore: addedFiles.canLoadMore)
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.nextOffset = nextOffset
|
||||||
|
strongSelf.multiplexedNode?.setFiles(files: files, synchronous: synchronous, resetScrollingToOffset: resetScrollingToOffset)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func loadMore() {
|
||||||
|
if self.isLoadingMore {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let nextOffset = self.nextOffset else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch self.mode {
|
||||||
|
case .trending, .emojiSearch:
|
||||||
|
self.resetMode(synchronous: false, searchOffset: nextOffset)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ final class ChatMediaInputMetaSectionItem: ListViewItem {
|
|||||||
let selectedItem: () -> Void
|
let selectedItem: () -> Void
|
||||||
|
|
||||||
var selectable: Bool {
|
var selectable: Bool {
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
init(inputNodeInteraction: ChatMediaInputNodeInteraction, type: ChatMediaInputMetaSectionItemType, theme: PresentationTheme, selected: @escaping () -> Void) {
|
init(inputNodeInteraction: ChatMediaInputNodeInteraction, type: ChatMediaInputMetaSectionItemType, theme: PresentationTheme, selected: @escaping () -> Void) {
|
||||||
@ -37,14 +37,16 @@ final class ChatMediaInputMetaSectionItem: ListViewItem {
|
|||||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||||
async {
|
async {
|
||||||
let node = ChatMediaInputMetaSectionItemNode()
|
let node = ChatMediaInputMetaSectionItemNode()
|
||||||
node.contentSize = CGSize(width: 41.0, height: 41.0)
|
Queue.mainQueue().async {
|
||||||
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
|
||||||
node.inputNodeInteraction = self.inputNodeInteraction
|
node.inputNodeInteraction = self.inputNodeInteraction
|
||||||
node.setItem(item: self)
|
node.setItem(item: self)
|
||||||
node.updateTheme(theme: self.theme)
|
node.updateTheme(theme: self.theme)
|
||||||
node.updateIsHighlighted()
|
node.updateIsHighlighted()
|
||||||
node.updateAppearanceTransition(transition: .immediate)
|
node.updateAppearanceTransition(transition: .immediate)
|
||||||
Queue.mainQueue().async {
|
|
||||||
|
node.contentSize = CGSize(width: 41.0, height: 41.0)
|
||||||
|
node.insets = ChatMediaInputNode.setupPanelIconInsets(item: self, previousItem: previousItem, nextItem: nextItem)
|
||||||
|
|
||||||
completion(node, {
|
completion(node, {
|
||||||
return (nil, { _ in
|
return (nil, { _ in
|
||||||
|
|
||||||
@ -78,7 +80,6 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
|
|||||||
private let textNodeContainer: ASDisplayNode
|
private let textNodeContainer: ASDisplayNode
|
||||||
private let textNode: ImmediateTextNode
|
private let textNode: ImmediateTextNode
|
||||||
private let highlightNode: ASImageNode
|
private let highlightNode: ASImageNode
|
||||||
private let buttonNode: HighlightTrackingButtonNode
|
|
||||||
|
|
||||||
var item: ChatMediaInputMetaSectionItem?
|
var item: ChatMediaInputMetaSectionItem?
|
||||||
var currentCollectionId: ItemCollectionId?
|
var currentCollectionId: ItemCollectionId?
|
||||||
@ -110,32 +111,22 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
self.textNodeContainer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
self.textNodeContainer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
|
|
||||||
self.buttonNode = HighlightTrackingButtonNode()
|
|
||||||
|
|
||||||
super.init(layerBacked: false, dynamicBounce: false)
|
super.init(layerBacked: false, dynamicBounce: false)
|
||||||
|
|
||||||
self.addSubnode(self.highlightNode)
|
self.addSubnode(self.highlightNode)
|
||||||
self.addSubnode(self.imageNode)
|
self.addSubnode(self.imageNode)
|
||||||
self.addSubnode(self.textNodeContainer)
|
self.addSubnode(self.textNodeContainer)
|
||||||
self.addSubnode(self.buttonNode)
|
|
||||||
|
|
||||||
let imageSize = CGSize(width: 26.0, height: 26.0)
|
let imageSize = CGSize(width: 26.0, height: 26.0)
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
|
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
|
||||||
|
|
||||||
self.textNodeContainer.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0) + UIScreenPixel), size: imageSize)
|
self.textNodeContainer.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0) + 1.0), size: imageSize)
|
||||||
|
|
||||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: boundingSize)
|
|
||||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func buttonPressed() {
|
|
||||||
self.item?.selectedItem()
|
|
||||||
}
|
|
||||||
|
|
||||||
func setItem(item: ChatMediaInputMetaSectionItem) {
|
func setItem(item: ChatMediaInputMetaSectionItem) {
|
||||||
self.item = item
|
self.item = item
|
||||||
switch item.type {
|
switch item.type {
|
||||||
@ -167,7 +158,7 @@ final class ChatMediaInputMetaSectionItemNode: ListViewItemNode {
|
|||||||
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingGifsIcon(theme)
|
self.imageNode.image = PresentationResourcesChat.chatInputMediaPanelTrendingGifsIcon(theme)
|
||||||
case let .gifEmoji(emoji):
|
case let .gifEmoji(emoji):
|
||||||
self.imageNode.image = nil
|
self.imageNode.image = nil
|
||||||
self.textNode.attributedText = NSAttributedString(string: emoji, font: Font.regular(28.0), textColor: .black)
|
self.textNode.attributedText = NSAttributedString(string: emoji, font: Font.regular(27.0), textColor: .black)
|
||||||
let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: 100.0))
|
||||||
self.textNode.frame = CGRect(origin: CGPoint(x: floor((self.textNodeContainer.bounds.width - textSize.width) / 2.0), y: floor((self.textNodeContainer.bounds.height - textSize.height) / 2.0)), size: textSize)
|
self.textNode.frame = CGRect(origin: CGPoint(x: floor((self.textNodeContainer.bounds.width - textSize.width) / 2.0), y: floor((self.textNodeContainer.bounds.height - textSize.height) / 2.0)), size: textSize)
|
||||||
}
|
}
|
||||||
|
@ -501,9 +501,11 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
|
|
||||||
self.listView = ListView()
|
self.listView = ListView()
|
||||||
self.listView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0)
|
self.listView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0)
|
||||||
|
self.listView.scroller.panGestureRecognizer.cancelsTouchesInView = false
|
||||||
|
|
||||||
self.gifListView = ListView()
|
self.gifListView = ListView()
|
||||||
self.gifListView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0)
|
self.gifListView.transform = CATransform3DMakeRotation(-CGFloat(Double.pi / 2.0), 0.0, 0.0, 1.0)
|
||||||
|
self.gifListView.scroller.panGestureRecognizer.cancelsTouchesInView = false
|
||||||
|
|
||||||
var paneDidScrollImpl: ((ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void)?
|
var paneDidScrollImpl: ((ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void)?
|
||||||
var fixPaneScrollImpl: ((ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void)?
|
var fixPaneScrollImpl: ((ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void)?
|
||||||
|
@ -11,6 +11,7 @@ import StickerResources
|
|||||||
import AccountContext
|
import AccountContext
|
||||||
import AnimatedStickerNode
|
import AnimatedStickerNode
|
||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
|
import ShimmerEffect
|
||||||
|
|
||||||
enum ChatMediaInputStickerGridSectionAccessory {
|
enum ChatMediaInputStickerGridSectionAccessory {
|
||||||
case none
|
case none
|
||||||
@ -121,6 +122,7 @@ final class ChatMediaInputStickerGridItem: GridItem {
|
|||||||
let selected: () -> Void
|
let selected: () -> Void
|
||||||
let interfaceInteraction: ChatControllerInteraction?
|
let interfaceInteraction: ChatControllerInteraction?
|
||||||
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||||
|
let theme: PresentationTheme
|
||||||
|
|
||||||
let section: GridSection?
|
let section: GridSection?
|
||||||
|
|
||||||
@ -130,6 +132,7 @@ final class ChatMediaInputStickerGridItem: GridItem {
|
|||||||
self.stickerItem = stickerItem
|
self.stickerItem = stickerItem
|
||||||
self.interfaceInteraction = interfaceInteraction
|
self.interfaceInteraction = interfaceInteraction
|
||||||
self.inputNodeInteraction = inputNodeInteraction
|
self.inputNodeInteraction = inputNodeInteraction
|
||||||
|
self.theme = theme
|
||||||
self.selected = selected
|
self.selected = selected
|
||||||
if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue {
|
if collectionId.namespace == ChatMediaInputPanelAuxiliaryNamespace.savedStickers.rawValue {
|
||||||
self.section = nil
|
self.section = nil
|
||||||
@ -170,6 +173,7 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
private var currentSize: CGSize?
|
private var currentSize: CGSize?
|
||||||
private let imageNode: TransformImageNode
|
private let imageNode: TransformImageNode
|
||||||
private var animationNode: AnimatedStickerNode?
|
private var animationNode: AnimatedStickerNode?
|
||||||
|
private var placeholderNode: ShimmerEffectNode?
|
||||||
private var didSetUpAnimationNode = false
|
private var didSetUpAnimationNode = false
|
||||||
private var item: ChatMediaInputStickerGridItem?
|
private var item: ChatMediaInputStickerGridItem?
|
||||||
|
|
||||||
@ -196,16 +200,45 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
|
self.placeholderNode = ShimmerEffectNode()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.addSubnode(self.imageNode)
|
self.addSubnode(self.imageNode)
|
||||||
|
if let placeholderNode = self.placeholderNode {
|
||||||
|
self.addSubnode(placeholderNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstTime = true
|
||||||
|
self.imageNode.imageUpdated = { [weak self] image in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if image != nil {
|
||||||
|
strongSelf.removePlaceholder(animated: !firstTime)
|
||||||
|
}
|
||||||
|
firstTime = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.stickerFetchedDisposable.dispose()
|
self.stickerFetchedDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func removePlaceholder(animated: Bool) {
|
||||||
|
if let placeholderNode = self.placeholderNode {
|
||||||
|
self.placeholderNode = nil
|
||||||
|
if !animated {
|
||||||
|
placeholderNode.removeFromSupernode()
|
||||||
|
} else {
|
||||||
|
placeholderNode.alpha = 0.0
|
||||||
|
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in
|
||||||
|
placeholderNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func didLoad() {
|
override func didLoad() {
|
||||||
super.didLoad()
|
super.didLoad()
|
||||||
|
|
||||||
@ -216,7 +249,13 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
guard let item = item as? ChatMediaInputStickerGridItem else {
|
guard let item = item as? ChatMediaInputStickerGridItem else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sideSize: CGFloat = size.width - 10.0
|
||||||
|
let boundingSize = CGSize(width: sideSize, height: sideSize)
|
||||||
|
|
||||||
self.item = item
|
self.item = item
|
||||||
|
|
||||||
|
|
||||||
if self.currentState == nil || self.currentState!.0 !== item.account || self.currentState!.1 != item.stickerItem {
|
if self.currentState == nil || self.currentState!.0 !== item.account || self.currentState!.1 != item.stickerItem {
|
||||||
if let dimensions = item.stickerItem.file.dimensions {
|
if let dimensions = item.stickerItem.file.dimensions {
|
||||||
if item.stickerItem.file.isAnimatedSticker {
|
if item.stickerItem.file.isAnimatedSticker {
|
||||||
@ -227,8 +266,12 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
animationNode.started = { [weak self] in
|
animationNode.started = { [weak self] in
|
||||||
self?.imageNode.isHidden = true
|
self?.imageNode.isHidden = true
|
||||||
}
|
}
|
||||||
|
if let placeholderNode = self.placeholderNode {
|
||||||
|
self.insertSubnode(animationNode, belowSubnode: placeholderNode)
|
||||||
|
} else {
|
||||||
self.addSubnode(animationNode)
|
self.addSubnode(animationNode)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let dimensions = item.stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
let dimensions = item.stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||||
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.account.postbox, file: item.stickerItem.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))))
|
self.imageNode.setSignal(chatMessageAnimatedSticker(postbox: item.account.postbox, file: item.stickerItem.file, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))))
|
||||||
self.updateVisibility()
|
self.updateVisibility()
|
||||||
@ -253,9 +296,6 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
if self.currentSize != size {
|
if self.currentSize != size {
|
||||||
self.currentSize = size
|
self.currentSize = size
|
||||||
|
|
||||||
let sideSize: CGFloat = size.width - 10.0 //min(75.0 - 10.0, size.width)
|
|
||||||
let boundingSize = CGSize(width: sideSize, height: sideSize)
|
|
||||||
|
|
||||||
if let (_, _, mediaDimensions) = self.currentState {
|
if let (_, _, mediaDimensions) = self.currentState {
|
||||||
let imageSize = mediaDimensions.aspectFitted(boundingSize)
|
let imageSize = mediaDimensions.aspectFitted(boundingSize)
|
||||||
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
||||||
@ -266,6 +306,20 @@ final class ChatMediaInputStickerGridItemNode: GridItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let placeholderNode = self.placeholderNode {
|
||||||
|
let placeholderFrame = CGRect(origin: CGPoint(x: floor((size.width - boundingSize.width) / 2.0), y: floor((size.height - boundingSize.height) / 2.0)), size: boundingSize)
|
||||||
|
placeholderNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
||||||
|
let theme = item.theme
|
||||||
|
placeholderNode.update(backgroundColor: theme.chat.inputMediaPanel.stickersBackgroundColor, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.mixedWith(theme.chat.inputMediaPanel.stickersBackgroundColor, alpha: 0.9), shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3), shapes: [.roundedRect(rect: placeholderFrame, cornerRadius: 10.0)], size: bounds.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
|
||||||
|
if let placeholderNode = self.placeholderNode {
|
||||||
|
placeholderNode.updateAbsoluteRect(absoluteRect, within: containerSize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) {
|
@objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) {
|
||||||
|
@ -11,6 +11,7 @@ import StickerResources
|
|||||||
import ItemListStickerPackItem
|
import ItemListStickerPackItem
|
||||||
import AnimatedStickerNode
|
import AnimatedStickerNode
|
||||||
import TelegramAnimatedStickerNode
|
import TelegramAnimatedStickerNode
|
||||||
|
import ShimmerEffect
|
||||||
|
|
||||||
final class ChatMediaInputStickerPackItem: ListViewItem {
|
final class ChatMediaInputStickerPackItem: ListViewItem {
|
||||||
let account: Account
|
let account: Account
|
||||||
@ -75,6 +76,8 @@ private let verticalOffset: CGFloat = 3.0
|
|||||||
final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
||||||
private let imageNode: TransformImageNode
|
private let imageNode: TransformImageNode
|
||||||
private var animatedStickerNode: AnimatedStickerNode?
|
private var animatedStickerNode: AnimatedStickerNode?
|
||||||
|
private var placeholderNode: ShimmerEffectNode?
|
||||||
|
private var placeholderImageNode: ASImageNode?
|
||||||
private let highlightNode: ASImageNode
|
private let highlightNode: ASImageNode
|
||||||
|
|
||||||
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
||||||
@ -107,7 +110,12 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
|||||||
self.imageNode = TransformImageNode()
|
self.imageNode = TransformImageNode()
|
||||||
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
self.imageNode.isLayerBacked = !smartInvertColorsEnabled()
|
||||||
|
|
||||||
self.highlightNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - highlightSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - highlightSize.height) / 2.0)), size: highlightSize)
|
self.placeholderImageNode = ASImageNode()
|
||||||
|
self.placeholderImageNode?.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
//self.placeholderNode = ShimmerEffectNode()
|
||||||
|
|
||||||
|
self.highlightNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - highlightSize.width) / 2.0) + verticalOffset - UIScreenPixel, y: floor((boundingSize.height - highlightSize.height) / 2.0) - UIScreenPixel), size: highlightSize)
|
||||||
|
|
||||||
self.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
self.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
self.imageNode.contentAnimations = [.firstUpdate]
|
self.imageNode.contentAnimations = [.firstUpdate]
|
||||||
@ -116,12 +124,54 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
|||||||
|
|
||||||
self.addSubnode(self.highlightNode)
|
self.addSubnode(self.highlightNode)
|
||||||
self.addSubnode(self.imageNode)
|
self.addSubnode(self.imageNode)
|
||||||
|
if let placeholderImageNode = self.placeholderImageNode {
|
||||||
|
self.addSubnode(placeholderImageNode)
|
||||||
|
}
|
||||||
|
if let placeholderNode = self.placeholderNode {
|
||||||
|
self.addSubnode(placeholderNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstTime = true
|
||||||
|
self.imageNode.imageUpdated = { [weak self] image in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if image != nil {
|
||||||
|
strongSelf.removePlaceholder(animated: !firstTime)
|
||||||
|
}
|
||||||
|
firstTime = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.stickerFetchedDisposable.dispose()
|
self.stickerFetchedDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func removePlaceholder(animated: Bool) {
|
||||||
|
if let placeholderNode = self.placeholderNode {
|
||||||
|
self.placeholderNode = nil
|
||||||
|
if !animated {
|
||||||
|
placeholderNode.removeFromSupernode()
|
||||||
|
} else {
|
||||||
|
placeholderNode.alpha = 0.0
|
||||||
|
placeholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderNode] _ in
|
||||||
|
placeholderNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let placeholderImageNode = self.placeholderImageNode {
|
||||||
|
self.placeholderImageNode = nil
|
||||||
|
if !animated {
|
||||||
|
placeholderImageNode.removeFromSupernode()
|
||||||
|
} else {
|
||||||
|
placeholderImageNode.alpha = 0.0
|
||||||
|
placeholderImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak placeholderImageNode] _ in
|
||||||
|
placeholderImageNode?.removeFromSupernode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateStickerPackItem(account: Account, info: StickerPackCollectionInfo, item: StickerPackItem?, collectionId: ItemCollectionId, theme: PresentationTheme) {
|
func updateStickerPackItem(account: Account, info: StickerPackCollectionInfo, item: StickerPackItem?, collectionId: ItemCollectionId, theme: PresentationTheme) {
|
||||||
self.currentCollectionId = collectionId
|
self.currentCollectionId = collectionId
|
||||||
|
|
||||||
@ -159,13 +209,13 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
|||||||
let imageSize = representation.dimensions.cgSize.aspectFitted(boundingImageSize)
|
let imageSize = representation.dimensions.cgSize.aspectFitted(boundingImageSize)
|
||||||
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
|
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
|
||||||
imageApply()
|
imageApply()
|
||||||
self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: representation.resource))
|
self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: representation.resource, nilIfEmpty: true))
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
|
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
|
||||||
case let .animated(resource):
|
case let .animated(resource):
|
||||||
let imageSize = boundingImageSize
|
let imageSize = boundingImageSize
|
||||||
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
|
let imageApply = self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))
|
||||||
imageApply()
|
imageApply()
|
||||||
self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: resource, animated: true))
|
self.imageNode.setSignal(chatMessageStickerPackThumbnail(postbox: account.postbox, resource: resource, animated: true, nilIfEmpty: true))
|
||||||
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
|
self.imageNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
|
||||||
|
|
||||||
let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false
|
let loopAnimatedStickers = self.inputNodeInteraction?.stickerSettings?.loopAnimatedStickers ?? false
|
||||||
@ -178,7 +228,11 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
|||||||
animatedStickerNode = AnimatedStickerNode()
|
animatedStickerNode = AnimatedStickerNode()
|
||||||
self.animatedStickerNode = animatedStickerNode
|
self.animatedStickerNode = animatedStickerNode
|
||||||
animatedStickerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
animatedStickerNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
|
if let placeholderNode = self.placeholderNode {
|
||||||
|
self.insertSubnode(animatedStickerNode, belowSubnode: placeholderNode)
|
||||||
|
} else {
|
||||||
self.addSubnode(animatedStickerNode)
|
self.addSubnode(animatedStickerNode)
|
||||||
|
}
|
||||||
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 80, height: 80, mode: .cached)
|
animatedStickerNode.setup(source: AnimatedStickerResourceSource(account: account, resource: resource), width: 80, height: 80, mode: .cached)
|
||||||
}
|
}
|
||||||
animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers
|
animatedStickerNode.visibility = self.visibilityStatus && loopAnimatedStickers
|
||||||
@ -191,10 +245,35 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let placeholderImageNode = self.placeholderImageNode {
|
||||||
|
if placeholderImageNode.image == nil {
|
||||||
|
placeholderImageNode.image = generateStretchableFilledCircleImage(diameter: 10.0, color: theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.mixedWith(.clear, alpha: 0.6))
|
||||||
|
}
|
||||||
|
let size = boundingSize
|
||||||
|
let imageSize = boundingImageSize
|
||||||
|
let placeholderFrame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
|
||||||
|
placeholderImageNode.frame = placeholderFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
if let placeholderNode = self.placeholderNode {
|
||||||
|
let size = boundingSize
|
||||||
|
let imageSize = boundingImageSize
|
||||||
|
let placeholderFrame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
|
||||||
|
placeholderNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
|
||||||
|
placeholderNode.update(backgroundColor: theme.chat.inputPanel.panelBackgroundColor, foregroundColor: theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.mixedWith(theme.chat.inputPanel.panelBackgroundColor, alpha: 0.8), shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3), shapes: [.roundedRect(rect: placeholderFrame, cornerRadius: 5.0)], size: bounds.size)
|
||||||
|
}
|
||||||
|
|
||||||
self.updateIsHighlighted()
|
self.updateIsHighlighted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||||
|
if let placeholderNode = self.placeholderNode {
|
||||||
|
//placeholderNode.updateAbsoluteRect(rect, within: containerSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateIsHighlighted() {
|
func updateIsHighlighted() {
|
||||||
assert(Queue.mainQueue().isCurrent())
|
assert(Queue.mainQueue().isCurrent())
|
||||||
if let currentCollectionId = self.currentCollectionId, let inputNodeInteraction = self.inputNodeInteraction {
|
if let currentCollectionId = self.currentCollectionId, let inputNodeInteraction = self.inputNodeInteraction {
|
||||||
|
@ -550,6 +550,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|
|||||||
replaceVideoNode = true
|
replaceVideoNode = true
|
||||||
} else if currentFile.fileId.namespace == Namespaces.Media.CloudFile && file.fileId.namespace == Namespaces.Media.CloudFile && currentFile.fileId != file.fileId {
|
} else if currentFile.fileId.namespace == Namespaces.Media.CloudFile && file.fileId.namespace == Namespaces.Media.CloudFile && currentFile.fileId != file.fileId {
|
||||||
replaceVideoNode = true
|
replaceVideoNode = true
|
||||||
|
} else if currentFile.fileId != file.fileId && file.fileId.namespace == Namespaces.Media.CloudSecretFile {
|
||||||
|
replaceVideoNode = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if !(file.resource is LocalFileVideoMediaResource) {
|
} else if !(file.resource is LocalFileVideoMediaResource) {
|
||||||
|
@ -1433,7 +1433,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
func updateIsProcessingInlineRequest(_ value: Bool) {
|
func updateIsProcessingInlineRequest(_ value: Bool) {
|
||||||
if value {
|
if value {
|
||||||
if self.searchActivityIndicator == nil, let currentState = self.presentationInterfaceState {
|
if self.searchActivityIndicator == nil, let currentState = self.presentationInterfaceState {
|
||||||
let searchActivityIndicator = ActivityIndicator(type: .custom(currentState.theme.list.itemAccentColor, 22.0, 1.0, false))
|
let searchActivityIndicator = ActivityIndicator(type: .custom(currentState.theme.list.itemAccentColor, 11.0, 1.0, false))
|
||||||
searchActivityIndicator.isUserInteractionEnabled = false
|
searchActivityIndicator.isUserInteractionEnabled = false
|
||||||
self.searchActivityIndicator = searchActivityIndicator
|
self.searchActivityIndicator = searchActivityIndicator
|
||||||
let indicatorSize = searchActivityIndicator.measure(CGSize(width: 100.0, height: 100.0))
|
let indicatorSize = searchActivityIndicator.measure(CGSize(width: 100.0, height: 100.0))
|
||||||
@ -1441,7 +1441,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
|
|||||||
searchActivityIndicator.frame = CGRect(origin: CGPoint(x: floor((size.width - indicatorSize.width) / 2.0), y: floor((size.height - indicatorSize.height) / 2.0) + 1.0), size: indicatorSize)
|
searchActivityIndicator.frame = CGRect(origin: CGPoint(x: floor((size.width - indicatorSize.width) / 2.0), y: floor((size.height - indicatorSize.height) / 2.0) + 1.0), size: indicatorSize)
|
||||||
self.searchLayoutClearImageNode.isHidden = true
|
self.searchLayoutClearImageNode.isHidden = true
|
||||||
self.searchLayoutClearButton.addSubnode(searchActivityIndicator)
|
self.searchLayoutClearButton.addSubnode(searchActivityIndicator)
|
||||||
searchActivityIndicator.layer.sublayerTransform = CATransform3DMakeScale(0.5, 0.5, 1.0)
|
//searchActivityIndicator.layer.sublayerTransform = CATransform3DMakeScale(0.5, 0.5, 1.0)
|
||||||
}
|
}
|
||||||
} else if let searchActivityIndicator = self.searchActivityIndicator {
|
} else if let searchActivityIndicator = self.searchActivityIndicator {
|
||||||
self.searchActivityIndicator = nil
|
self.searchActivityIndicator = nil
|
||||||
|
@ -11,7 +11,19 @@ import AccountContext
|
|||||||
import WebSearchUI
|
import WebSearchUI
|
||||||
import AppBundle
|
import AppBundle
|
||||||
|
|
||||||
func paneGifSearchForQuery(account: Account, query: String, offset: String?, incompleteResults: Bool = false, delayRequest: Bool = true, updateActivity: ((Bool) -> Void)?) -> Signal<([MultiplexedVideoNodeFile], String?)?, NoError> {
|
class PaneGifSearchForQueryResult {
|
||||||
|
let files: [MultiplexedVideoNodeFile]
|
||||||
|
let nextOffset: String?
|
||||||
|
let isComplete: Bool
|
||||||
|
|
||||||
|
init(files: [MultiplexedVideoNodeFile], nextOffset: String?, isComplete: Bool) {
|
||||||
|
self.files = files
|
||||||
|
self.nextOffset = nextOffset
|
||||||
|
self.isComplete = isComplete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func paneGifSearchForQuery(account: Account, query: String, offset: String?, incompleteResults: Bool = false, delayRequest: Bool = true, updateActivity: ((Bool) -> Void)?) -> Signal<PaneGifSearchForQueryResult?, NoError> {
|
||||||
let contextBot = account.postbox.transaction { transaction -> String in
|
let contextBot = account.postbox.transaction { transaction -> String in
|
||||||
let configuration = currentSearchBotsConfiguration(transaction: transaction)
|
let configuration = currentSearchBotsConfiguration(transaction: transaction)
|
||||||
return configuration.gifBotUsername ?? "gif"
|
return configuration.gifBotUsername ?? "gif"
|
||||||
@ -30,16 +42,14 @@ func paneGifSearchForQuery(account: Account, query: String, offset: String?, inc
|
|||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError> in
|
|> mapToSignal { peer -> Signal<(ChatPresentationInputQueryResult?, Bool), NoError> in
|
||||||
if let user = peer as? TelegramUser, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder {
|
if let user = peer as? TelegramUser, let botInfo = user.botInfo, let _ = botInfo.inlinePlaceholder {
|
||||||
let results = requestContextResults(account: account, botId: user.id, query: query, peerId: account.peerId, offset: offset ?? "", incompleteResults: incompleteResults, limit: 1)
|
let results = requestContextResults(account: account, botId: user.id, query: query, peerId: account.peerId, offset: offset ?? "", incompleteResults: incompleteResults, limit: 1)
|
||||||
|> map { results -> (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult? in
|
|> map { results -> (ChatPresentationInputQueryResult?, Bool) in
|
||||||
return { _ in
|
return (.contextRequestResult(user, results), results != nil)
|
||||||
return .contextRequestResult(user, results)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let maybeDelayedContextResults: Signal<(ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?, NoError>
|
let maybeDelayedContextResults: Signal<(ChatPresentationInputQueryResult?, Bool), NoError>
|
||||||
if delayRequest {
|
if delayRequest {
|
||||||
maybeDelayedContextResults = results |> delay(0.4, queue: Queue.concurrentDefaultQueue())
|
maybeDelayedContextResults = results |> delay(0.4, queue: Queue.concurrentDefaultQueue())
|
||||||
} else {
|
} else {
|
||||||
@ -48,12 +58,12 @@ func paneGifSearchForQuery(account: Account, query: String, offset: String?, inc
|
|||||||
|
|
||||||
return maybeDelayedContextResults
|
return maybeDelayedContextResults
|
||||||
} else {
|
} else {
|
||||||
return .single({ _ in return nil })
|
return .single((nil, true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return contextBot
|
return contextBot
|
||||||
|> mapToSignal { result -> Signal<([MultiplexedVideoNodeFile], String?)?, NoError> in
|
|> mapToSignal { result -> Signal<PaneGifSearchForQueryResult?, NoError> in
|
||||||
if let r = result(nil), case let .contextRequestResult(_, maybeCollection) = r, let collection = maybeCollection {
|
if let r = result.0, case let .contextRequestResult(_, maybeCollection) = r, let collection = maybeCollection {
|
||||||
let results = collection.results
|
let results = collection.results
|
||||||
var references: [MultiplexedVideoNodeFile] = []
|
var references: [MultiplexedVideoNodeFile] = []
|
||||||
for result in results {
|
for result in results {
|
||||||
@ -101,7 +111,7 @@ func paneGifSearchForQuery(account: Account, query: String, offset: String?, inc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return .single((references, collection.nextOffset))
|
return .single(PaneGifSearchForQueryResult(files: references, nextOffset: collection.nextOffset, isComplete: result.1))
|
||||||
} else if incompleteResults {
|
} else if incompleteResults {
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
} else {
|
} else {
|
||||||
@ -134,7 +144,7 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
|
|||||||
|
|
||||||
private var validLayout: CGSize?
|
private var validLayout: CGSize?
|
||||||
|
|
||||||
private let trendingPromise: Promise<[MultiplexedVideoNodeFile]?>
|
private let trendingPromise: Promise<ChatMediaInputGifPaneTrendingState?>
|
||||||
private let searchDisposable = MetaDisposable()
|
private let searchDisposable = MetaDisposable()
|
||||||
|
|
||||||
private let _ready = Promise<Void>()
|
private let _ready = Promise<Void>()
|
||||||
@ -149,7 +159,7 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
|
|||||||
|
|
||||||
private var hasInitialText = false
|
private var hasInitialText = false
|
||||||
|
|
||||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, trendingPromise: Promise<[MultiplexedVideoNodeFile]?>) {
|
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, trendingPromise: Promise<ChatMediaInputGifPaneTrendingState?>) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
self.inputNodeInteraction = inputNodeInteraction
|
self.inputNodeInteraction = inputNodeInteraction
|
||||||
@ -190,12 +200,19 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
|
|||||||
let signal: Signal<([MultiplexedVideoNodeFile], String?)?, NoError>
|
let signal: Signal<([MultiplexedVideoNodeFile], String?)?, NoError>
|
||||||
if !text.isEmpty {
|
if !text.isEmpty {
|
||||||
signal = paneGifSearchForQuery(account: self.context.account, query: text, offset: "", updateActivity: self.updateActivity)
|
signal = paneGifSearchForQuery(account: self.context.account, query: text, offset: "", updateActivity: self.updateActivity)
|
||||||
|
|> map { result -> ([MultiplexedVideoNodeFile], String?)? in
|
||||||
|
if let result = result {
|
||||||
|
return (result.files, result.nextOffset)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
self.updateActivity?(true)
|
self.updateActivity?(true)
|
||||||
} else {
|
} else {
|
||||||
signal = self.trendingPromise.get()
|
signal = self.trendingPromise.get()
|
||||||
|> map { items -> ([MultiplexedVideoNodeFile], String?)? in
|
|> map { items -> ([MultiplexedVideoNodeFile], String?)? in
|
||||||
if let items = items {
|
if let items = items {
|
||||||
return (items, nil)
|
return (items.files, nil)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -215,7 +232,7 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
|
|||||||
} else {
|
} else {
|
||||||
strongSelf.nextOffset = nil
|
strongSelf.nextOffset = nil
|
||||||
}
|
}
|
||||||
strongSelf.multiplexedNode?.setFiles(files: MultiplexedVideoNodeFiles(saved: [], trending: result, isSearch: true), synchronous: true, resetScrollingToOffset: nil)
|
strongSelf.multiplexedNode?.setFiles(files: MultiplexedVideoNodeFiles(saved: [], trending: result, isSearch: true, canLoadMore: false), synchronous: true, resetScrollingToOffset: nil)
|
||||||
strongSelf.updateActivity?(false)
|
strongSelf.updateActivity?(false)
|
||||||
strongSelf.notFoundNode.isHidden = text.isEmpty || !result.isEmpty
|
strongSelf.notFoundNode.isHidden = text.isEmpty || !result.isEmpty
|
||||||
}))
|
}))
|
||||||
@ -232,6 +249,13 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
|
|||||||
|
|
||||||
let signal: Signal<([MultiplexedVideoNodeFile], String?)?, NoError>
|
let signal: Signal<([MultiplexedVideoNodeFile], String?)?, NoError>
|
||||||
signal = paneGifSearchForQuery(account: self.context.account, query: text, offset: nextOffsetValue, updateActivity: self.updateActivity)
|
signal = paneGifSearchForQuery(account: self.context.account, query: text, offset: nextOffsetValue, updateActivity: self.updateActivity)
|
||||||
|
|> map { result -> ([MultiplexedVideoNodeFile], String?)? in
|
||||||
|
if let result = result {
|
||||||
|
return (result.files, result.nextOffset)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.searchDisposable.set((signal
|
self.searchDisposable.set((signal
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||||
@ -255,7 +279,7 @@ final class GifPaneSearchContentNode: ASDisplayNode & PaneSearchContentNode {
|
|||||||
} else {
|
} else {
|
||||||
strongSelf.nextOffset = nil
|
strongSelf.nextOffset = nil
|
||||||
}
|
}
|
||||||
strongSelf.multiplexedNode?.setFiles(files: MultiplexedVideoNodeFiles(saved: [], trending: files, isSearch: true), synchronous: true, resetScrollingToOffset: nil)
|
strongSelf.multiplexedNode?.setFiles(files: MultiplexedVideoNodeFiles(saved: [], trending: files, isSearch: true, canLoadMore: false), synchronous: true, resetScrollingToOffset: nil)
|
||||||
strongSelf.notFoundNode.isHidden = text.isEmpty || !files.isEmpty
|
strongSelf.notFoundNode.isHidden = text.isEmpty || !files.isEmpty
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
|
|||||||
private let imageNodeBackground: ASDisplayNode
|
private let imageNodeBackground: ASDisplayNode
|
||||||
private let imageNode: TransformImageNode
|
private let imageNode: TransformImageNode
|
||||||
private var animationNode: AnimatedStickerNode?
|
private var animationNode: AnimatedStickerNode?
|
||||||
private var videoLayer: (SoftwareVideoThumbnailLayer, SoftwareVideoLayerFrameManager, SampleBufferLayer)?
|
private var videoLayer: (SoftwareVideoThumbnailNode, SoftwareVideoLayerFrameManager, SampleBufferLayer)?
|
||||||
private var currentImageResource: TelegramMediaResource?
|
private var currentImageResource: TelegramMediaResource?
|
||||||
private var currentVideoFile: TelegramMediaFile?
|
private var currentVideoFile: TelegramMediaFile?
|
||||||
private var currentAnimatedStickerFile: TelegramMediaFile?
|
private var currentAnimatedStickerFile: TelegramMediaFile?
|
||||||
@ -346,14 +346,14 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode
|
|||||||
if updatedVideoFile {
|
if updatedVideoFile {
|
||||||
if let (thumbnailLayer, _, layer) = strongSelf.videoLayer {
|
if let (thumbnailLayer, _, layer) = strongSelf.videoLayer {
|
||||||
strongSelf.videoLayer = nil
|
strongSelf.videoLayer = nil
|
||||||
thumbnailLayer.removeFromSuperlayer()
|
thumbnailLayer.removeFromSupernode()
|
||||||
layer.layer.removeFromSuperlayer()
|
layer.layer.removeFromSuperlayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let videoFile = videoFile {
|
if let videoFile = videoFile {
|
||||||
let thumbnailLayer = SoftwareVideoThumbnailLayer(account: item.account, fileReference: .standalone(media: videoFile), synchronousLoad: synchronousLoads)
|
let thumbnailLayer = SoftwareVideoThumbnailNode(account: item.account, fileReference: .standalone(media: videoFile), synchronousLoad: synchronousLoads)
|
||||||
thumbnailLayer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
thumbnailLayer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
strongSelf.layer.addSublayer(thumbnailLayer)
|
strongSelf.addSubnode(thumbnailLayer)
|
||||||
let layerHolder = takeSampleBufferLayer()
|
let layerHolder = takeSampleBufferLayer()
|
||||||
layerHolder.layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
layerHolder.layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||||
layerHolder.layer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
layerHolder.layer.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
|
||||||
|
@ -9,6 +9,34 @@ import SyncCore
|
|||||||
import AVFoundation
|
import AVFoundation
|
||||||
import ContextUI
|
import ContextUI
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import ShimmerEffect
|
||||||
|
|
||||||
|
final class MultiplexedVideoPlaceholderNode: ASDisplayNode {
|
||||||
|
private let effectNode: ShimmerEffectNode
|
||||||
|
private var theme: PresentationTheme?
|
||||||
|
private var size: CGSize?
|
||||||
|
|
||||||
|
override init() {
|
||||||
|
self.effectNode = ShimmerEffectNode()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.addSubnode(self.effectNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(size: CGSize, theme: PresentationTheme) {
|
||||||
|
if self.theme === theme && self.size == size {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.effectNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
self.effectNode.update(backgroundColor: theme.chat.inputPanel.panelBackgroundColor, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.mixedWith(theme.chat.inputPanel.panelBackgroundColor, alpha: 0.72), shimmeringColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.3), shapes: [.rect(rect: CGRect(origin: CGPoint(), size: size))], size: bounds.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
|
||||||
|
self.effectNode.updateAbsoluteRect(absoluteRect, within: containerSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final class MultiplexedVideoTrackingNode: ASDisplayNode {
|
private final class MultiplexedVideoTrackingNode: ASDisplayNode {
|
||||||
var inHierarchyUpdated: ((Bool) -> Void)?
|
var inHierarchyUpdated: ((Bool) -> Void)?
|
||||||
@ -60,11 +88,13 @@ final class MultiplexedVideoNodeFiles {
|
|||||||
let saved: [MultiplexedVideoNodeFile]
|
let saved: [MultiplexedVideoNodeFile]
|
||||||
let trending: [MultiplexedVideoNodeFile]
|
let trending: [MultiplexedVideoNodeFile]
|
||||||
let isSearch: Bool
|
let isSearch: Bool
|
||||||
|
let canLoadMore: Bool
|
||||||
|
|
||||||
init(saved: [MultiplexedVideoNodeFile], trending: [MultiplexedVideoNodeFile], isSearch: Bool) {
|
init(saved: [MultiplexedVideoNodeFile], trending: [MultiplexedVideoNodeFile], isSearch: Bool, canLoadMore: Bool) {
|
||||||
self.saved = saved
|
self.saved = saved
|
||||||
self.trending = trending
|
self.trending = trending
|
||||||
self.isSearch = isSearch
|
self.isSearch = isSearch
|
||||||
|
self.canLoadMore = canLoadMore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +125,7 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) var files: MultiplexedVideoNodeFiles = MultiplexedVideoNodeFiles(saved: [], trending: [], isSearch: false)
|
private(set) var files: MultiplexedVideoNodeFiles = MultiplexedVideoNodeFiles(saved: [], trending: [], isSearch: false, canLoadMore: false)
|
||||||
|
|
||||||
func setFiles(files: MultiplexedVideoNodeFiles, synchronous: Bool, resetScrollingToOffset: CGFloat?) {
|
func setFiles(files: MultiplexedVideoNodeFiles, synchronous: Bool, resetScrollingToOffset: CGFloat?) {
|
||||||
self.files = files
|
self.files = files
|
||||||
@ -109,15 +139,14 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var displayItems: [VisibleVideoItem] = []
|
private var displayItems: [VisibleVideoItem] = []
|
||||||
private var visibleThumbnailLayers: [VisibleVideoItem.Id: SoftwareVideoThumbnailLayer] = [:]
|
private var visibleThumbnailLayers: [VisibleVideoItem.Id: SoftwareVideoThumbnailNode] = [:]
|
||||||
private var statusDisposable: [VisibleVideoItem.Id: MetaDisposable] = [:]
|
private var visiblePlaceholderNodes: [Int: MultiplexedVideoPlaceholderNode] = [:]
|
||||||
|
|
||||||
private let contextContainerNode: ContextControllerSourceNode
|
private let contextContainerNode: ContextControllerSourceNode
|
||||||
let scrollNode: ASScrollNode
|
let scrollNode: ASScrollNode
|
||||||
|
|
||||||
private var visibleLayers: [VisibleVideoItem.Id: (SoftwareVideoLayerFrameManager, SampleBufferLayer)] = [:]
|
private var visibleLayers: [VisibleVideoItem.Id: (SoftwareVideoLayerFrameManager, SampleBufferLayer)] = [:]
|
||||||
|
|
||||||
private let savedTitleNode: ImmediateTextNode
|
|
||||||
private let trendingTitleNode: ImmediateTextNode
|
private let trendingTitleNode: ImmediateTextNode
|
||||||
|
|
||||||
private var displayLink: CADisplayLink!
|
private var displayLink: CADisplayLink!
|
||||||
@ -145,9 +174,6 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.contextContainerNode = ContextControllerSourceNode()
|
self.contextContainerNode = ContextControllerSourceNode()
|
||||||
self.scrollNode = ASScrollNode()
|
self.scrollNode = ASScrollNode()
|
||||||
|
|
||||||
self.savedTitleNode = ImmediateTextNode()
|
|
||||||
self.savedTitleNode.attributedText = NSAttributedString(string: strings.Chat_Gifs_SavedSectionHeader, font: Font.medium(12.0), textColor: theme.chat.inputMediaPanel.stickersSectionTextColor)
|
|
||||||
|
|
||||||
self.trendingTitleNode = ImmediateTextNode()
|
self.trendingTitleNode = ImmediateTextNode()
|
||||||
self.trendingTitleNode.attributedText = NSAttributedString(string: strings.Chat_Gifs_TrendingSectionHeader, font: Font.medium(12.0), textColor: theme.chat.inputMediaPanel.stickersSectionTextColor)
|
self.trendingTitleNode.attributedText = NSAttributedString(string: strings.Chat_Gifs_TrendingSectionHeader, font: Font.medium(12.0), textColor: theme.chat.inputMediaPanel.stickersSectionTextColor)
|
||||||
|
|
||||||
@ -158,7 +184,6 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||||
self.scrollNode.view.alwaysBounceVertical = true
|
self.scrollNode.view.alwaysBounceVertical = true
|
||||||
|
|
||||||
self.scrollNode.addSubnode(self.savedTitleNode)
|
|
||||||
self.scrollNode.addSubnode(self.trendingTitleNode)
|
self.scrollNode.addSubnode(self.trendingTitleNode)
|
||||||
|
|
||||||
self.addSubnode(self.trackingNode)
|
self.addSubnode(self.trackingNode)
|
||||||
@ -255,9 +280,6 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
deinit {
|
deinit {
|
||||||
self.displayLink.invalidate()
|
self.displayLink.invalidate()
|
||||||
self.displayLink.isPaused = true
|
self.displayLink.isPaused = true
|
||||||
for(_, disposable) in self.statusDisposable {
|
|
||||||
disposable.dispose()
|
|
||||||
}
|
|
||||||
for (_, value) in self.visibleLayers {
|
for (_, value) in self.visibleLayers {
|
||||||
value.1.isFreed = true
|
value.1.isFreed = true
|
||||||
}
|
}
|
||||||
@ -310,9 +332,18 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
private var validVisibleItemsOffset: CGFloat?
|
private var validVisibleItemsOffset: CGFloat?
|
||||||
private func updateImmediatelyVisibleItems(ensureFrames: Bool = false, synchronous: Bool = false) {
|
private func updateImmediatelyVisibleItems(ensureFrames: Bool = false, synchronous: Bool = false) {
|
||||||
var visibleBounds = self.scrollNode.bounds
|
var visibleBounds = self.scrollNode.bounds
|
||||||
|
let containerSize = visibleBounds.size
|
||||||
visibleBounds.size.height += max(0.0, self.currentExtendSizeForTransition)
|
visibleBounds.size.height += max(0.0, self.currentExtendSizeForTransition)
|
||||||
let visibleThumbnailBounds = visibleBounds.insetBy(dx: 0.0, dy: -350.0)
|
let visibleThumbnailBounds = visibleBounds.insetBy(dx: 0.0, dy: -350.0)
|
||||||
|
|
||||||
|
let containerWidth = containerSize.width
|
||||||
|
let itemSpacing: CGFloat = 1.0
|
||||||
|
let itemsInRow = max(3, min(6, Int(containerWidth / 140.0)))
|
||||||
|
let itemSize: CGFloat = floor(containerWidth / CGFloat(itemsInRow))
|
||||||
|
|
||||||
|
let absoluteContainerSize = CGSize(width: containerSize.width, height: containerSize.height)
|
||||||
|
let absoluteContainerOffset = -visibleBounds.origin.y
|
||||||
|
|
||||||
if let validVisibleItemsOffset = self.validVisibleItemsOffset, validVisibleItemsOffset.isEqual(to: visibleBounds.origin.y) {
|
if let validVisibleItemsOffset = self.validVisibleItemsOffset, validVisibleItemsOffset.isEqual(to: visibleBounds.origin.y) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -326,27 +357,45 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
var visibleThumbnailIds = Set<VisibleVideoItem.Id>()
|
var visibleThumbnailIds = Set<VisibleVideoItem.Id>()
|
||||||
var visibleIds = Set<VisibleVideoItem.Id>()
|
var visibleIds = Set<VisibleVideoItem.Id>()
|
||||||
|
|
||||||
for item in self.displayItems {
|
var maxVisibleIndex = -1
|
||||||
|
|
||||||
|
for index in 0 ..< self.displayItems.count {
|
||||||
|
let item = self.displayItems[index]
|
||||||
|
|
||||||
if item.frame.maxY < minVisibleThumbnailY {
|
if item.frame.maxY < minVisibleThumbnailY {
|
||||||
continue;
|
continue
|
||||||
}
|
}
|
||||||
if item.frame.minY > maxVisibleThumbnailY {
|
if item.frame.minY > maxVisibleThumbnailY {
|
||||||
break;
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maxVisibleIndex = max(maxVisibleIndex, index)
|
||||||
|
|
||||||
visibleThumbnailIds.insert(item.id)
|
visibleThumbnailIds.insert(item.id)
|
||||||
|
|
||||||
if let thumbnailLayer = self.visibleThumbnailLayers[item.id] {
|
let thumbnailLayer: SoftwareVideoThumbnailNode
|
||||||
|
if let current = self.visibleThumbnailLayers[item.id] {
|
||||||
|
thumbnailLayer = current
|
||||||
if ensureFrames {
|
if ensureFrames {
|
||||||
thumbnailLayer.frame = item.frame
|
thumbnailLayer.frame = item.frame
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let thumbnailLayer = SoftwareVideoThumbnailLayer(account: self.account, fileReference: item.file.file, synchronousLoad: synchronous)
|
var existingPlaceholderNode: MultiplexedVideoPlaceholderNode?
|
||||||
|
if let placeholderNode = self.visiblePlaceholderNodes[index] {
|
||||||
|
existingPlaceholderNode = placeholderNode
|
||||||
|
self.visiblePlaceholderNodes.removeValue(forKey: index)
|
||||||
|
placeholderNode.removeFromSupernode()
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbnailLayer = SoftwareVideoThumbnailNode(account: self.account, fileReference: item.file.file, synchronousLoad: synchronous, usePlaceholder: true, existingPlaceholder: existingPlaceholderNode)
|
||||||
thumbnailLayer.frame = item.frame
|
thumbnailLayer.frame = item.frame
|
||||||
self.scrollNode.layer.addSublayer(thumbnailLayer)
|
self.scrollNode.addSubnode(thumbnailLayer)
|
||||||
self.visibleThumbnailLayers[item.id] = thumbnailLayer
|
self.visibleThumbnailLayers[item.id] = thumbnailLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thumbnailLayer.update(theme: self.theme, size: item.frame.size)
|
||||||
|
thumbnailLayer.updateAbsoluteRect(item.frame.offsetBy(dx: 0.0, dy: absoluteContainerOffset), within: absoluteContainerSize)
|
||||||
|
|
||||||
if item.frame.maxY < minVisibleY {
|
if item.frame.maxY < minVisibleY {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -375,6 +424,43 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var visiblePlaceholderIndices = Set<Int>()
|
||||||
|
if self.files.canLoadMore {
|
||||||
|
let verticalOffset: CGFloat = self.topInset
|
||||||
|
|
||||||
|
let sideInset: CGFloat = 0.0
|
||||||
|
|
||||||
|
var indexImpl = maxVisibleIndex + 1
|
||||||
|
while true {
|
||||||
|
let index = indexImpl
|
||||||
|
indexImpl += 1
|
||||||
|
|
||||||
|
let rowIndex = index / Int(itemsInRow)
|
||||||
|
let columnIndex = index % Int(itemsInRow)
|
||||||
|
let itemOrigin = CGPoint(x: sideInset + CGFloat(columnIndex) * (itemSize + itemSpacing), y: verticalOffset + itemSpacing + CGFloat(rowIndex) * (itemSize + itemSpacing))
|
||||||
|
let itemFrame = CGRect(origin: itemOrigin, size: CGSize(width: columnIndex == itemsInRow ? (containerWidth - itemOrigin.x) : itemSize, height: itemSize))
|
||||||
|
if itemFrame.maxY < minVisibleY {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if itemFrame.minY > maxVisibleY {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
visiblePlaceholderIndices.insert(index)
|
||||||
|
|
||||||
|
let placeholderNode: MultiplexedVideoPlaceholderNode
|
||||||
|
if let current = self.visiblePlaceholderNodes[index] {
|
||||||
|
placeholderNode = current
|
||||||
|
} else {
|
||||||
|
placeholderNode = MultiplexedVideoPlaceholderNode()
|
||||||
|
self.visiblePlaceholderNodes[index] = placeholderNode
|
||||||
|
self.scrollNode.addSubnode(placeholderNode)
|
||||||
|
}
|
||||||
|
placeholderNode.frame = itemFrame
|
||||||
|
placeholderNode.update(size: itemFrame.size, theme: self.theme)
|
||||||
|
placeholderNode.updateAbsoluteRect(itemFrame.offsetBy(dx: 0.0, dy: absoluteContainerOffset), within: absoluteContainerSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var removeIds: [VisibleVideoItem.Id] = []
|
var removeIds: [VisibleVideoItem.Id] = []
|
||||||
for id in self.visibleLayers.keys {
|
for id in self.visibleLayers.keys {
|
||||||
if !visibleIds.contains(id) {
|
if !visibleIds.contains(id) {
|
||||||
@ -389,12 +475,12 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*var removeProgressIds: [MediaId] = []
|
var removePlaceholderIndices: [Int] = []
|
||||||
for id in self.visibleProgressNodes.keys {
|
for index in self.visiblePlaceholderNodes.keys {
|
||||||
if !visibleIds.contains(id) {
|
if !visiblePlaceholderIndices.contains(index) {
|
||||||
removeProgressIds.append(id)
|
removePlaceholderIndices.append(index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}*/
|
|
||||||
|
|
||||||
for id in removeIds {
|
for id in removeIds {
|
||||||
let (_, layerHolder) = self.visibleLayers[id]!
|
let (_, layerHolder) = self.visibleLayers[id]!
|
||||||
@ -404,16 +490,16 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
for id in removeThumbnailIds {
|
for id in removeThumbnailIds {
|
||||||
let thumbnailLayer = self.visibleThumbnailLayers[id]!
|
let thumbnailLayer = self.visibleThumbnailLayers[id]!
|
||||||
thumbnailLayer.removeFromSuperlayer()
|
thumbnailLayer.removeFromSupernode()
|
||||||
self.visibleThumbnailLayers.removeValue(forKey: id)
|
self.visibleThumbnailLayers.removeValue(forKey: id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*for id in removeProgressIds {
|
for index in removePlaceholderIndices {
|
||||||
let progressNode = self.visibleProgressNodes[id]!
|
if let placeholderNode = self.visiblePlaceholderNodes[index] {
|
||||||
progressNode.removeFromSupernode()
|
placeholderNode.removeFromSupernode()
|
||||||
self.visibleProgressNodes.removeValue(forKey: id)
|
self.visiblePlaceholderNodes.removeValue(forKey: index)
|
||||||
self.statusDisposable.removeValue(forKey: id)?.dispose()
|
}
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateVisibleItems(extendSizeForTransition: CGFloat, transition: ContainedViewLayoutTransition, synchronous: Bool = false) {
|
private func updateVisibleItems(extendSizeForTransition: CGFloat, transition: ContainedViewLayoutTransition, synchronous: Bool = false) {
|
||||||
@ -423,6 +509,29 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
var verticalOffset: CGFloat = self.topInset
|
var verticalOffset: CGFloat = self.topInset
|
||||||
|
|
||||||
|
func commitFileGrid(files: [MultiplexedVideoNodeFile], isTrending: Bool) {
|
||||||
|
let containerWidth = drawableSize.width
|
||||||
|
let itemCount = files.count
|
||||||
|
let itemSpacing: CGFloat = 1.0
|
||||||
|
let itemsInRow = max(3, min(6, Int(containerWidth / 140.0)))
|
||||||
|
let itemSize: CGFloat = floor(containerWidth / CGFloat(itemsInRow))
|
||||||
|
|
||||||
|
let rowCount = itemCount / itemsInRow + (itemCount % itemsInRow == 0 ? 0 : 1)
|
||||||
|
|
||||||
|
let sideInset: CGFloat = 0.0
|
||||||
|
|
||||||
|
for index in 0 ..< itemCount {
|
||||||
|
let rowIndex = index / Int(itemsInRow)
|
||||||
|
let columnIndex = index % Int(itemsInRow)
|
||||||
|
let itemOrigin = CGPoint(x: sideInset + CGFloat(columnIndex) * (itemSize + itemSpacing), y: verticalOffset + itemSpacing + CGFloat(rowIndex) * (itemSize + itemSpacing))
|
||||||
|
let itemFrame = CGRect(origin: itemOrigin, size: CGSize(width: columnIndex == itemsInRow ? (containerWidth - itemOrigin.x) : itemSize, height: itemSize))
|
||||||
|
displayItems.append(VisibleVideoItem(file: files[index], frame: itemFrame, isTrending: isTrending))
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentHeight = CGFloat(rowCount + 1) * itemSpacing + CGFloat(rowCount) * itemSize
|
||||||
|
verticalOffset += contentHeight
|
||||||
|
}
|
||||||
|
|
||||||
func commitFilesSpans(files: [MultiplexedVideoNodeFile], isTrending: Bool) {
|
func commitFilesSpans(files: [MultiplexedVideoNodeFile], isTrending: Bool) {
|
||||||
var rowsCount = 0
|
var rowsCount = 0
|
||||||
var firstRowMax = 0;
|
var firstRowMax = 0;
|
||||||
@ -529,15 +638,8 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
var hasContent = false
|
var hasContent = false
|
||||||
if !self.files.saved.isEmpty {
|
if !self.files.saved.isEmpty {
|
||||||
self.savedTitleNode.isHidden = false
|
commitFileGrid(files: self.files.saved, isTrending: false)
|
||||||
let leftInset: CGFloat = 10.0
|
|
||||||
let savedTitleSize = self.savedTitleNode.updateLayout(CGSize(width: drawableSize.width - leftInset * 2.0, height: 100.0))
|
|
||||||
self.savedTitleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalOffset - 3.0), size: savedTitleSize)
|
|
||||||
verticalOffset += savedTitleSize.height + 5.0
|
|
||||||
commitFilesSpans(files: self.files.saved, isTrending: false)
|
|
||||||
hasContent = true
|
hasContent = true
|
||||||
} else {
|
|
||||||
self.savedTitleNode.isHidden = true
|
|
||||||
}
|
}
|
||||||
if !self.files.trending.isEmpty {
|
if !self.files.trending.isEmpty {
|
||||||
if self.files.isSearch {
|
if self.files.isSearch {
|
||||||
@ -545,14 +647,14 @@ final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
|
|||||||
} else {
|
} else {
|
||||||
self.trendingTitleNode.isHidden = false
|
self.trendingTitleNode.isHidden = false
|
||||||
if hasContent {
|
if hasContent {
|
||||||
verticalOffset += 15.0
|
verticalOffset += 16.0
|
||||||
}
|
}
|
||||||
let leftInset: CGFloat = 10.0
|
let leftInset: CGFloat = 10.0
|
||||||
let trendingTitleSize = self.trendingTitleNode.updateLayout(CGSize(width: drawableSize.width - leftInset * 2.0, height: 100.0))
|
let trendingTitleSize = self.trendingTitleNode.updateLayout(CGSize(width: drawableSize.width - leftInset * 2.0, height: 100.0))
|
||||||
self.trendingTitleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalOffset - 3.0), size: trendingTitleSize)
|
self.trendingTitleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: verticalOffset - 3.0), size: trendingTitleSize)
|
||||||
verticalOffset += trendingTitleSize.height + 5.0
|
verticalOffset += trendingTitleSize.height + 5.0
|
||||||
}
|
}
|
||||||
commitFilesSpans(files: self.files.trending, isTrending: true)
|
commitFileGrid(files: self.files.trending, isTrending: true)
|
||||||
} else {
|
} else {
|
||||||
self.trendingTitleNode.isHidden = true
|
self.trendingTitleNode.isHidden = true
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ final class PaneSearchContainerNode: ASDisplayNode {
|
|||||||
return self.contentNode.ready
|
return self.contentNode.ready
|
||||||
}
|
}
|
||||||
|
|
||||||
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, mode: ChatMediaInputSearchMode, trendingGifsPromise: Promise<[MultiplexedVideoNodeFile]?>, cancel: @escaping () -> Void) {
|
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, mode: ChatMediaInputSearchMode, trendingGifsPromise: Promise<ChatMediaInputGifPaneTrendingState?>, cancel: @escaping () -> Void) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
|
@ -201,6 +201,10 @@ private final class VisualMediaItemNode: ASDisplayNode {
|
|||||||
self.videoLayerFrameManager?.start()
|
self.videoLayerFrameManager?.start()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if let sampleBufferLayer = self.sampleBufferLayer {
|
||||||
|
sampleBufferLayer.layer.removeFromSuperlayer()
|
||||||
|
self.sampleBufferLayer = nil
|
||||||
|
}
|
||||||
self.videoLayerFrameManager = nil
|
self.videoLayerFrameManager = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -571,9 +575,9 @@ private enum ItemsLayout {
|
|||||||
return (i, j - 1)
|
return (i, j - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return (i, self.frames.count - 1)
|
return (i, self.frames.count - 1)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return (0, -1)
|
return (0, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -873,10 +877,10 @@ final class PeerInfoVisualMediaPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScro
|
|||||||
itemsLayout = current
|
itemsLayout = current
|
||||||
} else {
|
} else {
|
||||||
switch self.contentType {
|
switch self.contentType {
|
||||||
case .photoOrVideo:
|
case .photoOrVideo, .gifs:
|
||||||
itemsLayout = .grid(ItemsLayout.Grid(containerWidth: availableWidth, itemCount: self.mediaItems.count, bottomInset: bottomInset))
|
itemsLayout = .grid(ItemsLayout.Grid(containerWidth: availableWidth, itemCount: self.mediaItems.count, bottomInset: bottomInset))
|
||||||
case .gifs:
|
/*case .gifs:
|
||||||
itemsLayout = .balanced(ItemsLayout.Balanced(containerWidth: availableWidth, items: self.mediaItems, bottomInset: bottomInset))
|
itemsLayout = .balanced(ItemsLayout.Balanced(containerWidth: availableWidth, items: self.mediaItems, bottomInset: bottomInset))*/
|
||||||
}
|
}
|
||||||
self.itemsLayout = itemsLayout
|
self.itemsLayout = itemsLayout
|
||||||
}
|
}
|
||||||
|
@ -216,7 +216,7 @@ final class PeerInfoPaneTabsContainerNode: ASDisplayNode {
|
|||||||
totalRawTabSize += paneNodeSize.width
|
totalRawTabSize += paneNodeSize.width
|
||||||
}
|
}
|
||||||
|
|
||||||
let minSpacing: CGFloat = 10.0
|
let minSpacing: CGFloat = 26.0
|
||||||
if tabSizes.count <= 1 {
|
if tabSizes.count <= 1 {
|
||||||
for i in 0 ..< tabSizes.count {
|
for i in 0 ..< tabSizes.count {
|
||||||
let (paneNodeSize, paneNode, wasAdded) = tabSizes[i]
|
let (paneNodeSize, paneNode, wasAdded) = tabSizes[i]
|
||||||
|
@ -54,11 +54,14 @@ func takeSampleBufferLayer() -> SampleBufferLayer {
|
|||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
layer.flushAndRemoveImage()
|
layer.flushAndRemoveImage()
|
||||||
layer.setAffineTransform(CGAffineTransform.identity)
|
layer.setAffineTransform(CGAffineTransform.identity)
|
||||||
|
#if targetEnvironment(simulator)
|
||||||
|
#else
|
||||||
let _ = pool.modify { list in
|
let _ = pool.modify { list in
|
||||||
var list = list
|
var list = list
|
||||||
list.append(layer)
|
list.append(layer)
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -6,32 +6,55 @@ import Postbox
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import Display
|
import Display
|
||||||
import PhotoResources
|
import PhotoResources
|
||||||
|
import TelegramPresentationData
|
||||||
|
import AsyncDisplayKit
|
||||||
|
|
||||||
private final class SoftwareVideoThumbnailLayerNullAction: NSObject, CAAction {
|
private final class SoftwareVideoThumbnailLayerNullAction: NSObject, CAAction {
|
||||||
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
|
@objc func run(forKey event: String, object anObject: Any, arguments dict: [AnyHashable : Any]?) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class SoftwareVideoThumbnailLayer: CALayer {
|
final class SoftwareVideoThumbnailNode: ASDisplayNode {
|
||||||
|
private let usePlaceholder: Bool
|
||||||
|
private var placeholder: MultiplexedVideoPlaceholderNode?
|
||||||
|
private var theme: PresentationTheme?
|
||||||
|
private var asolutePosition: (CGRect, CGSize)?
|
||||||
|
|
||||||
var disposable = MetaDisposable()
|
var disposable = MetaDisposable()
|
||||||
|
|
||||||
var ready: (() -> Void)? {
|
var ready: (() -> Void)? {
|
||||||
didSet {
|
didSet {
|
||||||
if self.contents != nil {
|
if self.layer.contents != nil {
|
||||||
self.ready?()
|
self.ready?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(account: Account, fileReference: FileMediaReference, synchronousLoad: Bool) {
|
init(account: Account, fileReference: FileMediaReference, synchronousLoad: Bool, usePlaceholder: Bool = false, existingPlaceholder: MultiplexedVideoPlaceholderNode? = nil) {
|
||||||
|
self.usePlaceholder = usePlaceholder
|
||||||
|
if usePlaceholder {
|
||||||
|
self.placeholder = existingPlaceholder
|
||||||
|
} else {
|
||||||
|
self.placeholder = nil
|
||||||
|
}
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.backgroundColor = UIColor.clear.cgColor
|
if !usePlaceholder {
|
||||||
self.contentsGravity = .resizeAspectFill
|
self.isLayerBacked = true
|
||||||
self.masksToBounds = true
|
}
|
||||||
|
|
||||||
|
if let placeholder = self.placeholder {
|
||||||
|
self.addSubnode(placeholder)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.backgroundColor = UIColor.clear
|
||||||
|
self.layer.contentsGravity = .resizeAspectFill
|
||||||
|
self.layer.masksToBounds = true
|
||||||
|
|
||||||
if let dimensions = fileReference.media.dimensions {
|
if let dimensions = fileReference.media.dimensions {
|
||||||
self.disposable.set((mediaGridMessageVideo(postbox: account.postbox, videoReference: fileReference, synchronousLoad: synchronousLoad)).start(next: { [weak self] transform in
|
self.disposable.set((mediaGridMessageVideo(postbox: account.postbox, videoReference: fileReference, synchronousLoad: synchronousLoad, nilForEmptyResult: true)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] transform in
|
||||||
var boundingSize = dimensions.cgSize.aspectFilled(CGSize(width: 93.0, height: 93.0))
|
var boundingSize = dimensions.cgSize.aspectFilled(CGSize(width: 93.0, height: 93.0))
|
||||||
let imageSize = boundingSize
|
let imageSize = boundingSize
|
||||||
boundingSize.width = min(200.0, boundingSize.width)
|
boundingSize.width = min(200.0, boundingSize.width)
|
||||||
@ -40,9 +63,31 @@ final class SoftwareVideoThumbnailLayer: CALayer {
|
|||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.contents = image.cgImage
|
strongSelf.contents = image.cgImage
|
||||||
|
if let placeholder = strongSelf.placeholder {
|
||||||
|
strongSelf.placeholder = placeholder
|
||||||
|
placeholder.removeFromSupernode()
|
||||||
|
}
|
||||||
strongSelf.ready?()
|
strongSelf.ready?()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strongSelf.usePlaceholder && strongSelf.placeholder == nil {
|
||||||
|
let placeholder = MultiplexedVideoPlaceholderNode()
|
||||||
|
strongSelf.placeholder = placeholder
|
||||||
|
strongSelf.addSubnode(placeholder)
|
||||||
|
placeholder.frame = strongSelf.bounds
|
||||||
|
if let theme = strongSelf.theme {
|
||||||
|
placeholder.update(size: strongSelf.bounds.size, theme: theme)
|
||||||
|
}
|
||||||
|
if let (absoluteRect, containerSize) = strongSelf.asolutePosition {
|
||||||
|
placeholder.updateAbsoluteRect(absoluteRect, within: containerSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -56,7 +101,24 @@ final class SoftwareVideoThumbnailLayer: CALayer {
|
|||||||
self.disposable.dispose()
|
self.disposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func action(forKey event: String) -> CAAction? {
|
func update(theme: PresentationTheme, size: CGSize) {
|
||||||
|
if self.usePlaceholder {
|
||||||
|
self.theme = theme
|
||||||
|
}
|
||||||
|
if let placeholder = self.placeholder {
|
||||||
|
placeholder.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
placeholder.update(size: size, theme: theme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateAbsoluteRect(_ absoluteRect: CGRect, within containerSize: CGSize) {
|
||||||
|
self.asolutePosition = (absoluteRect, containerSize)
|
||||||
|
if let placeholder = self.placeholder {
|
||||||
|
placeholder.updateAbsoluteRect(absoluteRect, within: containerSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*override func action(forKey event: String) -> CAAction? {
|
||||||
return SoftwareVideoThumbnailLayerNullAction()
|
return SoftwareVideoThumbnailLayerNullAction()
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user