mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-02 00:17:02 +00:00
Improve gif input
This commit is contained in:
parent
69ee217fd7
commit
a23934a51d
@ -7,18 +7,19 @@ import TelegramCore
|
|||||||
import SyncCore
|
import SyncCore
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramPresentationData
|
import TelegramPresentationData
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
private func fixListScrolling(_ multiplexedNode: MultiplexedVideoNode) {
|
private func fixListScrolling(_ multiplexedNode: MultiplexedVideoNode) {
|
||||||
let searchBarHeight: CGFloat = 56.0
|
let searchBarHeight: CGFloat = 56.0
|
||||||
|
|
||||||
let contentOffset = multiplexedNode.view.contentOffset.y
|
let contentOffset = multiplexedNode.scrollNode.view.contentOffset.y
|
||||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
|
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .easeInOut)
|
||||||
|
|
||||||
if contentOffset < 60.0 {
|
if contentOffset < 60.0 {
|
||||||
if contentOffset < searchBarHeight * 0.6 {
|
if contentOffset < searchBarHeight * 0.6 {
|
||||||
transition.updateBounds(layer: multiplexedNode.layer, bounds: CGRect(origin: CGPoint(), size: multiplexedNode.bounds.size))
|
transition.updateBounds(layer: multiplexedNode.scrollNode.layer, bounds: CGRect(origin: CGPoint(), size: multiplexedNode.bounds.size))
|
||||||
} else {
|
} else {
|
||||||
transition.updateBounds(layer: multiplexedNode.layer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 60.0), size: multiplexedNode.bounds.size))
|
transition.updateBounds(layer: multiplexedNode.scrollNode.layer, bounds: CGRect(origin: CGPoint(x: 0.0, y: 60.0), size: multiplexedNode.bounds.size))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -29,6 +30,7 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
|
|
||||||
private let paneDidScroll: (ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void
|
private let paneDidScroll: (ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void
|
||||||
private let fixPaneScroll: (ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void
|
private let fixPaneScroll: (ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void
|
||||||
|
private let openGifContextMenu: (FileMediaReference, ASDisplayNode, CGRect, ContextGesture) -> Void
|
||||||
|
|
||||||
let searchPlaceholderNode: PaneSearchBarPlaceholderNode
|
let searchPlaceholderNode: PaneSearchBarPlaceholderNode
|
||||||
private var multiplexedNode: MultiplexedVideoNode?
|
private var multiplexedNode: MultiplexedVideoNode?
|
||||||
@ -42,11 +44,12 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
|
|
||||||
private var didScrollPreviousState: ChatMediaInputPaneScrollState?
|
private var didScrollPreviousState: ChatMediaInputPaneScrollState?
|
||||||
|
|
||||||
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, paneDidScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void, fixPaneScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void) {
|
init(account: Account, theme: PresentationTheme, strings: PresentationStrings, controllerInteraction: ChatControllerInteraction, paneDidScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void, fixPaneScroll: @escaping (ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void, openGifContextMenu: @escaping (FileMediaReference, ASDisplayNode, CGRect, ContextGesture) -> Void) {
|
||||||
self.account = account
|
self.account = account
|
||||||
self.controllerInteraction = controllerInteraction
|
self.controllerInteraction = controllerInteraction
|
||||||
self.paneDidScroll = paneDidScroll
|
self.paneDidScroll = paneDidScroll
|
||||||
self.fixPaneScroll = fixPaneScroll
|
self.fixPaneScroll = fixPaneScroll
|
||||||
|
self.openGifContextMenu = openGifContextMenu
|
||||||
|
|
||||||
self.searchPlaceholderNode = PaneSearchBarPlaceholderNode()
|
self.searchPlaceholderNode = PaneSearchBarPlaceholderNode()
|
||||||
|
|
||||||
@ -94,7 +97,7 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
transition.updateFrame(node: self.emptyNode, frame: CGRect(origin: CGPoint(x: floor(size.width - emptySize.width) / 2.0, y: topInset + floor(size.height - topInset - emptySize.height) / 2.0), size: emptySize))
|
transition.updateFrame(node: self.emptyNode, frame: CGRect(origin: CGPoint(x: floor(size.width - emptySize.width) / 2.0, y: topInset + floor(size.height - topInset - emptySize.height) / 2.0), size: emptySize))
|
||||||
|
|
||||||
if let multiplexedNode = self.multiplexedNode {
|
if let multiplexedNode = self.multiplexedNode {
|
||||||
let previousBounds = multiplexedNode.layer.bounds
|
let previousBounds = multiplexedNode.scrollNode.layer.bounds
|
||||||
multiplexedNode.topInset = topInset + 60.0
|
multiplexedNode.topInset = topInset + 60.0
|
||||||
multiplexedNode.bottomInset = bottomInset
|
multiplexedNode.bottomInset = bottomInset
|
||||||
|
|
||||||
@ -111,8 +114,8 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
targetBounds.origin.y = isExpanded || multiplexedNode.files.isEmpty ? 0.0 : 60.0
|
targetBounds.origin.y = isExpanded || multiplexedNode.files.isEmpty ? 0.0 : 60.0
|
||||||
}
|
}
|
||||||
|
|
||||||
transition.updateBounds(layer: multiplexedNode.layer, bounds: targetBounds)
|
transition.updateBounds(layer: multiplexedNode.scrollNode.layer, bounds: targetBounds)
|
||||||
transition.updatePosition(layer: multiplexedNode.layer, position: nodeFrame.center)
|
transition.updateFrame(node: multiplexedNode, frame: nodeFrame)
|
||||||
|
|
||||||
multiplexedNode.updateLayout(size: nodeFrame.size, transition: transition)
|
multiplexedNode.updateLayout(size: nodeFrame.size, transition: transition)
|
||||||
self.searchPlaceholderNode.frame = CGRect(x: 0.0, y: 41.0, width: size.width, height: 56.0)
|
self.searchPlaceholderNode.frame = CGRect(x: 0.0, y: 41.0, width: size.width, height: 56.0)
|
||||||
@ -134,6 +137,10 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
override func willEnterHierarchy() {
|
override func willEnterHierarchy() {
|
||||||
super.willEnterHierarchy()
|
super.willEnterHierarchy()
|
||||||
|
|
||||||
|
self.initializeIfNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
func initializeIfNeeded() {
|
||||||
if self.multiplexedNode == nil {
|
if self.multiplexedNode == nil {
|
||||||
self.trendingPromise.set(paneGifSearchForQuery(account: account, query: "", updateActivity: nil))
|
self.trendingPromise.set(paneGifSearchForQuery(account: account, query: "", updateActivity: nil))
|
||||||
|
|
||||||
@ -144,7 +151,7 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.addSubnode(multiplexedNode)
|
self.addSubnode(multiplexedNode)
|
||||||
multiplexedNode.addSubnode(self.searchPlaceholderNode)
|
multiplexedNode.scrollNode.addSubnode(self.searchPlaceholderNode)
|
||||||
|
|
||||||
let gifs = self.account.postbox.combinedView(keys: [.orderedItemList(id: Namespaces.OrderedItemList.CloudRecentGifs)])
|
let gifs = self.account.postbox.combinedView(keys: [.orderedItemList(id: Namespaces.OrderedItemList.CloudRecentGifs)])
|
||||||
|> map { view -> [FileMediaReference] in
|
|> map { view -> [FileMediaReference] in
|
||||||
@ -161,13 +168,14 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.disposable.set((gifs |> deliverOnMainQueue).start(next: { [weak self] gifs in
|
self.disposable.set((gifs
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] gifs in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
let previousFiles = strongSelf.multiplexedNode?.files
|
let previousFiles = strongSelf.multiplexedNode?.files
|
||||||
strongSelf.multiplexedNode?.files = gifs
|
strongSelf.multiplexedNode?.files = gifs
|
||||||
strongSelf.emptyNode.isHidden = !gifs.isEmpty
|
strongSelf.emptyNode.isHidden = !gifs.isEmpty
|
||||||
if (previousFiles ?? []).isEmpty && !gifs.isEmpty {
|
if (previousFiles ?? []).isEmpty && !gifs.isEmpty {
|
||||||
strongSelf.multiplexedNode?.view.contentOffset = CGPoint(x: 0.0, y: 60.0)
|
strongSelf.multiplexedNode?.scrollNode.view.contentOffset = CGPoint(x: 0.0, y: 60.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
@ -176,6 +184,10 @@ final class ChatMediaInputGifPane: ChatMediaInputPane, UIScrollViewDelegate {
|
|||||||
let _ = self?.controllerInteraction.sendGif(fileReference, sourceNode, sourceRect)
|
let _ = self?.controllerInteraction.sendGif(fileReference, sourceNode, sourceRect)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
multiplexedNode.fileContextMenu = { [weak self] fileReference, sourceNode, sourceRect, gesture in
|
||||||
|
self?.openGifContextMenu(fileReference, sourceNode, sourceRect, gesture)
|
||||||
|
}
|
||||||
|
|
||||||
multiplexedNode.didScroll = { [weak self] offset, height in
|
multiplexedNode.didScroll = { [weak self] offset, height in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
|
@ -12,6 +12,8 @@ import AccountContext
|
|||||||
import StickerPackPreviewUI
|
import StickerPackPreviewUI
|
||||||
import PeerInfoUI
|
import PeerInfoUI
|
||||||
import SettingsUI
|
import SettingsUI
|
||||||
|
import ContextUI
|
||||||
|
import GalleryUI
|
||||||
|
|
||||||
private struct PeerSpecificPackData {
|
private struct PeerSpecificPackData {
|
||||||
let peer: Peer
|
let peer: Peer
|
||||||
@ -466,6 +468,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
|
|
||||||
var paneDidScrollImpl: ((ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void)?
|
var paneDidScrollImpl: ((ChatMediaInputPane, ChatMediaInputPaneScrollState, ContainedViewLayoutTransition) -> Void)?
|
||||||
var fixPaneScrollImpl: ((ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void)?
|
var fixPaneScrollImpl: ((ChatMediaInputPane, ChatMediaInputPaneScrollState) -> Void)?
|
||||||
|
var openGifContextMenuImpl: ((FileMediaReference, ASDisplayNode, CGRect, ContextGesture) -> Void)?
|
||||||
|
|
||||||
self.stickerPane = ChatMediaInputStickerPane(theme: theme, strings: strings, paneDidScroll: { pane, state, transition in
|
self.stickerPane = ChatMediaInputStickerPane(theme: theme, strings: strings, paneDidScroll: { pane, state, transition in
|
||||||
paneDidScrollImpl?(pane, state, transition)
|
paneDidScrollImpl?(pane, state, transition)
|
||||||
@ -476,6 +479,8 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
paneDidScrollImpl?(pane, state, transition)
|
paneDidScrollImpl?(pane, state, transition)
|
||||||
}, fixPaneScroll: { pane, state in
|
}, fixPaneScroll: { pane, state in
|
||||||
fixPaneScrollImpl?(pane, state)
|
fixPaneScrollImpl?(pane, state)
|
||||||
|
}, openGifContextMenu: { fileReference, sourceNode, sourceRect, gesture in
|
||||||
|
openGifContextMenuImpl?(fileReference, sourceNode, sourceRect, gesture)
|
||||||
})
|
})
|
||||||
|
|
||||||
var getItemIsPreviewedImpl: ((StickerPackItem) -> Bool)?
|
var getItemIsPreviewedImpl: ((StickerPackItem) -> Bool)?
|
||||||
@ -806,6 +811,37 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
fixPaneScrollImpl = { [weak self] pane, state in
|
fixPaneScrollImpl = { [weak self] pane, state in
|
||||||
self?.fixPaneScroll(pane: pane, state: state)
|
self?.fixPaneScroll(pane: pane, state: state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openGifContextMenuImpl = { [weak self] fileReference, sourceNode, sourceRect, gesture in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: PeerId(namespace: 0, id: 0), namespace: Namespaces.Message.Local, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [fileReference.media], peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [])
|
||||||
|
|
||||||
|
let gallery = GalleryController(context: strongSelf.context, source: .standaloneMessage(message), streamSingleVideo: true, replaceRootController: { _, _ in
|
||||||
|
}, baseNavigationController: nil)
|
||||||
|
gallery.setHintWillBePresentedInPreviewingContext(true)
|
||||||
|
|
||||||
|
var items: [ContextMenuItem] = []
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strings.MediaPicker_Send, icon: { _ in nil }, action: { _, f in
|
||||||
|
f(.default)
|
||||||
|
self?.controllerInteraction.sendGif(fileReference, sourceNode, sourceRect)
|
||||||
|
})))
|
||||||
|
items.append(.action(ContextMenuActionItem(text: strings.Conversation_ContextMenuDelete, textColor: .destructive, icon: { _ in nil }, action: { _, f in
|
||||||
|
f(.dismissWithoutContent)
|
||||||
|
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let _ = removeSavedGif(postbox: strongSelf.context.account.postbox, mediaId: fileReference.media.fileId).start()
|
||||||
|
})))
|
||||||
|
|
||||||
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
let contextController = ContextController(account: strongSelf.context.account, theme: presentationData.theme, strings: presentationData.strings, source: .controller(ContextControllerContentSourceImpl(controller: gallery, sourceNode: sourceNode, sourceRect: sourceRect)), items: .single(items), reactionItems: [], gesture: gesture)
|
||||||
|
strongSelf.controllerInteraction.presentGlobalOverlayController(contextController, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -903,7 +939,8 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let file = item as? FileMediaReference {
|
} else if let file = item as? FileMediaReference {
|
||||||
return .single((strongSelf, ChatContextResultPeekContent(account: strongSelf.context.account, contextResult: .internalReference(queryId: 0, id: "", type: "gif", title: nil, description: nil, image: nil, file: file.media, message: .auto(caption: "", entities: nil, replyMarkup: nil)), menu: [
|
return nil
|
||||||
|
/*return .single((strongSelf, ChatContextResultPeekContent(account: strongSelf.context.account, contextResult: .internalReference(queryId: 0, id: "", type: "gif", title: nil, description: nil, image: nil, file: file.media, message: .auto(caption: "", entities: nil, replyMarkup: nil)), menu: [
|
||||||
PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, font: .bold, action: { node, rect in
|
PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, font: .bold, action: { node, rect in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
return strongSelf.controllerInteraction.sendGif(file, node, rect)
|
return strongSelf.controllerInteraction.sendGif(file, node, rect)
|
||||||
@ -917,7 +954,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
])))
|
])))*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -932,7 +969,8 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
if pane.supernode != nil, pane.frame.contains(point) {
|
if pane.supernode != nil, pane.frame.contains(point) {
|
||||||
if let pane = pane as? ChatMediaInputGifPane {
|
if let pane = pane as? ChatMediaInputGifPane {
|
||||||
if let (file, _) = pane.fileAt(point: point.offsetBy(dx: -pane.frame.minX, dy: -pane.frame.minY)) {
|
if let (file, _) = pane.fileAt(point: point.offsetBy(dx: -pane.frame.minX, dy: -pane.frame.minY)) {
|
||||||
return .single((strongSelf, ChatContextResultPeekContent(account: strongSelf.context.account, contextResult: .internalReference(queryId: 0, id: "", type: "gif", title: nil, description: nil, image: nil, file: file.media, message: .auto(caption: "", entities: nil, replyMarkup: nil)), menu: [
|
return nil
|
||||||
|
/*return .single((strongSelf, ChatContextResultPeekContent(account: strongSelf.context.account, contextResult: .internalReference(queryId: 0, id: "", type: "gif", title: nil, description: nil, image: nil, file: file.media, message: .auto(caption: "", entities: nil, replyMarkup: nil)), menu: [
|
||||||
PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, font: .bold, action: { node, rect in
|
PeekControllerMenuItem(title: strongSelf.strings.ShareMenu_Send, color: .accent, font: .bold, action: { node, rect in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
return strongSelf.controllerInteraction.sendGif(file, node, rect)
|
return strongSelf.controllerInteraction.sendGif(file, node, rect)
|
||||||
@ -946,7 +984,7 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
])))
|
])))*/
|
||||||
}
|
}
|
||||||
} else if pane is ChatMediaInputStickerPane || pane is ChatMediaInputTrendingPane {
|
} else if pane is ChatMediaInputStickerPane || pane is ChatMediaInputTrendingPane {
|
||||||
var itemNodeAndItem: (ASDisplayNode, StickerPackItem)?
|
var itemNodeAndItem: (ASDisplayNode, StickerPackItem)?
|
||||||
@ -1204,6 +1242,8 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
searchMode = mode
|
searchMode = mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let wasVisible = self.validLayout?.10 ?? false
|
||||||
|
|
||||||
self.validLayout = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible)
|
self.validLayout = (width, leftInset, rightInset, bottomInset, standardInputHeight, inputHeight, maximumHeight, inputPanelHeight, interfaceState, deviceMetrics, isVisible)
|
||||||
|
|
||||||
if self.theme !== interfaceState.theme || self.strings !== interfaceState.strings {
|
if self.theme !== interfaceState.theme || self.strings !== interfaceState.strings {
|
||||||
@ -1463,6 +1503,12 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
panRecognizer.isEnabled = !displaySearch
|
panRecognizer.isEnabled = !displaySearch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isVisible && !wasVisible {
|
||||||
|
transition.updateFrame(node: self.gifPane, frame: self.gifPane.frame, force: true, completion: { [weak self] _ in
|
||||||
|
self?.gifPane.initializeIfNeeded()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (standardInputHeight, max(0.0, panelHeight - standardInputHeight))
|
return (standardInputHeight, max(0.0, panelHeight - standardInputHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1658,3 +1704,37 @@ final class ChatMediaInputNode: ChatInputNode {
|
|||||||
}).start()
|
}).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||||
|
let controller: ViewController
|
||||||
|
weak var sourceNode: ASDisplayNode?
|
||||||
|
let sourceRect: CGRect
|
||||||
|
|
||||||
|
let navigationController: NavigationController? = nil
|
||||||
|
|
||||||
|
let passthroughTouches: Bool = false
|
||||||
|
|
||||||
|
init(controller: ViewController, sourceNode: ASDisplayNode?, sourceRect: CGRect) {
|
||||||
|
self.controller = controller
|
||||||
|
self.sourceNode = sourceNode
|
||||||
|
self.sourceRect = sourceRect
|
||||||
|
}
|
||||||
|
|
||||||
|
func transitionInfo() -> ContextControllerTakeControllerInfo? {
|
||||||
|
let sourceNode = self.sourceNode
|
||||||
|
let sourceRect = self.sourceRect
|
||||||
|
return ContextControllerTakeControllerInfo(contentAreaInScreenSpace: CGRect(origin: CGPoint(), size: CGSize(width: 10.0, height: 10.0)), sourceNode: { [weak sourceNode] in
|
||||||
|
if let sourceNode = sourceNode {
|
||||||
|
return (sourceNode, sourceRect)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func animatedIn() {
|
||||||
|
if let controller = self.controller as? GalleryController {
|
||||||
|
controller.viewDidAppear(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@ import Postbox
|
|||||||
import TelegramCore
|
import TelegramCore
|
||||||
import SyncCore
|
import SyncCore
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
import ContextUI
|
||||||
|
|
||||||
private final class MultiplexedVideoTrackingNode: ASDisplayNode {
|
private final class MultiplexedVideoTrackingNode: ASDisplayNode {
|
||||||
var inHierarchyUpdated: ((Bool) -> Void)?
|
var inHierarchyUpdated: ((Bool) -> Void)?
|
||||||
@ -34,7 +35,7 @@ private final class VisibleVideoItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
|
final class MultiplexedVideoNode: ASDisplayNode, UIScrollViewDelegate {
|
||||||
private let account: Account
|
private let account: Account
|
||||||
private let trackingNode: MultiplexedVideoTrackingNode
|
private let trackingNode: MultiplexedVideoTrackingNode
|
||||||
var didScroll: ((CGFloat, CGFloat) -> Void)?
|
var didScroll: ((CGFloat, CGFloat) -> Void)?
|
||||||
@ -67,6 +68,9 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
|
|||||||
private var visibleThumbnailLayers: [MediaId: SoftwareVideoThumbnailLayer] = [:]
|
private var visibleThumbnailLayers: [MediaId: SoftwareVideoThumbnailLayer] = [:]
|
||||||
private var statusDisposable: [MediaId : MetaDisposable] = [:]
|
private var statusDisposable: [MediaId : MetaDisposable] = [:]
|
||||||
|
|
||||||
|
private let contextContainerNode: ContextControllerSourceNode
|
||||||
|
let scrollNode: ASScrollNode
|
||||||
|
|
||||||
private var visibleLayers: [MediaId: (SoftwareVideoLayerFrameManager, SampleBufferLayer)] = [:]
|
private var visibleLayers: [MediaId: (SoftwareVideoLayerFrameManager, SampleBufferLayer)] = [:]
|
||||||
|
|
||||||
private var displayLink: CADisplayLink!
|
private var displayLink: CADisplayLink!
|
||||||
@ -76,6 +80,7 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
|
|||||||
private let timebase: CMTimebase
|
private let timebase: CMTimebase
|
||||||
|
|
||||||
var fileSelected: ((FileMediaReference, ASDisplayNode, CGRect) -> Void)?
|
var fileSelected: ((FileMediaReference, ASDisplayNode, CGRect) -> Void)?
|
||||||
|
var fileContextMenu: ((FileMediaReference, ASDisplayNode, CGRect, ContextGesture) -> Void)?
|
||||||
var enableVideoNodes = false
|
var enableVideoNodes = false
|
||||||
|
|
||||||
init(account: Account) {
|
init(account: Account) {
|
||||||
@ -88,14 +93,19 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
|
|||||||
CMTimebaseSetRate(timebase!, rate: 0.0)
|
CMTimebaseSetRate(timebase!, rate: 0.0)
|
||||||
self.timebase = timebase!
|
self.timebase = timebase!
|
||||||
|
|
||||||
|
self.contextContainerNode = ContextControllerSourceNode()
|
||||||
|
self.scrollNode = ASScrollNode()
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
self.isOpaque = true
|
self.isOpaque = true
|
||||||
self.view.showsVerticalScrollIndicator = false
|
self.scrollNode.view.showsVerticalScrollIndicator = false
|
||||||
self.view.showsHorizontalScrollIndicator = false
|
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||||
self.view.alwaysBounceVertical = true
|
self.scrollNode.view.alwaysBounceVertical = true
|
||||||
|
|
||||||
self.addSubnode(self.trackingNode)
|
self.addSubnode(self.trackingNode)
|
||||||
|
self.addSubnode(self.contextContainerNode)
|
||||||
|
self.contextContainerNode.addSubnode(self.scrollNode)
|
||||||
|
|
||||||
class DisplayLinkProxy: NSObject {
|
class DisplayLinkProxy: NSObject {
|
||||||
weak var target: MultiplexedVideoNode?
|
weak var target: MultiplexedVideoNode?
|
||||||
@ -135,10 +145,49 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.view.delegate = self
|
self.scrollNode.view.delegate = self
|
||||||
|
|
||||||
let recognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
let recognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
|
||||||
self.view.addGestureRecognizer(recognizer)
|
self.view.addGestureRecognizer(recognizer)
|
||||||
|
|
||||||
|
var gestureLocation: CGPoint?
|
||||||
|
|
||||||
|
self.contextContainerNode.shouldBegin = { [weak self] point in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
gestureLocation = point
|
||||||
|
return strongSelf.fileAt(point: point) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
self.contextContainerNode.activated = { [weak self] gesture in
|
||||||
|
guard let strongSelf = self, let gestureLocation = gestureLocation else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let (file, rect) = strongSelf.fileAt(point: gestureLocation) {
|
||||||
|
strongSelf.fileContextMenu?(file, strongSelf, rect.offsetBy(dx: 0.0, dy: -strongSelf.scrollNode.bounds.minY), gesture)
|
||||||
|
} else {
|
||||||
|
gesture.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.contextContainerNode.customActivationProgress = { [weak self] progress, update in
|
||||||
|
guard let strongSelf = self, let gestureLocation = gestureLocation else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
/*let minScale: CGFloat = (strongSelf.bounds.width - 10.0) / strongSelf.bounds.width
|
||||||
|
let currentScale = 1.0 * (1.0 - progress) + minScale * progress
|
||||||
|
switch update {
|
||||||
|
case .update:
|
||||||
|
strongSelf.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0)
|
||||||
|
case .begin:
|
||||||
|
strongSelf.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0)
|
||||||
|
case let .ended(previousProgress):
|
||||||
|
let previousScale = 1.0 * (1.0 - previousProgress) + minScale * previousProgress
|
||||||
|
strongSelf.layer.sublayerTransform = CATransform3DMakeScale(currentScale, currentScale, 1.0)
|
||||||
|
strongSelf.layer.animateSpring(from: previousScale as NSNumber, to: currentScale as NSNumber, keyPath: "sublayerTransform.scale", duration: 0.5, delay: 0.0, initialVelocity: 0.0, damping: 90.0)
|
||||||
|
}*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
@ -168,6 +217,8 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
|
|||||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
|
||||||
if self.validSize == nil || !self.validSize!.equalTo(size) {
|
if self.validSize == nil || !self.validSize!.equalTo(size) {
|
||||||
self.validSize = size
|
self.validSize = size
|
||||||
|
self.contextContainerNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
self.scrollNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
self.updateVisibleItems(transition: transition)
|
self.updateVisibleItems(transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,7 +240,7 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
|
|||||||
|
|
||||||
private var validVisibleItemsOffset: CGFloat?
|
private var validVisibleItemsOffset: CGFloat?
|
||||||
private func updateImmediatelyVisibleItems(ensureFrames: Bool = false) {
|
private func updateImmediatelyVisibleItems(ensureFrames: Bool = false) {
|
||||||
let visibleBounds = self.bounds
|
let visibleBounds = self.scrollNode.bounds
|
||||||
let visibleThumbnailBounds = visibleBounds.insetBy(dx: 0.0, dy: -350.0)
|
let visibleThumbnailBounds = visibleBounds.insetBy(dx: 0.0, dy: -350.0)
|
||||||
|
|
||||||
if let validVisibleItemsOffset = self.validVisibleItemsOffset, validVisibleItemsOffset.isEqual(to: visibleBounds.origin.y) {
|
if let validVisibleItemsOffset = self.validVisibleItemsOffset, validVisibleItemsOffset.isEqual(to: visibleBounds.origin.y) {
|
||||||
@ -222,7 +273,7 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
|
|||||||
} else {
|
} else {
|
||||||
let thumbnailLayer = SoftwareVideoThumbnailLayer(account: self.account, fileReference: item.fileReference)
|
let thumbnailLayer = SoftwareVideoThumbnailLayer(account: self.account, fileReference: item.fileReference)
|
||||||
thumbnailLayer.frame = item.frame
|
thumbnailLayer.frame = item.frame
|
||||||
self.layer.addSublayer(thumbnailLayer)
|
self.scrollNode.layer.addSublayer(thumbnailLayer)
|
||||||
self.visibleThumbnailLayers[item.fileReference.media.fileId] = thumbnailLayer
|
self.visibleThumbnailLayers[item.fileReference.media.fileId] = thumbnailLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,51 +287,6 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if self.statusDisposable[item.fileReference.media.fileId] == nil {
|
|
||||||
let statusDisposable = MetaDisposable()
|
|
||||||
let updatedStatusSignal = account.postbox.mediaBox.resourceStatus(item.fileReference.media.resource)
|
|
||||||
self.statusDisposable[item.fileReference.media.fileId] = statusDisposable
|
|
||||||
statusDisposable.set((updatedStatusSignal
|
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
|
||||||
guard let `self` = self else {return}
|
|
||||||
|
|
||||||
let state: RadialStatusNodeState
|
|
||||||
|
|
||||||
switch status {
|
|
||||||
case let .Fetching(_, progress):
|
|
||||||
state = .progress(color: .white, lineWidth: nil, value: CGFloat(max(progress, 0.2)), cancelEnabled: false)
|
|
||||||
case .Remote:
|
|
||||||
state = .progress(color: .white, lineWidth: nil, value: 0, cancelEnabled: false)
|
|
||||||
case .Local:
|
|
||||||
state = .none
|
|
||||||
}
|
|
||||||
|
|
||||||
/*if let statusNode = self.visibleProgressNodes[item.fileReference.media.fileId] {
|
|
||||||
if state == .none {
|
|
||||||
self.visibleProgressNodes.removeValue(forKey: item.fileReference.media.fileId)
|
|
||||||
statusNode.transitionToState(state, completion: { [weak statusNode] in
|
|
||||||
statusNode?.isHidden = true
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
statusNode.isHidden = false
|
|
||||||
statusNode.transitionToState(state, completion: {})
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}))
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/*if let visibleProgressNode = self.visibleProgressNodes[item.fileReference.media.fileId] {
|
|
||||||
if ensureFrames {
|
|
||||||
visibleProgressNode.frame = progressFrame
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.5))
|
|
||||||
statusNode.isHidden = true
|
|
||||||
statusNode.frame = progressFrame
|
|
||||||
self.visibleProgressNodes[item.fileReference.media.fileId] = statusNode
|
|
||||||
self.addSubnode(statusNode)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
visibleIds.insert(item.fileReference.media.fileId)
|
visibleIds.insert(item.fileReference.media.fileId)
|
||||||
|
|
||||||
if let (_, layerHolder) = self.visibleLayers[item.fileReference.media.fileId] {
|
if let (_, layerHolder) = self.visibleLayers[item.fileReference.media.fileId] {
|
||||||
@ -291,7 +297,7 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
|
|||||||
let layerHolder = takeSampleBufferLayer()
|
let layerHolder = takeSampleBufferLayer()
|
||||||
layerHolder.layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
layerHolder.layer.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||||
layerHolder.layer.frame = item.frame
|
layerHolder.layer.frame = item.frame
|
||||||
self.layer.addSublayer(layerHolder.layer)
|
self.scrollNode.layer.addSublayer(layerHolder.layer)
|
||||||
let manager = SoftwareVideoLayerFrameManager(account: self.account, fileReference: item.fileReference, resource: item.fileReference.media.resource, layerHolder: layerHolder)
|
let manager = SoftwareVideoLayerFrameManager(account: self.account, fileReference: item.fileReference, resource: item.fileReference.media.resource, layerHolder: layerHolder)
|
||||||
self.visibleLayers[item.fileReference.media.fileId] = (manager, layerHolder)
|
self.visibleLayers[item.fileReference.media.fileId] = (manager, layerHolder)
|
||||||
self.visibleThumbnailLayers[item.fileReference.media.fileId]?.ready = { [weak self] in
|
self.visibleThumbnailLayers[item.fileReference.media.fileId]?.ready = { [weak self] in
|
||||||
@ -344,7 +350,7 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func updateVisibleItems(transition: ContainedViewLayoutTransition = .immediate) {
|
private func updateVisibleItems(transition: ContainedViewLayoutTransition = .immediate) {
|
||||||
let drawableSize = self.bounds.size
|
let drawableSize = self.scrollNode.bounds.size
|
||||||
if !drawableSize.width.isZero {
|
if !drawableSize.width.isZero {
|
||||||
var displayItems: [VisibleVideoItem] = []
|
var displayItems: [VisibleVideoItem] = []
|
||||||
|
|
||||||
@ -447,7 +453,7 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
|
|||||||
i += row.count
|
i += row.count
|
||||||
}
|
}
|
||||||
let contentSize = CGSize(width: drawableSize.width, height: contentMaxValueInScrollDirection + self.bottomInset)
|
let contentSize = CGSize(width: drawableSize.width, height: contentMaxValueInScrollDirection + self.bottomInset)
|
||||||
self.view.contentSize = contentSize
|
self.scrollNode.view.contentSize = contentSize
|
||||||
|
|
||||||
self.displayItems = displayItems
|
self.displayItems = displayItems
|
||||||
|
|
||||||
@ -475,7 +481,7 @@ final class MultiplexedVideoNode: ASScrollNode, UIScrollViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fileAt(point: CGPoint) -> (FileMediaReference, CGRect)? {
|
func fileAt(point: CGPoint) -> (FileMediaReference, CGRect)? {
|
||||||
let offsetPoint = point.offsetBy(dx: 0.0, dy: self.bounds.minY)
|
let offsetPoint = point.offsetBy(dx: 0.0, dy: self.scrollNode.bounds.minY)
|
||||||
return self.offsetFileAt(point: offsetPoint)
|
return self.offsetFileAt(point: offsetPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user