mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '6783076846f4e34c8e6f28e9f6bbd40cd56d6875'
This commit is contained in:
commit
a6985734a2
@ -366,17 +366,17 @@ private final class CameraContext {
|
||||
|> map { first, second in
|
||||
return first && second
|
||||
}
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> delay(0.1, queue: self.queue)
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] _ in
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> delay(0.1, queue: self.queue)
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] _ in
|
||||
self?.modeChange = .none
|
||||
})
|
||||
} else {
|
||||
let _ = (previewView.isPreviewing
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] _ in
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] _ in
|
||||
self?.modeChange = .none
|
||||
})
|
||||
}
|
||||
|
@ -75,7 +75,6 @@ final class CameraOutput: NSObject {
|
||||
let videoOutput = AVCaptureVideoDataOutput()
|
||||
let audioOutput = AVCaptureAudioDataOutput()
|
||||
let metadataOutput = AVCaptureMetadataOutput()
|
||||
private let faceLandmarksOutput = FaceLandmarksDataOutput()
|
||||
|
||||
let exclusive: Bool
|
||||
|
||||
@ -85,17 +84,12 @@ final class CameraOutput: NSObject {
|
||||
|
||||
private let queue = DispatchQueue(label: "")
|
||||
private let metadataQueue = DispatchQueue(label: "")
|
||||
private let faceLandmarksQueue = DispatchQueue(label: "")
|
||||
|
||||
private var photoCaptureRequests: [Int64: PhotoCaptureContext] = [:]
|
||||
private var videoRecorder: VideoRecorder?
|
||||
|
||||
var activeFilter: CameraFilter?
|
||||
var faceLandmarks: Bool = false
|
||||
|
||||
|
||||
var processSampleBuffer: ((CMSampleBuffer, CVImageBuffer, AVCaptureConnection) -> Void)?
|
||||
var processCodes: (([CameraCode]) -> Void)?
|
||||
var processFaceLandmarks: (([VNFaceObservation]) -> Void)?
|
||||
|
||||
init(exclusive: Bool) {
|
||||
self.exclusive = exclusive
|
||||
@ -104,12 +98,6 @@ final class CameraOutput: NSObject {
|
||||
|
||||
self.videoOutput.alwaysDiscardsLateVideoFrames = false
|
||||
self.videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] as [String : Any]
|
||||
|
||||
self.faceLandmarksOutput.outputFaceObservations = { [weak self] observations in
|
||||
if let self {
|
||||
self.processFaceLandmarks?(observations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -273,7 +261,7 @@ final class CameraOutput: NSObject {
|
||||
}
|
||||
|
||||
let uniqueId = settings.uniqueID
|
||||
let photoCapture = PhotoCaptureContext(settings: settings, filter: self.activeFilter, mirror: mirror)
|
||||
let photoCapture = PhotoCaptureContext(settings: settings, filter: nil, mirror: mirror)
|
||||
self.photoCaptureRequests[uniqueId] = photoCapture
|
||||
self.photoOutput.capturePhoto(with: settings, delegate: photoCapture)
|
||||
|
||||
@ -361,32 +349,10 @@ extension CameraOutput: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureA
|
||||
return
|
||||
}
|
||||
|
||||
if self.faceLandmarks {
|
||||
self.faceLandmarksQueue.async {
|
||||
self.faceLandmarksOutput.process(sampleBuffer: sampleBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
if let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
|
||||
self.processSampleBuffer?(sampleBuffer, videoPixelBuffer, connection)
|
||||
}
|
||||
|
||||
// let finalSampleBuffer: CMSampleBuffer = sampleBuffer
|
||||
// if let videoPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer), let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer) {
|
||||
// var finalVideoPixelBuffer = videoPixelBuffer
|
||||
// if let filter = self.activeFilter {
|
||||
// if !filter.isPrepared {
|
||||
// filter.prepare(with: formatDescription, outputRetainedBufferCountHint: 3)
|
||||
// }
|
||||
//
|
||||
// guard let filteredBuffer = filter.render(pixelBuffer: finalVideoPixelBuffer) else {
|
||||
// return
|
||||
// }
|
||||
// finalVideoPixelBuffer = filteredBuffer
|
||||
// }
|
||||
// self.processSampleBuffer?(finalVideoPixelBuffer, connection)
|
||||
// }
|
||||
|
||||
if let videoRecorder = self.videoRecorder, videoRecorder.isRecording {
|
||||
videoRecorder.appendSampleBuffer(sampleBuffer)
|
||||
}
|
||||
|
@ -1227,7 +1227,7 @@ private final class DrawingScreenComponent: CombinedComponent {
|
||||
case .filled:
|
||||
nextStyle = .semi
|
||||
case .semi:
|
||||
nextStyle = .stroke
|
||||
nextStyle = .regular
|
||||
case .stroke:
|
||||
nextStyle = .regular
|
||||
}
|
||||
@ -2975,6 +2975,8 @@ public final class DrawingToolsInteraction {
|
||||
private var isActive = false
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
private let startTimestamp = CACurrentMediaTime()
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
drawingView: DrawingView,
|
||||
@ -3126,6 +3128,10 @@ public final class DrawingToolsInteraction {
|
||||
if let textEntityView = entityView as? DrawingTextEntityView {
|
||||
textEntityView.beginEditing(accessoryView: self.textEditAccessoryView)
|
||||
} else {
|
||||
if self.isVideo {
|
||||
entityView.seek(to: 0.0)
|
||||
}
|
||||
|
||||
entityView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
entityView.layer.animateScale(from: 0.1, to: entity.scale, duration: 0.2)
|
||||
|
||||
@ -3474,7 +3480,7 @@ public final class DrawingToolsInteraction {
|
||||
case .filled:
|
||||
nextStyle = .semi
|
||||
case .semi:
|
||||
nextStyle = .stroke
|
||||
nextStyle = .regular
|
||||
case .stroke:
|
||||
nextStyle = .regular
|
||||
}
|
||||
|
@ -295,7 +295,6 @@ private final class StickerSelectionComponent: Component {
|
||||
},
|
||||
peekBehavior: stickerPeekBehavior
|
||||
)
|
||||
|
||||
return searchContainerNode
|
||||
},
|
||||
contentIdUpdated: { _ in },
|
||||
|
@ -31,6 +31,8 @@ swift_library(
|
||||
"//submodules/StickerResources:StickerResources",
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
||||
"//submodules/TextFormat",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -1059,8 +1059,8 @@ private enum FeaturedSearchEntry: Identifiable, Comparable {
|
||||
func item(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, interaction: StickerPaneSearchInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction, itemContext: StickerPaneSearchGlobalItemContext) -> GridItem {
|
||||
switch self {
|
||||
case let .sticker(_, code, stickerItem, theme):
|
||||
return StickerPaneSearchStickerItem(context: context, code: code, stickerItem: stickerItem, inputNodeInteraction: inputNodeInteraction, theme: theme, selected: { node, rect in
|
||||
interaction.sendSticker(.standalone(media: stickerItem.file), node.view, rect)
|
||||
return StickerPaneSearchStickerItem(context: context, theme: theme, code: code, stickerItem: stickerItem, inputNodeInteraction: inputNodeInteraction, selected: { node, layer, rect in
|
||||
interaction.sendSticker(.standalone(media: stickerItem.file), node.view, layer, rect)
|
||||
})
|
||||
case let .global(_, info, topItems, installed, topSeparator):
|
||||
return StickerPaneSearchGlobalItem(context: context, theme: theme, strings: strings, listAppearance: true, fillsRow: true, info: info, topItems: topItems, topSeparator: topSeparator, regularInsets: false, installed: installed, unread: false, open: {
|
||||
@ -1201,7 +1201,7 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode {
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
})
|
||||
}
|
||||
}, sendSticker: { [weak self] file, sourceView, sourceRect in
|
||||
}, sendSticker: { [weak self] file, sourceView, layer, sourceRect in
|
||||
if let strongSelf = self {
|
||||
let _ = strongSelf.sendSticker?(file, sourceView, sourceRect)
|
||||
}
|
||||
@ -1521,10 +1521,10 @@ private final class FeaturedPaneSearchContentNode: ASDisplayNode {
|
||||
public final class StickerPaneSearchInteraction {
|
||||
public let open: (StickerPackCollectionInfo) -> Void
|
||||
public let install: (StickerPackCollectionInfo, [ItemCollectionItem], Bool) -> Void
|
||||
public let sendSticker: (FileMediaReference, UIView, CGRect) -> Void
|
||||
public let sendSticker: (FileMediaReference, UIView, CALayer, CGRect) -> Void
|
||||
public let getItemIsPreviewed: (StickerPackItem) -> Bool
|
||||
|
||||
public init(open: @escaping (StickerPackCollectionInfo) -> Void, install: @escaping (StickerPackCollectionInfo, [ItemCollectionItem], Bool) -> Void, sendSticker: @escaping (FileMediaReference, UIView, CGRect) -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) {
|
||||
public init(open: @escaping (StickerPackCollectionInfo) -> Void, install: @escaping (StickerPackCollectionInfo, [ItemCollectionItem], Bool) -> Void, sendSticker: @escaping (FileMediaReference, UIView, CALayer, CGRect) -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) {
|
||||
self.open = open
|
||||
self.install = install
|
||||
self.sendSticker = sendSticker
|
||||
|
@ -11,6 +11,8 @@ import AccountContext
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import ChatPresentationInterfaceState
|
||||
import EmojiTextAttachmentView
|
||||
import TextFormat
|
||||
|
||||
final class StickerPaneSearchStickerSection: GridSection {
|
||||
let code: String
|
||||
@ -50,10 +52,11 @@ final class StickerPaneSearchStickerSectionNode: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.titleNode.attributedText = NSAttributedString(string: code, font: sectionTitleFont, textColor: theme.chat.inputMediaPanel.stickersSectionTextColor)
|
||||
self.titleNode.maximumNumberOfLines = 1
|
||||
self.titleNode.truncationMode = .byTruncatingTail
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
@ -68,15 +71,17 @@ final class StickerPaneSearchStickerSectionNode: ASDisplayNode {
|
||||
|
||||
public final class StickerPaneSearchStickerItem: GridItem {
|
||||
public let context: AccountContext
|
||||
public let theme: PresentationTheme
|
||||
public let code: String?
|
||||
public let stickerItem: FoundStickerItem
|
||||
public let selected: (ASDisplayNode, CGRect) -> Void
|
||||
public let selected: (ASDisplayNode, CALayer, CGRect) -> Void
|
||||
public let inputNodeInteraction: ChatMediaInputNodeInteraction
|
||||
|
||||
public let section: GridSection?
|
||||
|
||||
public init(context: AccountContext, code: String?, stickerItem: FoundStickerItem, inputNodeInteraction: ChatMediaInputNodeInteraction, theme: PresentationTheme, selected: @escaping (ASDisplayNode, CGRect) -> Void) {
|
||||
public init(context: AccountContext, theme: PresentationTheme, code: String?, stickerItem: FoundStickerItem, inputNodeInteraction: ChatMediaInputNodeInteraction, selected: @escaping (ASDisplayNode, CALayer, CGRect) -> Void) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.stickerItem = stickerItem
|
||||
self.inputNodeInteraction = inputNodeInteraction
|
||||
self.selected = selected
|
||||
@ -87,7 +92,7 @@ public final class StickerPaneSearchStickerItem: GridItem {
|
||||
public func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
||||
let node = StickerPaneSearchStickerItemNode()
|
||||
node.inputNodeInteraction = self.inputNodeInteraction
|
||||
node.setup(context: self.context, stickerItem: self.stickerItem, code: self.code)
|
||||
node.setup(context: self.context, theme: self.theme, stickerItem: self.stickerItem, code: self.code)
|
||||
node.selected = self.selected
|
||||
return node
|
||||
}
|
||||
@ -98,7 +103,7 @@ public final class StickerPaneSearchStickerItem: GridItem {
|
||||
return
|
||||
}
|
||||
node.inputNodeInteraction = self.inputNodeInteraction
|
||||
node.setup(context: self.context, stickerItem: self.stickerItem, code: self.code)
|
||||
node.setup(context: self.context, theme: self.theme, stickerItem: self.stickerItem, code: self.code)
|
||||
node.selected = self.selected
|
||||
}
|
||||
}
|
||||
@ -107,8 +112,7 @@ private let textFont = Font.regular(20.0)
|
||||
|
||||
public final class StickerPaneSearchStickerItemNode: GridItemNode {
|
||||
private var currentState: (AccountContext, FoundStickerItem, CGSize)?
|
||||
public let imageNode: TransformImageNode
|
||||
public private(set) var animationNode: AnimatedStickerNode?
|
||||
var itemLayer: InlineStickerItemLayer?
|
||||
private let textNode: ASTextNode
|
||||
|
||||
private let stickerFetchedDisposable = MetaDisposable()
|
||||
@ -124,22 +128,21 @@ public final class StickerPaneSearchStickerItemNode: GridItemNode {
|
||||
private var isPlaying = false
|
||||
|
||||
public var inputNodeInteraction: ChatMediaInputNodeInteraction?
|
||||
public var selected: ((ASDisplayNode, CGRect) -> Void)?
|
||||
|
||||
public var selected: ((ASDisplayNode, CALayer, CGRect) -> Void)?
|
||||
|
||||
public var stickerItem: FoundStickerItem? {
|
||||
return self.currentState?.1
|
||||
}
|
||||
|
||||
public override init() {
|
||||
self.imageNode = TransformImageNode()
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.imageNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.textNode.maximumNumberOfLines = 1
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -149,39 +152,43 @@ public final class StickerPaneSearchStickerItemNode: GridItemNode {
|
||||
public override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.imageNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
|
||||
}
|
||||
|
||||
func setup(context: AccountContext, stickerItem: FoundStickerItem, code: String?) {
|
||||
func setup(context: AccountContext, theme: PresentationTheme, stickerItem: FoundStickerItem, code: String?) {
|
||||
if self.currentState == nil || self.currentState!.0 !== context || self.currentState!.1 != stickerItem {
|
||||
self.textNode.attributedText = NSAttributedString(string: code ?? "", font: textFont, textColor: .black)
|
||||
|
||||
if let dimensions = stickerItem.file.dimensions {
|
||||
if stickerItem.file.isAnimatedSticker || stickerItem.file.isVideoSticker {
|
||||
if self.animationNode == nil {
|
||||
let animationNode = DefaultAnimatedStickerNodeImpl()
|
||||
animationNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
|
||||
self.animationNode = animationNode
|
||||
self.insertSubnode(animationNode, belowSubnode: self.textNode)
|
||||
}
|
||||
let dimensions = stickerItem.file.dimensions ?? PixelDimensions(width: 512, height: 512)
|
||||
let fittedDimensions = dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0))
|
||||
self.animationNode?.setup(source: AnimatedStickerResourceSource(account: context.account, resource: stickerItem.file.resource, isVideo: stickerItem.file.isVideoSticker), width: Int(fittedDimensions.width), height: Int(fittedDimensions.height), playbackMode: .loop, mode: .cached)
|
||||
self.animationNode?.visibility = self.isVisibleInGrid && context.sharedContext.energyUsageSettings.loopStickers
|
||||
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: stickerPackFileReference(stickerItem.file), resource: stickerItem.file.resource).start())
|
||||
} else {
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.visibility = false
|
||||
self.animationNode = nil
|
||||
animationNode.removeFromSupernode()
|
||||
}
|
||||
self.imageNode.setSignal(chatMessageSticker(account: context.account, userLocation: .other, file: stickerItem.file, small: true))
|
||||
self.stickerFetchedDisposable.set(freeMediaFileResourceInteractiveFetched(account: context.account, userLocation: .other, fileReference: stickerPackFileReference(stickerItem.file), resource: chatMessageStickerResource(file: stickerItem.file, small: true)).start())
|
||||
}
|
||||
|
||||
self.currentState = (context, stickerItem, dimensions.cgSize)
|
||||
self.setNeedsLayout()
|
||||
let file = stickerItem.file
|
||||
let itemDimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
|
||||
let playbackItemSize = CGSize(width: 96.0, height: 96.0)
|
||||
|
||||
let itemPlaybackSize = itemDimensions.aspectFitted(playbackItemSize)
|
||||
|
||||
let itemLayer: InlineStickerItemLayer
|
||||
if let current = self.itemLayer {
|
||||
itemLayer = current
|
||||
itemLayer.dynamicColor = .white
|
||||
} else {
|
||||
itemLayer = InlineStickerItemLayer(
|
||||
context: context,
|
||||
userLocation: .other,
|
||||
attemptSynchronousLoad: false,
|
||||
emoji: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: file.fileId.id, file: file),
|
||||
file: file,
|
||||
cache: context.animationCache,
|
||||
renderer: context.animationRenderer,
|
||||
placeholderColor: theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1),
|
||||
pointSize: itemPlaybackSize,
|
||||
dynamicColor: .white
|
||||
)
|
||||
self.itemLayer = itemLayer
|
||||
self.layer.insertSublayer(itemLayer, at: 0)
|
||||
}
|
||||
|
||||
self.currentState = (context, stickerItem, itemDimensions)
|
||||
self.setNeedsLayout()
|
||||
self.updateVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,29 +198,26 @@ public final class StickerPaneSearchStickerItemNode: GridItemNode {
|
||||
let bounds = self.bounds
|
||||
let boundingSize = bounds.insetBy(dx: 6.0, dy: 6.0).size
|
||||
|
||||
if let (_, _, mediaDimensions) = self.currentState {
|
||||
let imageSize = mediaDimensions.aspectFitted(boundingSize)
|
||||
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()
|
||||
|
||||
let imageFrame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
|
||||
self.imageNode.frame = imageFrame
|
||||
|
||||
if let animationNode = self.animationNode {
|
||||
animationNode.frame = imageFrame
|
||||
animationNode.updateLayout(size: imageSize)
|
||||
if let (_, _, itemDimensions) = self.currentState {
|
||||
let itemSize = itemDimensions.aspectFitted(boundingSize)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: floor((bounds.size.width - itemSize.width) / 2.0), y: (bounds.size.height - itemSize.height) / 2.0), size: itemSize)
|
||||
if let itemLayer = self.itemLayer {
|
||||
itemLayer.frame = itemFrame
|
||||
}
|
||||
|
||||
let textSize = self.textNode.measure(CGSize(width: bounds.size.width - 24.0, height: CGFloat.greatestFiniteMagnitude))
|
||||
self.textNode.frame = CGRect(origin: CGPoint(x: bounds.size.width - textSize.width, y: bounds.size.height - textSize.height), size: textSize)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func imageNodeTap(_ recognizer: UITapGestureRecognizer) {
|
||||
self.selected?(self, self.bounds)
|
||||
guard let itemLayer = self.itemLayer else {
|
||||
return
|
||||
}
|
||||
self.selected?(self, itemLayer, self.bounds)
|
||||
}
|
||||
|
||||
public func transitionNode() -> ASDisplayNode? {
|
||||
return self.imageNode
|
||||
return self
|
||||
}
|
||||
|
||||
public func updateVisibility() {
|
||||
@ -222,9 +226,9 @@ public final class StickerPaneSearchStickerItemNode: GridItemNode {
|
||||
}
|
||||
|
||||
let isPlaying = self.isVisibleInGrid && context.sharedContext.energyUsageSettings.loopStickers
|
||||
if self.isPlaying != isPlaying {
|
||||
if self.isPlaying != isPlaying, let itemLayer = self.itemLayer {
|
||||
self.isPlaying = isPlaying
|
||||
self.animationNode?.visibility = isPlaying
|
||||
itemLayer.isVisibleForAnimations = isPlaying
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,8 @@ swift_library(
|
||||
"//submodules/MoreButtonNode:MoreButtonNode",
|
||||
"//submodules/InvisibleInkDustNode:InvisibleInkDustNode",
|
||||
"//submodules/TelegramUI/Components/CameraScreen",
|
||||
"//submodules/TelegramUI/Components/MediaEditor",
|
||||
"//submodules/TelegramUI/Components/MediaEditor",
|
||||
"//submodules/RadialStatusNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -17,7 +17,7 @@ final class AssetDownloadManager {
|
||||
let identifier: String
|
||||
let updated: () -> Void
|
||||
|
||||
var status: AssetDownloadStatus = .progress(0.0)
|
||||
var status: AssetDownloadStatus = .none
|
||||
var disposable: Disposable?
|
||||
|
||||
init(identifier: String, updated: @escaping () -> Void) {
|
||||
@ -40,9 +40,7 @@ final class AssetDownloadManager {
|
||||
}
|
||||
|
||||
func download(asset: PHAsset) {
|
||||
if let currentAssetContext = self.currentAssetContext {
|
||||
currentAssetContext.disposable?.dispose()
|
||||
}
|
||||
self.cancelAllDownloads()
|
||||
|
||||
let queue = self.queue
|
||||
let identifier = asset.localIdentifier
|
||||
@ -59,6 +57,7 @@ final class AssetDownloadManager {
|
||||
}
|
||||
}
|
||||
})
|
||||
self.currentAssetContext = assetContext
|
||||
assetContext.disposable = (downloadAssetMediaData(asset)
|
||||
|> deliverOn(queue)).start(next: { [weak self] status in
|
||||
guard let self else {
|
||||
@ -69,13 +68,31 @@ final class AssetDownloadManager {
|
||||
currentAssetContext.updated()
|
||||
}
|
||||
})
|
||||
self.currentAssetContext = assetContext
|
||||
}
|
||||
|
||||
func cancelAllDownloads() {
|
||||
if let currentAssetContext = self.currentAssetContext {
|
||||
currentAssetContext.status = .none
|
||||
currentAssetContext.updated()
|
||||
currentAssetContext.disposable?.dispose()
|
||||
self.queue.justDispatch {
|
||||
if self.currentAssetContext === currentAssetContext {
|
||||
self.currentAssetContext = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cancel(identifier: String) {
|
||||
if let currentAssetContext = self.currentAssetContext, currentAssetContext.identifier == identifier {
|
||||
currentAssetContext.status = .none
|
||||
currentAssetContext.updated()
|
||||
currentAssetContext.disposable?.dispose()
|
||||
self.currentAssetContext = nil
|
||||
self.queue.justDispatch {
|
||||
if self.currentAssetContext === currentAssetContext {
|
||||
self.currentAssetContext = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,7 +110,7 @@ final class AssetDownloadManager {
|
||||
if let currentAssetContext = self.currentAssetContext, currentAssetContext.identifier == identifier {
|
||||
next(currentAssetContext.status)
|
||||
} else {
|
||||
next(.progress(0.0))
|
||||
next(.none)
|
||||
}
|
||||
|
||||
let queue = self.queue
|
||||
@ -129,27 +146,35 @@ final class AssetDownloadManager {
|
||||
}
|
||||
|
||||
func checkIfAssetIsLocal(_ asset: PHAsset) -> Signal<Bool, NoError> {
|
||||
if asset.isLocallyAvailable == true {
|
||||
return .single(true)
|
||||
}
|
||||
return Signal { subscriber in
|
||||
let options = PHImageRequestOptions()
|
||||
options.isNetworkAccessAllowed = false
|
||||
|
||||
let requestId: PHImageRequestID
|
||||
if #available(iOS 13, *) {
|
||||
requestId = imageManager.requestImageDataAndOrientation(for: asset, options: options) { data, _, _, _ in
|
||||
if data != nil {
|
||||
subscriber.putNext(data != nil)
|
||||
}
|
||||
if case .video = asset.mediaType {
|
||||
let options = PHVideoRequestOptions()
|
||||
options.isNetworkAccessAllowed = false
|
||||
|
||||
requestId = imageManager.requestAVAsset(forVideo: asset, options: options) { asset, _, _ in
|
||||
subscriber.putNext(asset != nil)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
} else {
|
||||
requestId = imageManager.requestImageData(for: asset, options: options) { data, _, _, _ in
|
||||
if data != nil {
|
||||
let options = PHImageRequestOptions()
|
||||
options.isNetworkAccessAllowed = false
|
||||
|
||||
if #available(iOS 13, *) {
|
||||
requestId = imageManager.requestImageDataAndOrientation(for: asset, options: options) { data, _, _, _ in
|
||||
subscriber.putNext(data != nil)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
} else {
|
||||
requestId = imageManager.requestImageData(for: asset, options: options) { data, _, _, _ in
|
||||
subscriber.putNext(data != nil)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
|
||||
return ActionDisposable {
|
||||
imageManager.cancelImageRequest(requestId)
|
||||
}
|
||||
@ -157,32 +182,57 @@ func checkIfAssetIsLocal(_ asset: PHAsset) -> Signal<Bool, NoError> {
|
||||
}
|
||||
|
||||
enum AssetDownloadStatus {
|
||||
case none
|
||||
case progress(Float)
|
||||
case completed
|
||||
}
|
||||
|
||||
private func downloadAssetMediaData(_ asset: PHAsset) -> Signal<AssetDownloadStatus, NoError> {
|
||||
return Signal { subscriber in
|
||||
let options = PHImageRequestOptions()
|
||||
options.isNetworkAccessAllowed = true
|
||||
options.progressHandler = { progress, _, _, _ in
|
||||
subscriber.putNext(.progress(Float(progress)))
|
||||
}
|
||||
|
||||
let requestId: PHImageRequestID
|
||||
if #available(iOS 13, *) {
|
||||
requestId = imageManager.requestImageDataAndOrientation(for: asset, options: options) { data, _, _, _ in
|
||||
if data != nil {
|
||||
if case .video = asset.mediaType {
|
||||
let options = PHVideoRequestOptions()
|
||||
options.isNetworkAccessAllowed = true
|
||||
options.progressHandler = { progress, _, _, _ in
|
||||
subscriber.putNext(.progress(Float(progress)))
|
||||
}
|
||||
|
||||
subscriber.putNext(.progress(0.0))
|
||||
|
||||
requestId = imageManager.requestAVAsset(forVideo: asset, options: options) { asset, _, _ in
|
||||
if asset != nil {
|
||||
subscriber.putNext(.completed)
|
||||
} else {
|
||||
subscriber.putNext(.none)
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
} else {
|
||||
requestId = imageManager.requestImageData(for: asset, options: options) { data, _, _, _ in
|
||||
if data != nil {
|
||||
subscriber.putNext(.completed)
|
||||
let options = PHImageRequestOptions()
|
||||
options.isNetworkAccessAllowed = true
|
||||
options.progressHandler = { progress, _, _, _ in
|
||||
subscriber.putNext(.progress(Float(progress)))
|
||||
}
|
||||
|
||||
subscriber.putNext(.progress(0.0))
|
||||
if #available(iOS 13, *) {
|
||||
requestId = imageManager.requestImageDataAndOrientation(for: asset, options: options) { data, _, _, _ in
|
||||
if data != nil {
|
||||
subscriber.putNext(.completed)
|
||||
} else {
|
||||
subscriber.putNext(.none)
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
} else {
|
||||
requestId = imageManager.requestImageData(for: asset, options: options) { data, _, _, _ in
|
||||
if data != nil {
|
||||
subscriber.putNext(.completed)
|
||||
} else {
|
||||
subscriber.putNext(.none)
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import InvisibleInkDustNode
|
||||
import ImageBlur
|
||||
import FastBlur
|
||||
import MediaEditor
|
||||
import RadialStatusNode
|
||||
|
||||
enum MediaPickerGridItemContent: Equatable {
|
||||
case asset(PHFetchResult<PHAsset>, Int)
|
||||
@ -90,7 +91,9 @@ private let maskImage = generateImage(CGSize(width: 1.0, height: 36.0), opaque:
|
||||
|
||||
final class MediaPickerGridItemNode: GridItemNode {
|
||||
var currentMediaState: (TGMediaSelectableItem, Int)?
|
||||
var currentState: (PHFetchResult<PHAsset>, Int)?
|
||||
var currentAssetState: (PHFetchResult<PHAsset>, Int)?
|
||||
var currentAsset: PHAsset?
|
||||
|
||||
var currentDraftState: (MediaEditorDraft, Int)?
|
||||
var enableAnimations: Bool = true
|
||||
var stories: Bool = false
|
||||
@ -103,6 +106,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
private let typeIconNode: ASImageNode
|
||||
private let durationNode: ImmediateTextNode
|
||||
private let draftNode: ImmediateTextNode
|
||||
private var statusNode: RadialStatusNode?
|
||||
|
||||
private let activateAreaNode: AccessibilityAreaNode
|
||||
|
||||
@ -112,6 +116,8 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
private let spoilerDisposable = MetaDisposable()
|
||||
var spoilerNode: SpoilerOverlayNode?
|
||||
|
||||
private let progressDisposable = MetaDisposable()
|
||||
|
||||
private var currentIsPreviewing = false
|
||||
|
||||
var selected: (() -> Void)?
|
||||
@ -140,7 +146,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
|
||||
self.activateAreaNode = AccessibilityAreaNode()
|
||||
self.activateAreaNode.accessibilityTraits = [.image]
|
||||
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
@ -168,7 +174,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
var selectableItem: TGMediaSelectableItem? {
|
||||
if let (media, _) = self.currentMediaState {
|
||||
return media
|
||||
} else if let (fetchResult, index) = self.currentState {
|
||||
} else if let (fetchResult, index) = self.currentAssetState {
|
||||
return TGMediaAsset(phAsset: fetchResult[index])
|
||||
} else {
|
||||
return nil
|
||||
@ -179,7 +185,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
var tag: Int32? {
|
||||
if let tag = self._cachedTag {
|
||||
return tag
|
||||
} else if let (fetchResult, index) = self.currentState {
|
||||
} else if let (fetchResult, index) = self.currentAssetState {
|
||||
let asset = fetchResult.object(at: index)
|
||||
if let localTimestamp = asset.creationDate?.timeIntervalSince1970 {
|
||||
let tag = Month(localTimestamp: Int32(localTimestamp)).packedValue
|
||||
@ -251,6 +257,32 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.imageNodeTap(_:))))
|
||||
}
|
||||
|
||||
func updateProgress(_ value: Float?, animated: Bool) {
|
||||
if let value {
|
||||
let statusNode: RadialStatusNode
|
||||
if let current = self.statusNode {
|
||||
statusNode = current
|
||||
} else {
|
||||
statusNode = RadialStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.6))
|
||||
statusNode.isUserInteractionEnabled = false
|
||||
self.addSubnode(statusNode)
|
||||
self.statusNode = statusNode
|
||||
}
|
||||
let adjustedProgress = max(0.027, CGFloat(value))
|
||||
let state: RadialStatusNodeState = .progress(color: .white, lineWidth: nil, value: adjustedProgress, cancelEnabled: true, animateRotation: true)
|
||||
statusNode.transitionToState(state)
|
||||
} else if let statusNode = self.statusNode {
|
||||
self.statusNode = nil
|
||||
if animated {
|
||||
statusNode.transitionToState(.none, animated: true, completion: { [weak statusNode] in
|
||||
statusNode?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
statusNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setup(interaction: MediaPickerInteraction, draft: MediaEditorDraft, index: Int, theme: PresentationTheme, selectable: Bool, enableAnimations: Bool, stories: Bool) {
|
||||
self.interaction = interaction
|
||||
self.theme = theme
|
||||
@ -259,14 +291,18 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
|
||||
self.backgroundColor = theme.list.mediaPlaceholderColor
|
||||
|
||||
if self.currentDraftState == nil || self.currentDraftState?.0.path != draft.path || self.currentDraftState!.1 != index || self.currentState != nil {
|
||||
if self.currentDraftState == nil || self.currentDraftState?.0.path != draft.path || self.currentDraftState!.1 != index || self.currentAssetState != nil {
|
||||
let imageSignal: Signal<UIImage?, NoError> = .single(draft.thumbnail)
|
||||
self.imageNode.setSignal(imageSignal)
|
||||
|
||||
self.currentDraftState = (draft, index)
|
||||
if self.currentState != nil {
|
||||
self.currentState = nil
|
||||
if self.currentAssetState != nil {
|
||||
self.currentAsset = nil
|
||||
self.currentAssetState = nil
|
||||
self.typeIconNode.removeFromSupernode()
|
||||
|
||||
self.progressDisposable.set(nil)
|
||||
self.updateProgress(nil, animated: false)
|
||||
}
|
||||
|
||||
if self.draftNode.supernode == nil {
|
||||
@ -354,11 +390,30 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
self.draftNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if self.currentState == nil || self.currentState!.0 !== fetchResult || self.currentState!.1 != index || self.currentDraftState != nil {
|
||||
self.backgroundNode.image = nil
|
||||
if self.currentAssetState == nil || self.currentAssetState!.0 !== fetchResult || self.currentAssetState!.1 != index || self.currentDraftState != nil {
|
||||
let editingContext = interaction.editingState
|
||||
let asset = fetchResult.object(at: index)
|
||||
|
||||
if asset.localIdentifier == self.currentAsset?.localIdentifier {
|
||||
return
|
||||
}
|
||||
|
||||
self.progressDisposable.set(
|
||||
(interaction.downloadManager.downloadProgress(identifier: asset.localIdentifier)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let self {
|
||||
switch status {
|
||||
case .none, .completed:
|
||||
self.updateProgress(nil, animated: true)
|
||||
case let .progress(progress):
|
||||
self.updateProgress(progress, animated: true)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
self.backgroundNode.image = nil
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
self.activateAreaNode.accessibilityLabel = "Photo \(asset.creationDate?.formatted(date: .abbreviated, time: .standard) ?? "")"
|
||||
}
|
||||
@ -489,7 +544,8 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
}
|
||||
}
|
||||
|
||||
self.currentState = (fetchResult, index)
|
||||
self.currentAssetState = (fetchResult, index)
|
||||
self.currentAsset = asset
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
@ -554,6 +610,11 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
spoilerNode.frame = self.bounds
|
||||
spoilerNode.update(size: self.bounds.size, transition: .immediate)
|
||||
}
|
||||
|
||||
let statusSize = CGSize(width: 40.0, height: 40.0)
|
||||
if let statusNode = self.statusNode {
|
||||
statusNode.view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.bounds.width - statusSize.width) / 2.0), y: floorToScreenPixels((self.bounds.height - statusSize.height) / 2.0)), size: statusSize)
|
||||
}
|
||||
}
|
||||
|
||||
func transitionView(snapshot: Bool) -> UIView {
|
||||
@ -589,10 +650,16 @@ final class MediaPickerGridItemNode: GridItemNode {
|
||||
self.interaction?.openDraft(draft, self.imageNode.image)
|
||||
return
|
||||
}
|
||||
guard let (fetchResult, index) = self.currentState else {
|
||||
guard let (fetchResult, index) = self.currentAssetState else {
|
||||
return
|
||||
}
|
||||
self.interaction?.openMedia(fetchResult, index, self.imageNode.image)
|
||||
if self.statusNode != nil {
|
||||
if let asset = self.currentAsset {
|
||||
self.interaction?.downloadManager.cancel(identifier: asset.localIdentifier)
|
||||
}
|
||||
} else {
|
||||
self.interaction?.openMedia(fetchResult, index, self.imageNode.image)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ import CameraScreen
|
||||
import MediaEditor
|
||||
|
||||
final class MediaPickerInteraction {
|
||||
let downloadManager: AssetDownloadManager
|
||||
let openMedia: (PHFetchResult<PHAsset>, Int, UIImage?) -> Void
|
||||
let openSelectedMedia: (TGMediaSelectableItem, UIImage?) -> Void
|
||||
let openDraft: (MediaEditorDraft, UIImage?) -> Void
|
||||
@ -36,7 +37,8 @@ final class MediaPickerInteraction {
|
||||
let editingState: TGMediaEditingContext
|
||||
var hiddenMediaId: String?
|
||||
|
||||
init(openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||
init(downloadManager: AssetDownloadManager, openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, @escaping () -> Void) -> Void, schedule: @escaping () -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
|
||||
self.downloadManager = downloadManager
|
||||
self.openMedia = openMedia
|
||||
self.openSelectedMedia = openSelectedMedia
|
||||
self.openDraft = openDraft
|
||||
@ -393,6 +395,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.selectionChangedDisposable?.dispose()
|
||||
self.itemsDimensionsUpdatedDisposable?.dispose()
|
||||
self.fastScrollDisposable?.dispose()
|
||||
self.currentAssetDownloadDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -777,8 +780,29 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
|
||||
private weak var currentGalleryController: TGModernGalleryController?
|
||||
|
||||
private func requestAssetDownload(_ asset: PHAsset) {
|
||||
|
||||
fileprivate var currentAssetDownloadDisposable = MetaDisposable()
|
||||
|
||||
fileprivate func cancelAssetDownloads() {
|
||||
guard let downloadManager = self.controller?.downloadManager else {
|
||||
return
|
||||
}
|
||||
self.currentAssetDownloadDisposable.set(nil)
|
||||
downloadManager.cancelAllDownloads()
|
||||
}
|
||||
|
||||
fileprivate func requestAssetDownload(asset: PHAsset) {
|
||||
guard let downloadManager = self.controller?.downloadManager else {
|
||||
return
|
||||
}
|
||||
downloadManager.download(asset: asset)
|
||||
self.currentAssetDownloadDisposable.set(
|
||||
(downloadManager.downloadProgress(identifier: asset.localIdentifier)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
if let self, case .completed = status, let controller = self.controller, let customSelection = self.controller?.customSelection {
|
||||
customSelection(controller, asset)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
private var openingMedia = false
|
||||
@ -794,26 +818,19 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.openingMedia = true
|
||||
|
||||
let asset = fetchResult[index]
|
||||
customSelection(controller, asset)
|
||||
|
||||
// let isLocallyAvailable = asset.isLocallyAvailable
|
||||
//
|
||||
// if let isLocallyAvailable {
|
||||
// if isLocallyAvailable {
|
||||
// customSelection(controller, asset)
|
||||
// } else {
|
||||
// self.requestAssetDownload(asset)
|
||||
// }
|
||||
// } else {
|
||||
// let _ = (checkIfAssetIsLocal(asset)
|
||||
// |> deliverOnMainQueue).start(next: { [weak self] isLocallyAvailable in
|
||||
// if isLocallyAvailable {
|
||||
// customSelection(controller, asset)
|
||||
// } else {
|
||||
// self?.requestAssetDownload(asset)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
let _ = (checkIfAssetIsLocal(asset)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] isLocallyAvailable in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if isLocallyAvailable {
|
||||
self.cancelAssetDownloads()
|
||||
customSelection(controller, asset)
|
||||
} else {
|
||||
self.requestAssetDownload(asset: asset)
|
||||
}
|
||||
})
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
self.openingMedia = false
|
||||
@ -1365,6 +1382,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
private let groupedPromise = ValuePromise<Bool>(true)
|
||||
|
||||
private let downloadManager = AssetDownloadManager()
|
||||
|
||||
private var isDismissing = false
|
||||
|
||||
fileprivate let mainButtonState: AttachmentMainButtonState?
|
||||
@ -1538,13 +1557,17 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
}
|
||||
}
|
||||
|
||||
self.interaction = MediaPickerInteraction(openMedia: { [weak self] fetchResult, index, immediateThumbnail in
|
||||
self.interaction = MediaPickerInteraction(downloadManager: self.downloadManager,
|
||||
openMedia: { [weak self] fetchResult, index, immediateThumbnail in
|
||||
self?.controllerNode.openMedia(fetchResult: fetchResult, index: index, immediateThumbnail: immediateThumbnail)
|
||||
}, openSelectedMedia: { [weak self] item, immediateThumbnail in
|
||||
},
|
||||
openSelectedMedia: { [weak self] item, immediateThumbnail in
|
||||
self?.controllerNode.openSelectedMedia(item: item, immediateThumbnail: immediateThumbnail)
|
||||
}, openDraft: { [weak self] draft, immediateThumbnail in
|
||||
},
|
||||
openDraft: { [weak self] draft, immediateThumbnail in
|
||||
self?.controllerNode.openDraft(draft: draft, immediateThumbnail: immediateThumbnail)
|
||||
}, toggleSelection: { [weak self] item, value, suggestUndo in
|
||||
},
|
||||
toggleSelection: { [weak self] item, value, suggestUndo in
|
||||
if let self = self, let selectionState = self.interaction?.selectionState {
|
||||
if let _ = item as? TGMediaPickerGalleryPhotoItem {
|
||||
if self.bannedSendPhotos != nil {
|
||||
@ -1798,7 +1821,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.undoOverlayController?.dismissWithCommitAction()
|
||||
}
|
||||
|
||||
public func requestDismiss(completion: @escaping () -> Void) {
|
||||
public func requestDismiss(completion: @escaping () -> Void) {
|
||||
if let selectionState = self.interaction?.selectionState, selectionState.count() > 0 {
|
||||
self.isDismissing = true
|
||||
|
||||
@ -1837,6 +1860,12 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
|
||||
self.controllerNode.cancelAssetDownloads()
|
||||
|
||||
super.dismiss(animated: flag, completion: completion)
|
||||
}
|
||||
|
||||
@objc private func rightButtonPressed() {
|
||||
self.moreButtonNode.buttonPressed()
|
||||
}
|
||||
|
@ -284,6 +284,7 @@ final class PendingStoryManager {
|
||||
self.currentPendingItemContext = pendingItemContext
|
||||
|
||||
let stableId = firstItem.stableId
|
||||
Logger.shared.log("PendingStoryManager", "setting up item context for: \(firstItem.stableId) randomId: \(firstItem.randomId)")
|
||||
pendingItemContext.disposable = (_internal_uploadStoryImpl(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.revalidationContext, auxiliaryMethods: self.auxiliaryMethods, stableId: stableId, media: firstItem.media, text: firstItem.text, entities: firstItem.entities, embeddedStickers: firstItem.embeddedStickers, pin: firstItem.pin, privacy: firstItem.privacy, isForwardingDisabled: firstItem.isForwardingDisabled, period: Int(firstItem.period), randomId: firstItem.randomId)
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] event in
|
||||
guard let `self` = self else {
|
||||
|
@ -755,6 +755,7 @@ func _internal_uploadStory(account: Account, media: EngineStoryInputMedia, text:
|
||||
period: Int32(period),
|
||||
randomId: randomId
|
||||
))
|
||||
Logger.shared.log("UploadStory", "Appended new pending item stableId: \(stableId) randomId: \(randomId)")
|
||||
transaction.setLocalStoryState(state: CodableEntry(currentState))
|
||||
}).start()
|
||||
}
|
||||
@ -798,6 +799,7 @@ private func _internal_putPendingStoryIdMapping(accountPeerId: PeerId, stableId:
|
||||
}
|
||||
|
||||
func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId: PeerId, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, auxiliaryMethods: AccountAuxiliaryMethods, stableId: Int32, media: Media, text: String, entities: [MessageTextEntity], embeddedStickers: [TelegramMediaFile], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64) -> Signal<StoryUploadResult, NoError> {
|
||||
Logger.shared.log("UploadStory", "uploadStoryImpl for stableId: \(stableId) randomId: \(randomId)")
|
||||
let passFetchProgress = media is TelegramMediaFile
|
||||
let (contentSignal, originalMedia) = uploadedStoryContent(postbox: postbox, network: network, media: media, embeddedStickers: embeddedStickers, accountPeerId: accountPeerId, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, auxiliaryMethods: auxiliaryMethods, passFetchProgress: passFetchProgress)
|
||||
return contentSignal
|
||||
|
@ -303,6 +303,8 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
self.isPressingButton = true
|
||||
}
|
||||
self.buttonPressTimestamp = nil
|
||||
self.buttonPressTimer?.invalidate()
|
||||
self.buttonPressTimer = nil
|
||||
}
|
||||
}, queue: Queue.mainQueue())
|
||||
self.buttonPressTimer?.start()
|
||||
@ -641,11 +643,15 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
}
|
||||
controller.presentGallery()
|
||||
},
|
||||
swipeHintUpdated: { hint in
|
||||
state.updateSwipeHint(hint)
|
||||
swipeHintUpdated: { [weak state] hint in
|
||||
if let state {
|
||||
state.updateSwipeHint(hint)
|
||||
}
|
||||
},
|
||||
zoomUpdated: { fraction in
|
||||
state.updateZoom(fraction: fraction)
|
||||
zoomUpdated: { [weak state] fraction in
|
||||
if let state {
|
||||
state.updateZoom(fraction: fraction)
|
||||
}
|
||||
},
|
||||
flipAnimationAction: animateFlipAction
|
||||
),
|
||||
@ -737,10 +743,9 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
component: CameraButton(
|
||||
content: flashContentComponent,
|
||||
action: { [weak state] in
|
||||
guard let state else {
|
||||
return
|
||||
if let state {
|
||||
state.toggleFlashMode()
|
||||
}
|
||||
state.toggleFlashMode()
|
||||
}
|
||||
).tagged(flashButtonTag),
|
||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||
@ -762,10 +767,9 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
)
|
||||
),
|
||||
action: { [weak state] in
|
||||
guard let state else {
|
||||
return
|
||||
if let state {
|
||||
state.toggleDualCamera()
|
||||
}
|
||||
state.toggleDualCamera()
|
||||
}
|
||||
).tagged(dualButtonTag),
|
||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||
|
@ -89,8 +89,8 @@ private enum StickerSearchEntry: Identifiable, Comparable {
|
||||
func item(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, interaction: StickerPaneSearchInteraction, inputNodeInteraction: ChatMediaInputNodeInteraction) -> GridItem {
|
||||
switch self {
|
||||
case let .sticker(_, code, stickerItem, theme):
|
||||
return StickerPaneSearchStickerItem(context: context, code: code, stickerItem: stickerItem, inputNodeInteraction: inputNodeInteraction, theme: theme, selected: { node, rect in
|
||||
interaction.sendSticker(.standalone(media: stickerItem.file), node.view, rect)
|
||||
return StickerPaneSearchStickerItem(context: context, theme: theme, code: code, stickerItem: stickerItem, inputNodeInteraction: inputNodeInteraction, selected: { node, layer, rect in
|
||||
interaction.sendSticker(.standalone(media: stickerItem.file), node.view, layer, rect)
|
||||
})
|
||||
case let .global(_, info, topItems, installed, topSeparator):
|
||||
let itemContext = StickerPaneSearchGlobalItemContext()
|
||||
@ -316,9 +316,10 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
})
|
||||
}
|
||||
}, sendSticker: { [weak self] file, sourceView, sourceRect in
|
||||
if let strongSelf = self {
|
||||
let _ = strongSelf.interaction.sendSticker(file, false, false, nil, false, sourceView, sourceRect, nil, [])
|
||||
}, sendSticker: { [weak self] file, sourceView, sourceLayer, sourceRect in
|
||||
if let self {
|
||||
let sourceRect = sourceView.convert(sourceRect, to: self.view)
|
||||
let _ = self.interaction.sendSticker(file, false, false, nil, false, self.view, sourceRect, sourceLayer, [])
|
||||
}
|
||||
}, getItemIsPreviewed: { item in
|
||||
return inputNodeInteraction.previewedStickerPackItemFile?.id == item.file.id
|
||||
|
@ -743,10 +743,11 @@ public final class EntityKeyboardComponent: Component {
|
||||
panelHideBehavior = .hideOnScroll
|
||||
}
|
||||
|
||||
let isContentInFocus = component.isContentInFocus && self.searchComponent == nil
|
||||
let pagerSize = self.pagerView.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(PagerComponent(
|
||||
isContentInFocus: component.isContentInFocus,
|
||||
isContentInFocus: isContentInFocus,
|
||||
contentInsets: component.containerInsets,
|
||||
contents: contents,
|
||||
contentTopPanels: contentTopPanels,
|
||||
@ -801,7 +802,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
EntityKeyboardChildEnvironment(
|
||||
theme: component.theme,
|
||||
strings: component.strings,
|
||||
isContentInFocus: component.isContentInFocus,
|
||||
isContentInFocus: isContentInFocus,
|
||||
getContentActiveItemUpdated: { id in
|
||||
if id == AnyHashable("gifs") {
|
||||
return gifsContentItemIdUpdated
|
||||
@ -950,7 +951,7 @@ public final class EntityKeyboardComponent: Component {
|
||||
}
|
||||
)
|
||||
}
|
||||
//self.state?.updated(transition: Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
|
||||
component.hideInputUpdated(true, true, Transition(animation: .curve(duration: 0.3, curve: .spring)))
|
||||
}
|
||||
}
|
||||
|
@ -507,7 +507,9 @@ public final class MediaEditor {
|
||||
self.onPlaybackAction(.seek(start))
|
||||
self.player?.play()
|
||||
self.additionalPlayer?.play()
|
||||
self.onPlaybackAction(.play)
|
||||
Queue.mainQueue().justDispatch {
|
||||
self.onPlaybackAction(.play)
|
||||
}
|
||||
}
|
||||
})
|
||||
Queue.mainQueue().justDispatch {
|
||||
|
@ -3615,6 +3615,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
}
|
||||
let fittedSize = resultImage.size.aspectFitted(CGSize(width: 128.0, height: 128.0))
|
||||
|
||||
let context = self.context
|
||||
let saveImageDraft: (UIImage, PixelDimensions) -> Void = { image, dimensions in
|
||||
if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
|
||||
let path = "\(Int64.random(in: .min ... .max)).jpg"
|
||||
@ -3622,9 +3623,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
let draft = MediaEditorDraft(path: path, isVideo: false, thumbnail: thumbnailImage, dimensions: dimensions, duration: nil, values: values, caption: caption, privacy: privacy, timestamp: timestamp)
|
||||
try? data.write(to: URL(fileURLWithPath: draft.fullPath()))
|
||||
if let id {
|
||||
saveStorySource(engine: self.context.engine, item: draft, id: id)
|
||||
saveStorySource(engine: context.engine, item: draft, id: id)
|
||||
} else {
|
||||
addStoryDraft(engine: self.context.engine, item: draft)
|
||||
addStoryDraft(engine: context.engine, item: draft)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3636,9 +3637,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
let draft = MediaEditorDraft(path: path, isVideo: true, thumbnail: thumbnailImage, dimensions: dimensions, duration: duration, values: values, caption: caption, privacy: privacy, timestamp: timestamp)
|
||||
try? FileManager.default.moveItem(atPath: videoPath, toPath: draft.fullPath())
|
||||
if let id {
|
||||
saveStorySource(engine: self.context.engine, item: draft, id: id)
|
||||
saveStorySource(engine: context.engine, item: draft, id: id)
|
||||
} else {
|
||||
addStoryDraft(engine: self.context.engine, item: draft)
|
||||
addStoryDraft(engine: context.engine, item: draft)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3861,6 +3862,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
if let self {
|
||||
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image ?? UIImage(), dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in
|
||||
if let self {
|
||||
Logger.shared.log("Media Editor", "completed with video \(videoResult)")
|
||||
self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, stickers, { [weak self] finished in
|
||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||
self?.dismiss()
|
||||
@ -3883,6 +3885,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
||||
|
||||
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] resultImage in
|
||||
if let self, let resultImage {
|
||||
Logger.shared.log("Media Editor", "completed with image \(resultImage)")
|
||||
self.completion(randomId, .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), caption, self.state.privacy, stickers, { [weak self] finished in
|
||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||
self?.dismiss()
|
||||
|
@ -322,10 +322,10 @@ final class ContextResultPanelComponent: Component {
|
||||
|
||||
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: minimizedHeight)))
|
||||
|
||||
let visibleTopContentHeight = min(scrollContentSize.height, measureItemSize.height * 3.5 + 19.0)
|
||||
let visibleTopContentHeight = min(scrollContentSize.height, measureItemSize.height * 3.5)
|
||||
let topInset = availableSize.height - visibleTopContentHeight
|
||||
|
||||
let scrollContentInsets = UIEdgeInsets(top: topInset, left: 0.0, bottom: 19.0, right: 0.0)
|
||||
let scrollContentInsets = UIEdgeInsets(top: topInset, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
let scrollIndicatorInsets = UIEdgeInsets(top: topInset + 17.0, left: 0.0, bottom: 19.0, right: 0.0)
|
||||
if self.scrollView.contentInset != scrollContentInsets {
|
||||
self.scrollView.contentInset = scrollContentInsets
|
||||
|
@ -1449,7 +1449,7 @@ public final class MessageInputPanelComponent: Component {
|
||||
containerSize: CGSize(width: availableSize.width - panelLeftInset - panelRightInset, height: availablePanelHeight)
|
||||
)
|
||||
|
||||
let panelFrame = CGRect(origin: CGPoint(x: insets.left, y: -panelSize.height + 33.0), size: panelSize)
|
||||
let panelFrame = CGRect(origin: CGPoint(x: insets.left, y: -panelSize.height + 14.0), size: CGSize(width: panelSize.width, height: panelSize.height + 19.0))
|
||||
if let panelView = panel.view as? ContextResultPanelComponent.View {
|
||||
if panelView.superview == nil {
|
||||
self.insertSubview(panelView, at: 0)
|
||||
|
@ -295,12 +295,7 @@ final class StickersResultPanelComponent: Component {
|
||||
let controller = PeekController(presentationData: presentationData, content: content, sourceView: {
|
||||
return (sourceView, sourceRect)
|
||||
})
|
||||
// controller.visibilityUpdated = { [weak self] visible in
|
||||
// self?.previewingStickersPromise.set(visible)
|
||||
// }
|
||||
component.presentInGlobalOverlay(controller)
|
||||
// strongSelf.peekController = controller
|
||||
// strongSelf.getControllerInteraction?()?.presentGlobalOverlayController(controller, nil)
|
||||
return controller
|
||||
}
|
||||
return nil
|
||||
|
@ -2160,8 +2160,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
})
|
||||
} else if let sourceNode = sourceView.asyncdisplaykit_node as? HorizontalStickerGridItemNode {
|
||||
strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .mediaPanel(itemNode: sourceNode), replyPanel: replyPanel), initiated: {})
|
||||
} else if let sourceNode = sourceView.asyncdisplaykit_node as? StickerPaneSearchStickerItemNode {
|
||||
strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .inputPanelSearch(itemNode: sourceNode), replyPanel: replyPanel), initiated: {})
|
||||
} else if let sourceNode = sourceView.asyncdisplaykit_node as? ChatEmptyNodeStickerContentNode {
|
||||
strongSelf.chatDisplayNode.messageTransitionNode.add(correlationId: correlationId, source: .stickerMediaInput(input: .emptyPanel(itemNode: sourceNode), replyPanel: nil), initiated: {})
|
||||
} else if let sourceLayer = sourceLayer {
|
||||
|
@ -188,7 +188,6 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti
|
||||
case inputPanel(itemNode: ChatMediaInputStickerGridItemNode)
|
||||
case mediaPanel(itemNode: HorizontalStickerGridItemNode)
|
||||
case universal(sourceContainerView: UIView, sourceRect: CGRect, sourceLayer: CALayer)
|
||||
case inputPanelSearch(itemNode: StickerPaneSearchStickerItemNode)
|
||||
case emptyPanel(itemNode: ChatEmptyNodeStickerContentNode)
|
||||
}
|
||||
|
||||
@ -442,9 +441,6 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti
|
||||
case let .universal(sourceContainerView, sourceRect, sourceLayer):
|
||||
stickerSource = Sticker(imageNode: nil, animationNode: nil, placeholderNode: nil, imageLayer: sourceLayer, relativeSourceRect: sourceLayer.frame)
|
||||
sourceAbsoluteRect = convertAnimatingSourceRect(sourceRect, fromView: sourceContainerView, toView: self.view)
|
||||
case let .inputPanelSearch(sourceItemNode):
|
||||
stickerSource = Sticker(imageNode: sourceItemNode.imageNode, animationNode: sourceItemNode.animationNode, placeholderNode: nil, imageLayer: nil, relativeSourceRect: sourceItemNode.imageNode.frame)
|
||||
sourceAbsoluteRect = sourceItemNode.view.convert(sourceItemNode.imageNode.frame, to: self.view)
|
||||
case let .emptyPanel(sourceItemNode):
|
||||
stickerSource = Sticker(imageNode: sourceItemNode.stickerNode.imageNode, animationNode: sourceItemNode.stickerNode.animationNode, placeholderNode: nil, imageLayer: nil, relativeSourceRect: sourceItemNode.stickerNode.imageNode.frame)
|
||||
sourceAbsoluteRect = sourceItemNode.stickerNode.view.convert(sourceItemNode.stickerNode.imageNode.frame, to: self.view)
|
||||
@ -495,8 +491,6 @@ public final class ChatMessageTransitionNode: ASDisplayNode, ChatMessageTransiti
|
||||
break
|
||||
case let .mediaPanel(sourceItemNode):
|
||||
sourceItemNode.isHidden = true
|
||||
case let .inputPanelSearch(sourceItemNode):
|
||||
sourceItemNode.isHidden = true
|
||||
case let .emptyPanel(sourceItemNode):
|
||||
sourceItemNode.isHidden = true
|
||||
}
|
||||
|
@ -374,6 +374,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
case let .image(image, dimensions):
|
||||
if let imageData = compressImageToJPEG(image, quality: 0.7) {
|
||||
let entities = generateChatInputTextEntities(caption)
|
||||
Logger.shared.log("MediaEditor", "Calling uploadStory for image, randomId \(randomId)")
|
||||
self.context.engine.messages.uploadStory(media: .image(dimensions: dimensions, data: imageData, stickers: stickers), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId)
|
||||
Queue.mainQueue().justDispatch {
|
||||
commit({})
|
||||
@ -404,7 +405,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
Logger.shared.log("MediaEditor", "Calling uploadStory for video, randomId \(randomId)")
|
||||
let entities = generateChatInputTextEntities(caption)
|
||||
self.context.engine.messages.uploadStory(media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: stickers), text: caption.string, entities: entities, pin: privacy.pin, privacy: privacy.privacy, isForwardingDisabled: privacy.isForwardingDisabled, period: privacy.timeout, randomId: randomId)
|
||||
Queue.mainQueue().justDispatch {
|
||||
|
Loading…
x
Reference in New Issue
Block a user