diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift index 51d6de208c..3ffcd997c6 100644 --- a/Telegram/NotificationService/Sources/NotificationService.swift +++ b/Telegram/NotificationService/Sources/NotificationService.swift @@ -1707,7 +1707,7 @@ private final class NotificationServiceHandler { } else if let file = media as? TelegramMediaFile { resource = file.resource for attribute in file.attributes { - if case let .Video(_, _, _, preloadSize) = attribute { + if case let .Video(_, _, _, preloadSize, _) = attribute { fetchSize = preloadSize.flatMap(Int64.init) } } diff --git a/submodules/AccountContext/Sources/IsMediaStreamable.swift b/submodules/AccountContext/Sources/IsMediaStreamable.swift index a562f0a32c..650911e79a 100644 --- a/submodules/AccountContext/Sources/IsMediaStreamable.swift +++ b/submodules/AccountContext/Sources/IsMediaStreamable.swift @@ -18,7 +18,7 @@ public func isMediaStreamable(message: Message, media: TelegramMediaFile) -> Boo return false } for attribute in media.attributes { - if case let .Video(_, _, flags, _) = attribute { + if case let .Video(_, _, flags, _, _) = attribute { if flags.contains(.supportsStreaming) { return true } @@ -41,7 +41,7 @@ public func isMediaStreamable(media: TelegramMediaFile) -> Bool { return false } for attribute in media.attributes { - if case let .Video(_, _, flags, _) = attribute { + if case let .Video(_, _, flags, _, _) = attribute { if flags.contains(.supportsStreaming) { return true } diff --git a/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift b/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift index 634438cafa..15fc596bea 100644 --- a/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift +++ b/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift @@ -206,7 +206,7 @@ public final class AvatarVideoNode: ASDisplayNode { self.backgroundNode.image = nil let videoId = photo.id?.id ?? peer.id.id._internalGetInt64Value() - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [], preloadSize: nil)])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [], preloadSize: nil, coverTime: nil)])) let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: false, storeAfterDownload: nil) if videoContent.id != self.videoContent?.id { self.videoNode?.removeFromSupernode() diff --git a/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift b/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift index 0442a760a1..68e3aa5abd 100644 --- a/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift +++ b/submodules/ChatImportUI/Sources/ChatImportActivityScreen.swift @@ -460,7 +460,7 @@ public final class ChatImportActivityScreen: ViewController { if let path = getAppBundle().path(forResource: "BlankVideo", ofType: "m4v"), let size = fileSize(path) { let decoration = ChatBubbleVideoDecoration(corners: ImageCorners(), nativeSize: CGSize(width: 100.0, height: 100.0), contentMode: .aspectFit, backgroundColor: .black) - let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [], preloadSize: nil)]) + let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [], preloadSize: nil, coverTime: nil)]) let videoContent = NativeVideoContent(id: .message(1, EngineMedia.Id(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 232dc37a49..87b6d3462a 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -2576,7 +2576,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { case let .preview(dimensions, immediateThumbnailData, videoDuration): if let immediateThumbnailData { if let videoDuration { - let thumbnailMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: index), partialReference: nil, resource: EmptyMediaResource(), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Video(duration: Double(videoDuration), size: dimensions ?? PixelDimensions(width: 1, height: 1), flags: [], preloadSize: nil)]) + let thumbnailMedia = TelegramMediaFile(fileId: MediaId(namespace: 0, id: index), partialReference: nil, resource: EmptyMediaResource(), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Video(duration: Double(videoDuration), size: dimensions ?? PixelDimensions(width: 1, height: 1), flags: [], preloadSize: nil, coverTime: nil)]) contentImageSpecs.append(ContentImageSpec(message: message, media: .file(thumbnailMedia), size: fitSize)) } else { let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: index), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: []) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift index 5d0f029f29..4dc4de1c29 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift @@ -246,7 +246,7 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder: processed = true break inner } - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if flags.contains(.instantRoundVideo) { messageText = strings.Message_VideoMessage processed = true diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index 6beea637a0..c4bbe07918 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -836,7 +836,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll } else if let media = media as? TelegramMediaFile, !media.isAnimated { for attribute in media.attributes { switch attribute { - case let .Video(_, dimensions, _, _): + case let .Video(_, dimensions, _, _, _): isVideo = true if dimensions.height > 0 { if CGFloat(dimensions.width) / CGFloat(dimensions.height) > 1.33 { diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index a73a4b3923..7c82a528e0 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -1235,7 +1235,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode { } if let file = file { for attribute in file.attributes { - if case let .Video(duration, _, _, _) = attribute, duration >= 30 { + if case let .Video(duration, _, _, _, _) = attribute, duration >= 30 { hintSeekable = true break } diff --git a/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift b/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift index c45782b8e4..c8fcfe9691 100644 --- a/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift +++ b/submodules/InstantPageUI/Sources/InstantPageMediaPlaylist.swift @@ -53,7 +53,7 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem { } else { return SharedMediaPlaybackData(type: .music, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false, isViewOnce: false)) } - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if flags.contains(.instantRoundVideo) { return SharedMediaPlaybackData(type: .instantVideo, source: .telegramFile(reference: .webPage(webPage: WebpageReference(self.webPage), media: file), isCopyProtected: false, isViewOnce: false)) } else { @@ -99,7 +99,7 @@ final class InstantPageMediaPlaylistItem: SharedMediaPlaylistItem { return SharedMediaPlaybackDisplayData.music(title: updatedTitle, performer: updatedPerformer, albumArt: albumArt, long: false, caption: nil) } - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if flags.contains(.instantRoundVideo) { return SharedMediaPlaybackDisplayData.instantVideo(author: nil, peer: nil, timestamp: 0) } else { diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift index 5ae4ac8850..7425988c60 100644 --- a/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift +++ b/submodules/LegacyMediaPickerUI/Sources/LegacyMediaPickers.swift @@ -294,7 +294,7 @@ public func legacyEnqueueGifMessage(account: Account, data: Data, correlationId: let finalDimensions = TGMediaVideoConverter.dimensions(for: dimensions, adjustments: nil, preset: TGMediaVideoConversionPresetAnimation) var fileAttributes: [TelegramMediaFileAttribute] = [] - fileAttributes.append(.Video(duration: 0.0, size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil)) + fileAttributes.append(.Video(duration: 0.0, size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil, coverTime: nil)) fileAttributes.append(.FileName(fileName: fileName)) fileAttributes.append(.Animated) @@ -336,7 +336,7 @@ public func legacyEnqueueVideoMessage(account: Account, data: Data, correlationI let finalDimensions = TGMediaVideoConverter.dimensions(for: dimensions, adjustments: nil, preset: TGMediaVideoConversionPresetAnimation) var fileAttributes: [TelegramMediaFileAttribute] = [] - fileAttributes.append(.Video(duration: 0.0, size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil)) + fileAttributes.append(.Video(duration: 0.0, size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil, coverTime: nil)) fileAttributes.append(.FileName(fileName: fileName)) fileAttributes.append(.Animated) @@ -857,7 +857,7 @@ public func legacyAssetPickerEnqueueMessages(context: AccountContext, account: A fileAttributes.append(.Animated) } if !asFile { - fileAttributes.append(.Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil)) + fileAttributes.append(.Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: [.supportsStreaming], preloadSize: nil, coverTime: nil)) if let adjustments = adjustments { if adjustments.sendAsGif { fileAttributes.append(.Animated) diff --git a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift index 6a74a5da18..37b5bc76e6 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift @@ -187,7 +187,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { let subject: ShareControllerSubject var actionCompletionText: String? if let video = entry.videoRepresentations.last, let peerReference = PeerReference(peer._asPeer()) { - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil)])) subject = .media(videoFileReference.abstract) actionCompletionText = strongSelf.presentationData.strings.Gallery_VideoSaved } else { @@ -279,7 +279,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { if let video = entry.videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) { if video != previousVideoRepresentations?.last { let mediaManager = self.context.sharedContext.mediaManager - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: entry.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: entry.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil)])) let videoContent = NativeVideoContent(id: .profileVideo(id, category), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: true, useLargeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) let videoNode = UniversalVideoNode(postbox: self.context.account.postbox, audioSession: mediaManager.audioSession, manager: mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: videoContent, priority: .overlay) videoNode.isUserInteractionEnabled = false diff --git a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift index 3a5f259483..a3ba818d23 100644 --- a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift +++ b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift @@ -515,7 +515,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode { self.isReady.set(.single(true)) } } else if let video = videoRepresentations.last, let peerReference = PeerReference(self.peer._asPeer()) { - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil)])) let videoContent = NativeVideoContent(id: .profileVideo(id, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: fullSizeOnly, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) if videoContent.id != self.videoContent?.id { diff --git a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift index 1d6c98dede..292cc01841 100644 --- a/submodules/ShareController/Sources/ShareLoadingContainerNode.swift +++ b/submodules/ShareController/Sources/ShareLoadingContainerNode.swift @@ -279,7 +279,7 @@ public final class ShareProlongedLoadingContainerNode: ASDisplayNode, ShareConte if let postbox, let mediaManager = environment.mediaManager, let path = getAppBundle().path(forResource: "BlankVideo", ofType: "m4v"), let size = fileSize(path) { let decoration = ChatBubbleVideoDecoration(corners: ImageCorners(), nativeSize: CGSize(width: 100.0, height: 100.0), contentMode: .aspectFit, backgroundColor: .black) - let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [], preloadSize: nil)]) + let dummyFile = TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 1), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: 12345), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: size, attributes: [.Video(duration: 1, size: PixelDimensions(width: 100, height: 100), flags: [], preloadSize: nil, coverTime: nil)]) let videoContent = NativeVideoContent(id: .message(1, EngineMedia.Id(namespace: 0, id: 1)), userLocation: .other, fileReference: .standalone(media: dummyFile), streamVideo: .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .black, storeAfterDownload: nil) diff --git a/submodules/ShareItems/Sources/ShareItems.swift b/submodules/ShareItems/Sources/ShareItems.swift index 3ff8e54ca6..71ae6be423 100644 --- a/submodules/ShareItems/Sources/ShareItems.swift +++ b/submodules/ShareItems/Sources/ShareItems.swift @@ -144,7 +144,7 @@ private func preparedShareItem(postbox: Postbox, network: Network, to peerId: Pe let estimatedSize = TGMediaVideoConverter.estimatedSize(for: preset, duration: finalDuration, hasAudio: true) let resource = LocalFileVideoMediaResource(randomId: Int64.random(in: Int64.min ... Int64.max), path: asset.url.path, adjustments: resourceAdjustments) - return standaloneUploadedFile(postbox: postbox, network: network, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: finalDuration, size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags, preloadSize: nil)], hintFileIsLarge: estimatedSize > 10 * 1024 * 1024) + return standaloneUploadedFile(postbox: postbox, network: network, peerId: peerId, text: "", source: .resource(.standalone(resource: resource)), mimeType: "video/mp4", attributes: [.Video(duration: finalDuration, size: PixelDimensions(width: Int32(finalDimensions.width), height: Int32(finalDimensions.height)), flags: flags, preloadSize: nil, coverTime: nil)], hintFileIsLarge: estimatedSize > 10 * 1024 * 1024) |> mapError { _ -> PreparedShareItemError in return .generic } @@ -210,7 +210,7 @@ private func preparedShareItem(postbox: Postbox, network: Network, to peerId: Pe let mimeType: String if converted { mimeType = "video/mp4" - attributes = [.Video(duration: duration, size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height)), flags: [.supportsStreaming], preloadSize: nil), .Animated, .FileName(fileName: "animation.mp4")] + attributes = [.Video(duration: duration, size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height)), flags: [.supportsStreaming], preloadSize: nil, coverTime: nil), .Animated, .FileName(fileName: "animation.mp4")] } else { mimeType = "animation/gif" attributes = [.ImageSize(size: PixelDimensions(width: Int32(dimensions.width), height: Int32(dimensions.height))), .Animated, .FileName(fileName: fileName ?? "animation.gif")] diff --git a/submodules/StatisticsUI/Sources/ChannelStatsController.swift b/submodules/StatisticsUI/Sources/ChannelStatsController.swift index 2b6a0aba20..a294b33a05 100644 --- a/submodules/StatisticsUI/Sources/ChannelStatsController.swift +++ b/submodules/StatisticsUI/Sources/ChannelStatsController.swift @@ -1677,7 +1677,7 @@ private func monetizationEntries( } } - if isCreator { + if isCreator && canViewRevenue { var switchOffAdds: Bool? = nil if let boostData, boostData.level >= premiumConfiguration.minChannelRestrictAdsLevel { switchOffAdds = adsRestricted diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index e8d07b397e..8d07904fd2 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -50,7 +50,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], var isAnimated = false inner: for attribute in file.attributes { switch attribute { - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if flags.contains(.instantRoundVideo) { refinedTag = .voiceOrInstantVideo } else { diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift index 2d3c10d386..611db7cedc 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaFile.swift @@ -6,7 +6,7 @@ import TelegramApi func dimensionsForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> PixelDimensions? { for attribute in attributes { switch attribute { - case let .Video(_, size, _, _): + case let .Video(_, size, _, _, _): return size case let .ImageSize(size): return size @@ -20,7 +20,7 @@ func dimensionsForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> func durationForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> Double? { for attribute in attributes { switch attribute { - case let .Video(duration, _, _, _): + case let .Video(duration, _, _, _, _): return duration case let .Audio(_, duration, _, _, _): return Double(duration) @@ -99,7 +99,7 @@ func telegramMediaFileAttributesFromApiAttributes(_ attributes: [Api.DocumentAtt result.append(.ImageSize(size: PixelDimensions(width: w, height: h))) case .documentAttributeAnimated: result.append(.Animated) - case let .documentAttributeVideo(flags, duration, w, h, preloadSize, _): + case let .documentAttributeVideo(flags, duration, w, h, preloadSize, videoStart): var videoFlags = TelegramMediaVideoFlags() if (flags & (1 << 0)) != 0 { videoFlags.insert(.instantRoundVideo) @@ -110,7 +110,7 @@ func telegramMediaFileAttributesFromApiAttributes(_ attributes: [Api.DocumentAtt if (flags & (1 << 3)) != 0 { videoFlags.insert(.isSilent) } - result.append(.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: preloadSize)) + result.append(.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: preloadSize, coverTime: videoStart)) case let .documentAttributeAudio(flags, duration, title, performer, waveform): let isVoice = (flags & (1 << 10)) != 0 let waveformBuffer: Data? = waveform?.makeData() diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index 0ec84b8642..6d3872c492 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -701,7 +701,7 @@ func inputDocumentAttributesFromFileAttributes(_ fileAttributes: [TelegramMediaF attributes.append(.documentAttributeSticker(flags: flags, alt: displayText, stickerset: stickerSet, maskCoords: inputMaskCoords)) case .HasLinkedStickers: attributes.append(.documentAttributeHasStickers) - case let .Video(duration, size, videoFlags, preloadSize): + case let .Video(duration, size, videoFlags, preloadSize, coverTime): var flags: Int32 = 0 if videoFlags.contains(.instantRoundVideo) { flags |= (1 << 0) @@ -715,8 +715,10 @@ func inputDocumentAttributesFromFileAttributes(_ fileAttributes: [TelegramMediaF if videoFlags.contains(.isSilent) { flags |= (1 << 3) } - - attributes.append(.documentAttributeVideo(flags: flags, duration: duration, w: Int32(size.width), h: Int32(size.height), preloadPrefixSize: preloadSize, videoStartTs: nil)) + if let coverTime = coverTime, coverTime > 0.0 { + flags |= (1 << 4) + } + attributes.append(.documentAttributeVideo(flags: flags, duration: duration, w: Int32(size.width), h: Int32(size.height), preloadPrefixSize: preloadSize, videoStartTs: coverTime)) case let .Audio(isVoice, duration, title, performer, waveform): var flags: Int32 = 0 if isVoice { @@ -786,7 +788,7 @@ public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileA } else { return .audio } - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if flags.contains(TelegramMediaVideoFlags.instantRoundVideo) { return .voiceMessages } else { diff --git a/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift b/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift index 20b9c662dd..88374c2a7e 100644 --- a/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift +++ b/submodules/TelegramCore/Sources/State/ManagedSecretChatOutgoingOperations.swift @@ -553,7 +553,7 @@ private func decryptedAttributes46(_ attributes: [TelegramMediaFileAttribute], t result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet)) case let .ImageSize(size): result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height))) - case let .Video(duration, size, _, _): + case let .Video(duration, size, _, _, _): result.append(.documentAttributeVideo(duration: Int32(duration), w: Int32(size.width), h: Int32(size.height))) case let .Audio(isVoice, duration, title, performer, waveform): var flags: Int32 = 0 @@ -612,7 +612,7 @@ private func decryptedAttributes73(_ attributes: [TelegramMediaFileAttribute], t result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet)) case let .ImageSize(size): result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height))) - case let .Video(duration, size, videoFlags, _): + case let .Video(duration, size, videoFlags, _, _): var flags: Int32 = 0 if videoFlags.contains(.instantRoundVideo) { flags |= 1 << 0 @@ -675,7 +675,7 @@ private func decryptedAttributes101(_ attributes: [TelegramMediaFileAttribute], result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet)) case let .ImageSize(size): result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height))) - case let .Video(duration, size, videoFlags, _): + case let .Video(duration, size, videoFlags, _, _): var flags: Int32 = 0 if videoFlags.contains(.instantRoundVideo) { flags |= 1 << 0 @@ -738,7 +738,7 @@ private func decryptedAttributes144(_ attributes: [TelegramMediaFileAttribute], result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet)) case let .ImageSize(size): result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height))) - case let .Video(duration, size, videoFlags, _): + case let .Video(duration, size, videoFlags, _, _): var flags: Int32 = 0 if videoFlags.contains(.instantRoundVideo) { flags |= 1 << 0 diff --git a/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift b/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift index 24085d5110..ddf283a43d 100644 --- a/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift +++ b/submodules/TelegramCore/Sources/State/ProcessSecretChatIncomingDecryptedOperations.swift @@ -610,7 +610,7 @@ extension TelegramMediaFileAttribute { } self = .Sticker(displayText: alt, packReference: packReference, maskData: nil) case let .documentAttributeVideo(duration, w, h): - self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil) + self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil, coverTime: nil) } } } @@ -642,7 +642,7 @@ extension TelegramMediaFileAttribute { if (flags & (1 << 0)) != 0 { videoFlags.insert(.instantRoundVideo) } - self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil) + self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil, coverTime: nil) } } } @@ -674,7 +674,7 @@ extension TelegramMediaFileAttribute { if (flags & (1 << 0)) != 0 { videoFlags.insert(.instantRoundVideo) } - self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil) + self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil, coverTime: nil) } } } @@ -706,7 +706,7 @@ extension TelegramMediaFileAttribute { if (flags & (1 << 0)) != 0 { videoFlags.insert(.instantRoundVideo) } - self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil) + self = .Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: videoFlags, preloadSize: nil, coverTime: nil) } } } @@ -821,7 +821,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 text = caption } if let file = file { - let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil), .FileName(fileName: "video.mov")] + let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil, coverTime: nil), .FileName(fileName: "video.mov")] var previewRepresentations: [TelegramMediaImageRepresentation] = [] if thumb.size != 0 { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) @@ -1021,7 +1021,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 loop: for attr in parsedAttributes { switch attr { - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if flags.contains(.instantRoundVideo) { attributes.append(ConsumableContentMessageAttribute(consumed: false)) } @@ -1040,7 +1040,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 text = caption } if let file = file { - let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil), .FileName(fileName: "video.mov")] + let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil, coverTime: nil), .FileName(fileName: "video.mov")] var previewRepresentations: [TelegramMediaImageRepresentation] = [] if thumb.size != 0 { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) @@ -1300,7 +1300,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 loop: for attr in parsedAttributes { switch attr { - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if flags.contains(.instantRoundVideo) { attributes.append(ConsumableContentMessageAttribute(consumed: false)) } @@ -1319,7 +1319,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 text = caption } if let file = file { - let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil), .FileName(fileName: "video.mov")] + let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil, coverTime: nil), .FileName(fileName: "video.mov")] var previewRepresentations: [TelegramMediaImageRepresentation] = [] if thumb.size != 0 { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) @@ -1501,7 +1501,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 loop: for attr in parsedAttributes { switch attr { - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if flags.contains(.instantRoundVideo) { attributes.append(ConsumableContentMessageAttribute(consumed: false)) } @@ -1520,7 +1520,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 text = caption } if let file = file { - let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil), .FileName(fileName: "video.mov")] + let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Double(duration), size: PixelDimensions(width: w, height: h), flags: [], preloadSize: nil, coverTime: nil), .FileName(fileName: "video.mov")] var previewRepresentations: [TelegramMediaImageRepresentation] = [] if thumb.size != 0 { let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift index 1ae8b35fd7..aaddf2debb 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaFile.swift @@ -235,7 +235,7 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable { case Sticker(displayText: String, packReference: StickerPackReference?, maskData: StickerMaskCoords?) case ImageSize(size: PixelDimensions) case Animated - case Video(duration: Double, size: PixelDimensions, flags: TelegramMediaVideoFlags, preloadSize: Int32?) + case Video(duration: Double, size: PixelDimensions, flags: TelegramMediaVideoFlags, preloadSize: Int32?, coverTime: Double?) case Audio(isVoice: Bool, duration: Int, title: String?, performer: String?, waveform: Data?) case HasLinkedStickers case hintFileIsLarge @@ -262,7 +262,7 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable { duration = Double(decoder.decodeInt32ForKey("du", orElse: 0)) } - self = .Video(duration: duration, size: PixelDimensions(width: decoder.decodeInt32ForKey("w", orElse: 0), height: decoder.decodeInt32ForKey("h", orElse: 0)), flags: TelegramMediaVideoFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0)), preloadSize: decoder.decodeOptionalInt32ForKey("prs")) + self = .Video(duration: duration, size: PixelDimensions(width: decoder.decodeInt32ForKey("w", orElse: 0), height: decoder.decodeInt32ForKey("h", orElse: 0)), flags: TelegramMediaVideoFlags(rawValue: decoder.decodeInt32ForKey("f", orElse: 0)), preloadSize: decoder.decodeOptionalInt32ForKey("prs"), coverTime: decoder.decodeOptionalDoubleForKey("ct")) case typeAudio: let waveformBuffer = decoder.decodeBytesForKeyNoCopy("wf") var waveform: Data? @@ -309,7 +309,7 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable { encoder.encodeInt32(Int32(size.height), forKey: "h") case .Animated: encoder.encodeInt32(typeAnimated, forKey: "t") - case let .Video(duration, size, flags, preloadSize): + case let .Video(duration, size, flags, preloadSize, coverTime): encoder.encodeInt32(typeVideo, forKey: "t") encoder.encodeDouble(duration, forKey: "dur") encoder.encodeInt32(Int32(size.width), forKey: "w") @@ -320,6 +320,11 @@ public enum TelegramMediaFileAttribute: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "prs") } + if let coverTime = coverTime { + encoder.encodeDouble(coverTime, forKey: "ct") + } else { + encoder.encodeNil(forKey: "ct") + } case let .Audio(isVoice, duration, title, performer, waveform): encoder.encodeInt32(typeAudio, forKey: "t") encoder.encodeInt32(isVoice ? 1 : 0, forKey: "iv") @@ -592,7 +597,7 @@ public final class TelegramMediaFile: Media, Equatable, Codable { public var isInstantVideo: Bool { for attribute in self.attributes { - if case .Video(_, _, let flags, _) = attribute { + if case .Video(_, _, let flags, _, _) = attribute { return flags.contains(.instantRoundVideo) } } @@ -601,7 +606,7 @@ public final class TelegramMediaFile: Media, Equatable, Codable { public var preloadSize: Int32? { for attribute in self.attributes { - if case .Video(_, _, _, let preloadSize) = attribute { + if case .Video(_, _, _, let preloadSize, _) = attribute { return preloadSize } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift index de9bc76cf8..019afadf9e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/OutgoingMessageWithChatContextResult.swift @@ -118,7 +118,7 @@ func _internal_outgoingMessageWithChatContextResult(to peerId: PeerId, threadId: if let dimensions = externalReference.content?.dimensions { fileAttributes.append(.ImageSize(size: dimensions)) if externalReference.type == "gif" { - fileAttributes.append(.Video(duration: externalReference.content?.duration ?? 0.0, size: dimensions, flags: [], preloadSize: nil)) + fileAttributes.append(.Video(duration: externalReference.content?.duration ?? 0.0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil)) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index 11663bbd71..a9305946cb 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -5,12 +5,12 @@ import TelegramApi public enum EngineStoryInputMedia { case image(dimensions: PixelDimensions, data: Data, stickers: [TelegramMediaFile]) - case video(dimensions: PixelDimensions, duration: Double, resource: TelegramMediaResource, firstFrameFile: TempBoxFile?, stickers: [TelegramMediaFile]) + case video(dimensions: PixelDimensions, duration: Double, resource: TelegramMediaResource, firstFrameFile: TempBoxFile?, stickers: [TelegramMediaFile], coverTime: Double?) case existing(media: Media) var embeddedStickers: [TelegramMediaFile] { switch self { - case let .image(_, _, stickers), let .video(_, _, _, _, stickers): + case let .image(_, _, stickers), let .video(_, _, _, _, stickers, _): return stickers case .existing: return [] @@ -849,7 +849,7 @@ private func prepareUploadStoryContent(account: Account, media: EngineStoryInput flags: [] ) return imageMedia - case let .video(dimensions, duration, resource, firstFrameFile, _): + case let .video(dimensions, duration, resource, firstFrameFile, _, coverTime): var previewRepresentations: [TelegramMediaImageRepresentation] = [] if let firstFrameFile = firstFrameFile { account.postbox.mediaBox.storeCachedResourceRepresentation(resource.id.stringRepresentation, representationId: "first-frame", keepDuration: .general, tempFile: firstFrameFile) @@ -871,7 +871,7 @@ private func prepareUploadStoryContent(account: Account, media: EngineStoryInput mimeType: "video/mp4", size: nil, attributes: [ - TelegramMediaFileAttribute.Video(duration: duration, size: dimensions, flags: .supportsStreaming, preloadSize: nil) + TelegramMediaFileAttribute.Video(duration: duration, size: dimensions, flags: .supportsStreaming, preloadSize: nil, coverTime: coverTime) ] ) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift index ac6ed07c33..6ba7171906 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift @@ -144,7 +144,7 @@ public extension ImportSticker { fileAttributes.append(.FileName(fileName: "sticker.webm")) fileAttributes.append(.Animated) fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) - fileAttributes.append(.Video(duration: self.duration ?? 3.0, size: self.dimensions, flags: [], preloadSize: nil)) + fileAttributes.append(.Video(duration: self.duration ?? 3.0, size: self.dimensions, flags: [], preloadSize: nil, coverTime: nil)) } else if self.mimeType == "application/x-tgsticker" { fileAttributes.append(.FileName(fileName: "sticker.tgs")) fileAttributes.append(.Animated) diff --git a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift index 9cba0d586f..a548f9aac1 100644 --- a/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift +++ b/submodules/TelegramStringFormatting/Sources/MessageContentKind.swift @@ -330,7 +330,7 @@ public func mediaContentKind(_ media: EngineMedia, message: EngineMessage? = nil return .file(performer) } } - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if file.isAnimated { result = .animation } else { diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 2deee3a50a..1816f40ef8 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -235,7 +235,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } else { for attribute in file.attributes { switch attribute { - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if flags.contains(.instantRoundVideo) { type = .round } else { diff --git a/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/Sources/ChatContextResultPeekContent.swift b/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/Sources/ChatContextResultPeekContent.swift index d32cd4adf8..67891754b9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/Sources/ChatContextResultPeekContent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatContextResultPeekContent/Sources/ChatContextResultPeekContent.swift @@ -172,7 +172,7 @@ private final class ChatContextResultPeekNode: ASDisplayNode, PeekControllerCont imageDimensions = externalReference.content?.dimensions?.cgSize if let content = externalReference.content, externalReference.type == "gif", let thumbnailResource = imageResource , let dimensions = content.dimensions { - videoFileReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil)])) + videoFileReference = .standalone(media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil)])) imageResource = nil } case let .internalReference(internalReference): diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift index d0066f268f..feaacffdb1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift @@ -272,7 +272,7 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.mediaBackgroundNode.image = backgroundImage if let image = image, let video = image.videoRepresentations.last, let id = image.id?.id { - let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: image.representations, videoThumbnails: [], immediateThumbnailData: image.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [], preloadSize: nil)])) + let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: image.representations, videoThumbnails: [], immediateThumbnailData: image.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [], preloadSize: nil, coverTime: nil)])) let videoContent = NativeVideoContent(id: .profileVideo(id, "action"), userLocation: .peer(item.message.id.peerId), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) if videoContent.id != strongSelf.videoContent?.id { let mediaManager = item.context.sharedContext.mediaManager diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift index 208c51937c..6ae1859216 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveFileNode/Sources/ChatMessageInteractiveFileNode.swift @@ -650,7 +650,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { let messageTheme = arguments.incoming ? arguments.presentationData.theme.theme.chat.message.incoming : arguments.presentationData.theme.theme.chat.message.outgoing let isInstantVideo = arguments.file.isInstantVideo for attribute in arguments.file.attributes { - if case let .Video(videoDuration, _, flags, _) = attribute, flags.contains(.instantRoundVideo) { + if case let .Video(videoDuration, _, flags, _, _) = attribute, flags.contains(.instantRoundVideo) { isAudio = true isVoice = true @@ -1558,7 +1558,7 @@ public final class ChatMessageInteractiveFileNode: ASDisplayNode { var isVoice = false var audioDuration: Int32? for attribute in file.attributes { - if case let .Video(duration, _, flags, _) = attribute, flags.contains(.instantRoundVideo) { + if case let .Video(duration, _, flags, _, _) = attribute, flags.contains(.instantRoundVideo) { isAudio = true isVoice = true audioDuration = Int32(duration) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift index 4deb81d110..d54a68b500 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemImpl/Sources/ChatMessageItemImpl.swift @@ -27,7 +27,7 @@ private func mediaMergeableStyle(_ media: Media) -> ChatMessageMerge { switch attribute { case .Sticker: return .semanticallyMerged - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if flags.contains(.instantRoundVideo) { return .none } @@ -423,7 +423,7 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible viewClassName = ChatMessageStickerItemNode.self } break loop - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if flags.contains(.instantRoundVideo) { viewClassName = ChatMessageBubbleItemNode.self break loop diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift index a473d69ea6..9424d985ab 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift @@ -203,7 +203,7 @@ public final class ChatMessageAccessibilityData { text = item.presentationData.strings.VoiceOver_Chat_MusicTitle(title, performer).string text.append(item.presentationData.strings.VoiceOver_Chat_Duration(durationString).string) } - case let .Video(duration, _, flags, _): + case let .Video(duration, _, flags, _, _): isSpecialFile = true if isSelected == nil { hint = item.presentationData.strings.VoiceOver_Chat_PlayHint diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift index d3187ba59b..7f8236c6ee 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageProfilePhotoSuggestionContentNode/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift @@ -218,7 +218,7 @@ public class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleCont } if let photo = photo, let video = photo.videoRepresentations.last, let id = photo.id?.id { - let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [], preloadSize: nil)])) + let videoFileReference = FileMediaReference.message(message: MessageReference(item.message), media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.resource, previewRepresentations: photo.representations, videoThumbnails: [], immediateThumbnailData: photo.immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.dimensions, flags: [], preloadSize: nil, coverTime: nil)])) let videoContent = NativeVideoContent(id: .profileVideo(id, "action"), userLocation: .peer(item.message.id.peerId), fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, storeAfterDownload: nil) if videoContent.id != strongSelf.videoContent?.id { let mediaManager = item.context.sharedContext.mediaManager diff --git a/submodules/TelegramUI/Components/EntityKeyboardGifContent/Sources/GifContext.swift b/submodules/TelegramUI/Components/EntityKeyboardGifContent/Sources/GifContext.swift index 15c955e0b3..e133ecbacb 100644 --- a/submodules/TelegramUI/Components/EntityKeyboardGifContent/Sources/GifContext.swift +++ b/submodules/TelegramUI/Components/EntityKeyboardGifContent/Sources/GifContext.swift @@ -126,7 +126,7 @@ public func paneGifSearchForQuery(context: AccountContext, query: String, offset )) } } - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: uniqueId ?? 0), partialReference: nil, resource: resource, previewRepresentations: previews, videoThumbnails: videoThumbnails, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil)]) + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: uniqueId ?? 0), partialReference: nil, resource: resource, previewRepresentations: previews, videoThumbnails: videoThumbnails, immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil)]) references.append(MultiplexedVideoNodeFile(file: FileMediaReference.standalone(media: file), contextResult: (collection, result))) } case let .internalReference(internalReference): diff --git a/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift b/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift index 69dd016f21..0dc69ee662 100644 --- a/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift +++ b/submodules/TelegramUI/Components/LegacyInstantVideoController/Sources/LegacyInstantVideoController.swift @@ -231,7 +231,7 @@ public func legacyInstantVideoController(theme: PresentationTheme, forStory: Boo } } - let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: [.instantRoundVideo], preloadSize: nil)]) + let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: finalDuration, size: PixelDimensions(finalDimensions), flags: [.instantRoundVideo], preloadSize: nil, coverTime: nil)]) var message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []) let scheduleTime: Int32? = scheduleTimestamp > 0 ? scheduleTimestamp : nil diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift index 8cbf706e03..0f4bc63cf2 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditor.swift @@ -476,6 +476,7 @@ public final class MediaEditor { audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, + coverImageTimestamp: nil, qualityPreset: nil ) } @@ -1733,6 +1734,12 @@ public final class MediaEditor { } } + public func setCoverImageTimestamp(_ coverImageTimestamp: Double?) { + self.updateValues(mode: .skipRendering) { values in + return values.withUpdatedCoverImageTimestamp(coverImageTimestamp) + } + } + public func setDrawingAndEntities(data: Data?, image: UIImage?, entities: [CodableDrawingEntity]) { self.updateValues(mode: .skipRendering) { values in return values.withUpdatedDrawingAndEntities(drawing: image, entities: entities) diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift index deec4efc3d..828e7500d8 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/MediaEditorValues.swift @@ -324,6 +324,9 @@ public final class MediaEditorValues: Codable, Equatable { if lhs.audioTrackSamples != rhs.audioTrackSamples { return false } + if lhs.coverImageTimestamp != rhs.coverImageTimestamp { + return false + } if lhs.nightTheme != rhs.nightTheme { return false } @@ -394,6 +397,7 @@ public final class MediaEditorValues: Codable, Equatable { case audioTrackTrimRange case audioTrackOffset case audioTrackVolume + case coverImageTimestamp case qualityPreset } @@ -438,6 +442,8 @@ public final class MediaEditorValues: Codable, Equatable { public let audioTrackVolume: CGFloat? public let audioTrackSamples: MediaAudioTrackSamples? + public let coverImageTimestamp: Double? + public let qualityPreset: MediaQualityPreset? var isStory: Bool { @@ -486,6 +492,7 @@ public final class MediaEditorValues: Codable, Equatable { audioTrackOffset: Double?, audioTrackVolume: CGFloat?, audioTrackSamples: MediaAudioTrackSamples?, + coverImageTimestamp: Double?, qualityPreset: MediaQualityPreset? ) { self.peerId = peerId @@ -521,6 +528,7 @@ public final class MediaEditorValues: Codable, Equatable { self.audioTrackOffset = audioTrackOffset self.audioTrackVolume = audioTrackVolume self.audioTrackSamples = audioTrackSamples + self.coverImageTimestamp = coverImageTimestamp self.qualityPreset = qualityPreset } @@ -591,6 +599,8 @@ public final class MediaEditorValues: Codable, Equatable { self.audioTrackSamples = nil + self.coverImageTimestamp = try container.decodeIfPresent(Double.self, forKey: .coverImageTimestamp) + self.qualityPreset = (try container.decodeIfPresent(Int32.self, forKey: .qualityPreset)).flatMap { MediaQualityPreset(rawValue: $0) } } @@ -652,109 +662,115 @@ public final class MediaEditorValues: Codable, Equatable { try container.encodeIfPresent(self.audioTrackOffset, forKey: .audioTrackOffset) try container.encodeIfPresent(self.audioTrackVolume, forKey: .audioTrackVolume) + try container.encodeIfPresent(self.coverImageTimestamp, forKey: .coverImageTimestamp) + try container.encodeIfPresent(self.qualityPreset?.rawValue, forKey: .qualityPreset) } public func makeCopy() -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedCrop(offset: CGPoint, scale: CGFloat, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: offset, cropRect: self.cropRect, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: offset, cropRect: self.cropRect, cropScale: scale, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } public func withUpdatedCropRect(cropRect: CGRect, rotation: CGFloat, mirroring: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: .zero, cropRect: cropRect, cropScale: 1.0, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: .zero, cropRect: cropRect, cropScale: 1.0, cropRotation: rotation, cropMirroring: mirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedGradientColors(gradientColors: [UIColor]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedVideoIsMuted(_ videoIsMuted: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedVideoIsFullHd(_ videoIsFullHd: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedVideoIsMirrored(_ videoIsMirrored: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedVideoVolume(_ videoVolume: CGFloat?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedAdditionalVideo(path: String?, isDual: Bool, positionChanges: [VideoPositionChange]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: path, additionalVideoIsDual: isDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: path, additionalVideoIsDual: isDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: positionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedAdditionalVideo(position: CGPoint, scale: CGFloat, rotation: CGFloat) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: position, additionalVideoScale: scale, additionalVideoRotation: rotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedAdditionalVideoTrimRange(_ additionalVideoTrimRange: Range?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedAdditionalVideoOffset(_ additionalVideoOffset: Double?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedAdditionalVideoVolume(_ additionalVideoVolume: CGFloat?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedVideoTrimRange(_ videoTrimRange: Range) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedDrawingAndEntities(drawing: UIImage?, entities: [CodableDrawingEntity]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } public func withUpdatedMaskDrawing(maskDrawing: UIImage?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedToolValues(_ toolValues: [EditorToolKey: Any]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrack(_ audioTrack: MediaAudioTrack?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: self.videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrackTrimRange(_ audioTrackTrimRange: Range?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrackOffset(_ audioTrackOffset: Double?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrackVolume(_ audioTrackVolume: CGFloat?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedAudioTrackSamples(_ audioTrackSamples: MediaAudioTrackSamples?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } func withUpdatedNightTheme(_ nightTheme: Bool) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) } public func withUpdatedEntities(_ entities: [CodableDrawingEntity]) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: self.qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: self.qualityPreset) + } + + public func withUpdatedCoverImageTimestamp(_ coverImageTimestamp: Double?) -> MediaEditorValues { + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: coverImageTimestamp, qualityPreset: self.qualityPreset) } public func withUpdatedQualityPreset(_ qualityPreset: MediaQualityPreset?) -> MediaEditorValues { - return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, qualityPreset: qualityPreset) + return MediaEditorValues(peerId: self.peerId, originalDimensions: self.originalDimensions, cropOffset: self.cropOffset, cropRect: self.cropRect, cropScale: self.cropScale, cropRotation: self.cropRotation, cropMirroring: self.cropMirroring, cropOrientation: self.cropOrientation, gradientColors: self.gradientColors, videoTrimRange: videoTrimRange, videoIsMuted: self.videoIsMuted, videoIsFullHd: self.videoIsFullHd, videoIsMirrored: self.videoIsMirrored, videoVolume: self.videoVolume, additionalVideoPath: self.additionalVideoPath, additionalVideoIsDual: self.additionalVideoIsDual, additionalVideoPosition: self.additionalVideoPosition, additionalVideoScale: self.additionalVideoScale, additionalVideoRotation: self.additionalVideoRotation, additionalVideoPositionChanges: self.additionalVideoPositionChanges, additionalVideoTrimRange: self.additionalVideoTrimRange, additionalVideoOffset: self.additionalVideoOffset, additionalVideoVolume: self.additionalVideoVolume, nightTheme: self.nightTheme, drawing: self.drawing, maskDrawing: self.maskDrawing, entities: self.entities, toolValues: self.toolValues, audioTrack: self.audioTrack, audioTrackTrimRange: self.audioTrackTrimRange, audioTrackOffset: self.audioTrackOffset, audioTrackVolume: self.audioTrackVolume, audioTrackSamples: self.audioTrackSamples, coverImageTimestamp: self.coverImageTimestamp, qualityPreset: qualityPreset) } public var resultDimensions: PixelDimensions { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/EditStories.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/EditStories.swift index c45a76b7c1..fb7682e02f 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/EditStories.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/EditStories.swift @@ -216,7 +216,7 @@ public extension MediaEditorScreen { } } - update((context.engine.messages.editStory(peerId: peer.id, id: storyItem.id, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil) + update((context.engine.messages.editStory(peerId: peer.id, id: storyItem.id, media: .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers, coverTime: nil), mediaAreas: result.mediaAreas, text: updatedText, entities: updatedEntities, privacy: nil) |> deliverOnMainQueue).startStrict(next: { result in switch result { case let .progress(progress): diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCoverScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCoverScreen.swift new file mode 100644 index 0000000000..9eca8afc3e --- /dev/null +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaCoverScreen.swift @@ -0,0 +1,589 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import ComponentFlow +import SwiftSignalKit +import ViewControllerComponent +import ComponentDisplayAdapters +import TelegramPresentationData +import AccountContext +import TelegramCore +import MultilineTextComponent +import MediaEditor +import MediaScrubberComponent +import ButtonComponent + +private final class MediaCoverScreenComponent: Component { + typealias EnvironmentType = ViewControllerComponentContainer.Environment + + let context: AccountContext + let mediaEditor: MediaEditor + + init( + context: AccountContext, + mediaEditor: MediaEditor + ) { + self.context = context + self.mediaEditor = mediaEditor + } + + static func ==(lhs: MediaCoverScreenComponent, rhs: MediaCoverScreenComponent) -> Bool { + if lhs.context !== rhs.context { + return false + } + return true + } + + final class State: ComponentState { + enum ImageKey: Hashable { + case done + } + private var cachedImages: [ImageKey: UIImage] = [:] + func image(_ key: ImageKey) -> UIImage { + if let image = self.cachedImages[key] { + return image + } else { + var image: UIImage + switch key { + case .done: + image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Done"), color: .white)! + } + cachedImages[key] = image + return image + } + } + + var playerStateDisposable: Disposable? + var playerState: MediaEditorPlayerState? + + init(mediaEditor: MediaEditor) { + super.init() + + self.playerStateDisposable = (mediaEditor.playerState(framesCount: 16) + |> deliverOnMainQueue).start(next: { [weak self] playerState in + if let self { + if self.playerState != playerState { + self.playerState = playerState + self.updated() + } + } + }) + } + + deinit { + self.playerStateDisposable?.dispose() + } + } + + func makeState() -> State { + return State(mediaEditor: self.mediaEditor) + } + + public final class View: UIView { + private let buttonsContainerView = UIView() + private let buttonsBackgroundView = UIImageView() + private let previewContainerView = UIView() + private let cancelButton = ComponentView() + private let label = ComponentView() + private let doneButton = ComponentView() + private let scrubber = ComponentView() + + private let fadeView = UIView() + + private var component: MediaCoverScreenComponent? + private weak var state: State? + private var environment: ViewControllerComponentContainer.Environment? + + override init(frame: CGRect) { + self.buttonsContainerView.clipsToBounds = true + + self.fadeView.alpha = 0.0 + self.fadeView.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.7) + + self.buttonsBackgroundView.image = generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in + context.setFillColor(UIColor.black.cgColor) + context.fill(CGRect(origin: .zero, size: size)) + + context.setBlendMode(.clear) + context.setFillColor(UIColor.clear.cgColor) + context.addPath(CGPath(roundedRect: CGRect(x: 0.0, y: -11.0, width: size.width, height: 22.0), cornerWidth: 11.0, cornerHeight: 11.0, transform: nil)) + context.fillPath() + })?.stretchableImage(withLeftCapWidth: 11, topCapHeight: 11) + + super.init(frame: frame) + + self.backgroundColor = .clear + + self.addSubview(self.buttonsContainerView) + self.buttonsContainerView.addSubview(self.buttonsBackgroundView) + + self.addSubview(self.fadeView) + self.addSubview(self.previewContainerView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func animateInFromEditor() { + self.buttonsBackgroundView.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: .zero, duration: 0.2, additive: true) + + self.label.view?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + + if let view = self.doneButton.view { + view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2) + } + } + + private var animatingOut = false + func animateOutToEditor(completion: @escaping () -> Void) { + self.animatingOut = true + + self.fadeView.layer.animateAlpha(from: self.fadeView.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.buttonsBackgroundView.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 44.0), duration: 0.2, removeOnCompletion: false, additive: true) + + self.label.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + + if let view = self.scrubber.view { + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in + completion() + }) + view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2) + } + + if let view = self.cancelButton.view { + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2) + } + + if let view = self.doneButton.view { + view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + view.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2) + } + + self.state?.updated() + } + +// override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { +// let result = super.hitTest(point, with: event) +// if let controller = self.environment?.controller() as? MediaCoverScreen, [.erase, .restore].contains(controller.mode), result == self.previewContainerView { +// return nil +// } +// return result +// } + + func update(component: MediaCoverScreenComponent, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { + let environment = environment[ViewControllerComponentContainer.Environment.self].value + self.environment = environment + + guard let controller = environment.controller() as? MediaCoverScreen else { + return .zero + } + +// let isFirstTime = self.component == nil + self.component = component + self.state = state + + let isTablet: Bool + if case .regular = environment.metrics.widthClass { + isTablet = true + } else { + isTablet = false + } + + let buttonSideInset: CGFloat = 16.0 + var controlsBottomInset: CGFloat = 0.0 + let previewSize: CGSize + var topInset: CGFloat = environment.statusBarHeight + 5.0 + if isTablet { + let previewHeight = availableSize.height - topInset - 75.0 + previewSize = CGSize(width: floorToScreenPixels(previewHeight / 1.77778), height: previewHeight) + } else { + previewSize = CGSize(width: availableSize.width, height: floorToScreenPixels(availableSize.width * 1.77778)) + if availableSize.height < previewSize.height + 30.0 { + topInset = 0.0 + controlsBottomInset = -75.0 + } + } + + let previewContainerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - previewSize.width) / 2.0), y: environment.safeInsets.top), size: CGSize(width: previewSize.width, height: availableSize.height - environment.safeInsets.top - environment.safeInsets.bottom + controlsBottomInset)) + let buttonsContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - environment.safeInsets.bottom + controlsBottomInset - 31.0), size: CGSize(width: availableSize.width, height: environment.safeInsets.bottom - controlsBottomInset)) + + let cancelButtonSize = self.cancelButton.update( + transition: transition, + component: AnyComponent(Button( + content: AnyComponent( + MultilineTextComponent(text: .plain(NSAttributedString(string: "Cancel", font: Font.regular(17.0), textColor: .white))) + ), + action: { [weak controller] in + controller?.requestDismiss(animated: true) + } + )), + environment: {}, + containerSize: CGSize(width: 120.0, height: 44.0) + ) + let cancelButtonFrame = CGRect( + origin: CGPoint(x: 16.0, y: 80.0), + size: cancelButtonSize + ) + if let cancelButtonView = self.cancelButton.view { + if cancelButtonView.superview == nil { + self.addSubview(cancelButtonView) + setupButtonShadow(cancelButtonView) + } + transition.setFrame(view: cancelButtonView, frame: cancelButtonFrame) + } + + let doneButtonSize = self.doneButton.update( + transition: transition, + component: AnyComponent( + ButtonComponent( + background: ButtonComponent.Background( + color: environment.theme.list.itemCheckColors.fillColor, + foreground: environment.theme.list.itemCheckColors.foregroundColor, + pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9) + ), + content: AnyComponentWithIdentity( + id: AnyHashable(0), + component: AnyComponent(ButtonTextContentComponent( + text: "Save Cover", + badge: 0, + textColor: environment.theme.list.itemCheckColors.foregroundColor, + badgeBackground: .clear, + badgeForeground: .clear + )) + ), + isEnabled: true, + displaysProgress: false, + action: { [weak controller, weak self] in + if let playerState = self?.state?.playerState, let mediaEditor = self?.component?.mediaEditor, let image = mediaEditor.resultImage { + mediaEditor.setCoverImageTimestamp(playerState.position) + controller?.completed(playerState.position, image) + } + controller?.requestDismiss(animated: true) + } + ) + ), + environment: {}, + containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 50.0) + ) + let doneButtonFrame = CGRect( + origin: CGPoint(x: floor((availableSize.width - doneButtonSize.width) / 2.0), y: availableSize.height - 99.0), + size: doneButtonSize + ) + if let doneButtonView = self.doneButton.view { + if doneButtonView.superview == nil { + self.addSubview(doneButtonView) + } + transition.setFrame(view: doneButtonView, frame: doneButtonFrame) + } + + let labelSize = self.label.update( + transition: transition, + component: AnyComponent(Text(text: "Story Cover", font: Font.semibold(17.0), color: UIColor(rgb: 0xffffff))), + environment: {}, + containerSize: CGSize(width: availableSize.width - 88.0, height: 44.0) + ) + let labelFrame = CGRect( + origin: CGPoint(x: floorToScreenPixels((availableSize.width - labelSize.width) / 2.0), y: 80.0), + size: labelSize + ) + if let labelView = self.label.view { + if labelView.superview == nil { + self.addSubview(labelView) + setupButtonShadow(labelView) + } + if labelView.bounds.width > 0.0 && labelFrame.width != labelView.bounds.width { + if let snapshotView = labelView.snapshotView(afterScreenUpdates: false) { + snapshotView.center = labelView.center + self.buttonsContainerView.addSubview(snapshotView) + + labelView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in + snapshotView.removeFromSuperview() + }) + } + } + labelView.bounds = CGRect(origin: .zero, size: labelFrame.size) + transition.setPosition(view: labelView, position: labelFrame.center) + } + + transition.setFrame(view: self.buttonsContainerView, frame: buttonsContainerFrame) + transition.setFrame(view: self.buttonsBackgroundView, frame: CGRect(origin: .zero, size: buttonsContainerFrame.size)) + + transition.setFrame(view: self.previewContainerView, frame: previewContainerFrame) + + if let playerState = state.playerState { + let visibleTracks = playerState.tracks.filter { $0.id == 0 }.map { MediaScrubberComponent.Track($0) } + + let mediaEditor = component.mediaEditor + let scrubberInset: CGFloat = buttonSideInset + let scrubberSize = self.scrubber.update( + transition: transition, + component: AnyComponent(MediaScrubberComponent( + context: component.context, + style: .cover, + theme: environment.theme, + generationTimestamp: playerState.generationTimestamp, + position: playerState.position, + minDuration: 1.0, + maxDuration: storyMaxVideoDuration, + isPlaying: playerState.isPlaying, + tracks: visibleTracks, + portalView: controller.portalView, + positionUpdated: { [weak mediaEditor] position, apply in + if let mediaEditor { + mediaEditor.seek(position, andPlay: false) + } + }, + trackTrimUpdated: { _, _, _, _, _ in + }, + trackOffsetUpdated: { _, _, _ in + }, + trackLongPressed: { _, _ in + } + )), + environment: {}, + containerSize: CGSize(width: previewSize.width - scrubberInset * 2.0, height: availableSize.height) + ) + + let scrubberFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - scrubberSize.width) / 2.0), y: availableSize.height - environment.safeInsets.bottom - scrubberSize.height + controlsBottomInset + 3.0 - 40.0), size: scrubberSize) + if let scrubberView = self.scrubber.view { + var animateIn = false + if scrubberView.superview == nil { + animateIn = true + self.addSubview(scrubberView) + } + if animateIn { + scrubberView.frame = scrubberFrame + } else { + transition.setFrame(view: scrubberView, frame: scrubberFrame) + } + if animateIn { + scrubberView.layer.animatePosition(from: CGPoint(x: 0.0, y: 44.0), to: .zero, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + scrubberView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + scrubberView.layer.animateScale(from: 0.6, to: 1.0, duration: 0.2) + } + } + } + + return availableSize + } + } + + func makeView() -> View { + return View() + } + + public func update(view: View, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} + +final class MediaCoverScreen: ViewController { + fileprivate final class Node: ViewControllerTracingNode, ASGestureRecognizerDelegate { + private weak var controller: MediaCoverScreen? + private let context: AccountContext + + fileprivate let componentHost: ComponentView + + private var presentationData: PresentationData + private var validLayout: ContainerViewLayout? + + init(controller: MediaCoverScreen) { + self.controller = controller + self.context = controller.context + + self.presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + + self.componentHost = ComponentView() + + super.init() + + self.backgroundColor = .clear + } + + override func didLoad() { + super.didLoad() + + self.view.disablesInteractiveModalDismiss = true + self.view.disablesInteractiveKeyboardGestureRecognizer = true + } + + @objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } + + func animateInFromEditor() { + if let view = self.componentHost.view as? MediaCoverScreenComponent.View { + view.animateInFromEditor() + } + } + + func animateOutToEditor(completion: @escaping () -> Void) { + if let mediaEditor = self.controller?.mediaEditor { + mediaEditor.play() + } + if let view = self.componentHost.view as? MediaCoverScreenComponent.View { + view.animateOutToEditor(completion: completion) + } + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + if result === self.view { + return nil + } + return result + } + + func requestLayout(transition: ComponentTransition) { + if let layout = self.validLayout { + self.containerLayoutUpdated(layout: layout, forceUpdate: true, transition: transition) + } + } + + func containerLayoutUpdated(layout: ContainerViewLayout, forceUpdate: Bool = false, animateOut: Bool = false, transition: ComponentTransition) { + guard let controller = self.controller else { + return + } + let isFirstTime = self.validLayout == nil + self.validLayout = layout + + let isTablet = layout.metrics.isTablet + + let previewSize: CGSize + let topInset: CGFloat = (layout.statusBarHeight ?? 0.0) + 5.0 + if isTablet { + let previewHeight = layout.size.height - topInset - 75.0 + previewSize = CGSize(width: floorToScreenPixels(previewHeight / 1.77778), height: previewHeight) + } else { + previewSize = CGSize(width: layout.size.width, height: floorToScreenPixels(layout.size.width * 1.77778)) + } + let bottomInset = layout.size.height - previewSize.height - topInset + + let environment = ViewControllerComponentContainer.Environment( + statusBarHeight: layout.statusBarHeight ?? 0.0, + navigationHeight: 0.0, + safeInsets: UIEdgeInsets( + top: topInset, + left: layout.safeInsets.left, + bottom: bottomInset, + right: layout.safeInsets.right + ), + additionalInsets: layout.additionalInsets, + inputHeight: layout.inputHeight ?? 0.0, + metrics: layout.metrics, + deviceMetrics: layout.deviceMetrics, + orientation: nil, + isVisible: true, + theme: self.presentationData.theme, + strings: self.presentationData.strings, + dateTimeFormat: self.presentationData.dateTimeFormat, + controller: { [weak self] in + return self?.controller + } + ) + + let componentSize = self.componentHost.update( + transition: transition, + component: AnyComponent( + MediaCoverScreenComponent( + context: self.context, + mediaEditor: controller.mediaEditor + ) + ), + environment: { + environment + }, + forceUpdate: forceUpdate || animateOut, + containerSize: layout.size + ) + if let componentView = self.componentHost.view { + if componentView.superview == nil { + self.view.insertSubview(componentView, at: 3) + componentView.clipsToBounds = true + } + let componentFrame = CGRect(origin: .zero, size: componentSize) + transition.setFrame(view: componentView, frame: CGRect(origin: componentFrame.origin, size: CGSize(width: componentFrame.width, height: componentFrame.height))) + } + + if isFirstTime { + self.animateInFromEditor() + } + } + } + + fileprivate var node: Node { + return self.displayNode as! Node + } + + fileprivate let context: AccountContext + fileprivate let mediaEditor: MediaEditor + fileprivate let previewView: MediaEditorPreviewView + fileprivate let portalView: PortalView + + var completed: (Double, UIImage) -> Void = { _, _ in } + var dismissed: () -> Void = {} + + private var initialValues: MediaEditorValues + + init( + context: AccountContext, + mediaEditor: MediaEditor, + previewView: MediaEditorPreviewView, + portalView: PortalView + ) { + self.context = context + self.mediaEditor = mediaEditor + self.previewView = previewView + self.portalView = portalView + self.initialValues = mediaEditor.values.makeCopy() + + super.init(navigationBarPresentationData: nil) + self.navigationPresentation = .flatModal + + self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) + + self.statusBar.statusBarStyle = .White + + if let coverImageTimestamp = mediaEditor.values.coverImageTimestamp { + mediaEditor.seek(coverImageTimestamp, andPlay: false) + } else { + mediaEditor.seek(0.0, andPlay: false) + } + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadDisplayNode() { + self.displayNode = Node(controller: self) + + super.displayNodeDidLoad() + } + + func requestDismiss(animated: Bool) { + self.dismissed() + + self.node.animateOutToEditor(completion: { + self.dismiss() + }) + } + + override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + (self.displayNode as! Node).containerLayoutUpdated(layout: layout, transition: ComponentTransition(transition)) + } +} + +private func setupButtonShadow(_ view: UIView, radius: CGFloat = 2.0) { + view.layer.shadowOffset = CGSize(width: 0.0, height: 0.0) + view.layer.shadowRadius = radius + view.layer.shadowColor = UIColor.black.cgColor + view.layer.shadowOpacity = 0.35 +} diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index b4f1b8c945..1ece306bb3 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -76,6 +76,7 @@ final class MediaEditorScreenComponent: Component { case cutout case cutoutErase case cutoutRestore + case cover } let context: AccountContext @@ -827,7 +828,6 @@ final class MediaEditorScreenComponent: Component { doneButtonTitle = nil doneButtonIcon = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/Apply"), color: .white)! case .botPreview: - //TODO:localize doneButtonTitle = environment.strings.Story_Editor_Add doneButtonIcon = nil } @@ -841,31 +841,7 @@ final class MediaEditorScreenComponent: Component { title: doneButtonTitle)), effectAlignment: .center, action: { [weak controller] in - guard let controller else { - return - } - switch controller.mode { - case .storyEditor: - guard !controller.node.recording.isActive else { - return - } - guard controller.checkCaptionLimit() else { - return - } - if controller.isEditingStory { - controller.requestStoryCompletion(animated: true) - } else { - if controller.checkIfCompletionIsAllowed() { - controller.openPrivacySettings(completion: { [weak controller] in - controller?.requestStoryCompletion(animated: true) - }) - } - } - case .stickerEditor: - controller.requestStickerCompletion(animated: true) - case .botPreview: - controller.requestStoryCompletion(animated: true) - } + controller?.node.requestCompletion() } )), environment: {}, @@ -2555,6 +2531,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate fileprivate var drawingScreen: DrawingScreen? fileprivate var stickerScreen: StickerPickerScreen? fileprivate weak var cutoutScreen: MediaCutoutScreen? + fileprivate weak var coverScreen: MediaCoverScreen? private var defaultToEmoji = false private var previousDrawingData: Data? @@ -4614,6 +4591,75 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate ) } + func requestCompletion() { + guard let controller = self.controller else { + return + } + switch controller.mode { + case .storyEditor: + guard !controller.node.recording.isActive else { + return + } + guard controller.checkCaptionLimit() else { + return + } + if controller.isEditingStory { + controller.requestStoryCompletion(animated: true) + } else { + if controller.checkIfCompletionIsAllowed() { + controller.openPrivacySettings(completion: { [weak controller] in + controller?.requestStoryCompletion(animated: true) + }) + } + } + case .stickerEditor: + controller.requestStickerCompletion(animated: true) + case .botPreview: + controller.requestStoryCompletion(animated: true) + } + } + + func openCoverSelection() { + guard let mediaEditor = self.mediaEditor else { + return + } + + guard let portalView = PortalView(matchPosition: false) else { + return + } + portalView.view.layer.rasterizationScale = UIScreenScale + self.previewContentContainerView.addPortal(view: portalView) + + let scale = 48.0 / self.previewContentContainerView.frame.height + portalView.view.transform = CGAffineTransformMakeScale(scale, scale) + + if self.entitiesView.hasSelection { + self.entitiesView.selectEntity(nil) + } + let coverController = MediaCoverScreen( + context: self.context, + mediaEditor: mediaEditor, + previewView: self.previewView, + portalView: portalView + ) + coverController.dismissed = { [weak self] in + if let self { + self.animateInFromTool() + Queue.mainQueue().after(0.1) { + self.requestCompletion() + } + } + } + coverController.completed = { [weak self] position, image in + if let self { + self.controller?.currentCoverImage = image + } + } + self.controller?.present(coverController, in: .window(.root)) + self.coverScreen = coverController + self.animateOutToTool(tool: .cover) + } + func updateModalTransitionFactor(_ value: CGFloat, transition: ContainedViewLayoutTransition) { guard let layout = self.validLayout, case .compact = layout.metrics.widthClass else { return @@ -5077,6 +5123,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } self.controller?.present(controller, in: .window(.root)) self.animateOutToTool(tool: .tools) + case .cover: + self.openCoverSelection() } } }, @@ -5652,8 +5700,12 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate return self.isEditingStory || self.forwardSource != nil } + private var currentCoverImage: UIImage? func openPrivacySettings(_ privacy: MediaEditorResultPrivacy? = nil, completion: @escaping () -> Void = {}) { - self.node.mediaEditor?.maybePauseVideo() + guard let mediaEditor = self.node.mediaEditor else { + return + } + mediaEditor.maybePauseVideo() self.hapticFeedback.impact(.light) @@ -5662,6 +5714,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate let text = self.getCaption().string let mentions = generateTextEntities(text, enabledTypes: [.mention], currentEntities: []).map { (text as NSString).substring(with: NSRange(location: $0.range.lowerBound + 1, length: $0.range.upperBound - $0.range.lowerBound - 1)) } + let coverImage = self.currentCoverImage ?? mediaEditor.resultImage + let stateContext = ShareWithPeersScreen.StateContext( context: self.context, subject: .stories(editing: false), @@ -5679,6 +5733,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate let initialPrivacy = privacy.privacy let timeout = privacy.timeout + var editCoverImpl: (() -> Void)? + let controller = ShareWithPeersScreen( context: self.context, initialPrivacy: initialPrivacy, @@ -5687,6 +5743,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate pin: privacy.pin, timeout: privacy.timeout, mentions: mentions, + coverImage: coverImage, stateContext: stateContext, completion: { [weak self] sendAsPeerId, privacy, allowScreenshots, pin, _, completed in guard let self else { @@ -5736,6 +5793,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate pin: pin ), completion: completion) }) + }, + editCover: { + editCoverImpl?() } ) controller.customModalStyleOverlayTransitionFactorUpdated = { [weak self, weak controller] transition in @@ -5748,6 +5808,17 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.node.mediaEditor?.play() } self.push(controller) + + editCoverImpl = { [weak self, weak controller] in + if let self { + Queue.mainQueue().after(0.25, { + self.node.openCoverSelection() + }) + } + if let controller { + controller.dismiss() + } + } }) } @@ -8074,7 +8145,7 @@ private func stickerFile(resource: TelegramMediaResource, thumbnailResource: Tel fileAttributes.append(.FileName(fileName: isVideo ? "sticker.webm" : "sticker.webp")) fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) if isVideo { - fileAttributes.append(.Video(duration: duration ?? 3.0, size: dimensions, flags: [], preloadSize: nil)) + fileAttributes.append(.Video(duration: duration ?? 3.0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil)) } else { fileAttributes.append(.ImageSize(size: dimensions)) } diff --git a/submodules/TelegramUI/Components/MediaScrubberComponent/Sources/MediaScrubberComponent.swift b/submodules/TelegramUI/Components/MediaScrubberComponent/Sources/MediaScrubberComponent.swift index c08a62d34e..e6a063aa26 100644 --- a/submodules/TelegramUI/Components/MediaScrubberComponent/Sources/MediaScrubberComponent.swift +++ b/submodules/TelegramUI/Components/MediaScrubberComponent/Sources/MediaScrubberComponent.swift @@ -70,6 +70,7 @@ public final class MediaScrubberComponent: Component { public enum Style { case editor case videoMessage + case cover } let context: AccountContext @@ -84,6 +85,7 @@ public final class MediaScrubberComponent: Component { let isPlaying: Bool let tracks: [Track] + let portalView: PortalView? let positionUpdated: (Double, Bool) -> Void let trackTrimUpdated: (Int32, Double, Double, Bool, Bool) -> Void @@ -100,6 +102,7 @@ public final class MediaScrubberComponent: Component { maxDuration: Double, isPlaying: Bool, tracks: [Track], + portalView: PortalView? = nil, positionUpdated: @escaping (Double, Bool) -> Void, trackTrimUpdated: @escaping (Int32, Double, Double, Bool, Bool) -> Void, trackOffsetUpdated: @escaping (Int32, Double, Bool) -> Void, @@ -114,6 +117,7 @@ public final class MediaScrubberComponent: Component { self.maxDuration = maxDuration self.isPlaying = isPlaying self.tracks = tracks + self.portalView = portalView self.positionUpdated = positionUpdated self.trackTrimUpdated = trackTrimUpdated self.trackOffsetUpdated = trackOffsetUpdated @@ -152,6 +156,7 @@ public final class MediaScrubberComponent: Component { private var trackViews: [Int32: TrackView] = [:] private let trimView: TrimView private let ghostTrimView: TrimView + private let cursorContentView: UIView private let cursorView: HandleView private var cursorDisplayLink: SharedDisplayLinkDriver.Link? @@ -169,6 +174,7 @@ public final class MediaScrubberComponent: Component { self.trimView = TrimView(frame: .zero) self.ghostTrimView = TrimView(frame: .zero) self.ghostTrimView.isHollow = true + self.cursorContentView = UIView() self.cursorView = HandleView() super.init(frame: frame) @@ -178,6 +184,10 @@ public final class MediaScrubberComponent: Component { self.disablesInteractiveModalDismiss = true self.disablesInteractiveKeyboardGestureRecognizer = true + self.cursorContentView.isUserInteractionEnabled = false + self.cursorContentView.clipsToBounds = true + self.cursorContentView.layer.cornerRadius = 10.0 + let positionImage = generateImage(CGSize(width: handleWidth, height: 50.0), rotatedContext: { size, context in context.clear(CGRect(origin: .zero, size: size)) context.setFillColor(UIColor.white.cgColor) @@ -187,13 +197,13 @@ public final class MediaScrubberComponent: Component { context.addPath(path.cgPath) context.fillPath() })?.stretchableImage(withLeftCapWidth: Int(handleWidth / 2.0), topCapHeight: 25) - self.cursorView.image = positionImage self.cursorView.isUserInteractionEnabled = true self.cursorView.hitTestSlop = UIEdgeInsets(top: -8.0, left: -9.0, bottom: -8.0, right: -9.0) self.addSubview(self.ghostTrimView) self.addSubview(self.trimView) + self.addSubview(self.cursorContentView) self.addSubview(self.cursorView) self.cursorView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handleCursorPan(_:)))) @@ -328,10 +338,23 @@ public final class MediaScrubberComponent: Component { } private func cursorFrame(size: CGSize, height: CGFloat, position: Double, duration : Double) -> CGRect { + var cursorWidth = handleWidth + var cursorMargin = handleWidth + var height = height + var isCover = false + var y: CGFloat = -5.0 - UIScreenPixel + if let component = self.component, case .cover = component.style { + cursorWidth = 30.0 + 12.0 + cursorMargin = 0.0 + height = 50.0 + isCover = true + y += 1.0 + } + let cursorPadding: CGFloat = 8.0 let cursorPositionFraction = duration > 0.0 ? position / duration : 0.0 - let cursorPosition = floorToScreenPixels(handleWidth - 1.0 + (size.width - handleWidth * 2.0 + 2.0) * cursorPositionFraction) - var cursorFrame = CGRect(origin: CGPoint(x: cursorPosition - handleWidth / 2.0, y: -5.0 - UIScreenPixel), size: CGSize(width: handleWidth, height: height)) + let cursorPosition = floorToScreenPixels(cursorMargin - 1.0 + (size.width - handleWidth * 2.0 + 2.0) * cursorPositionFraction) + var cursorFrame = CGRect(origin: CGPoint(x: cursorPosition - cursorWidth / 2.0, y: y), size: CGSize(width: cursorWidth, height: height)) var leftEdge = self.ghostTrimView.leftHandleView.frame.maxX var rightEdge = self.ghostTrimView.rightHandleView.frame.minX @@ -339,9 +362,13 @@ public final class MediaScrubberComponent: Component { leftEdge = self.trimView.leftHandleView.frame.maxX rightEdge = self.trimView.rightHandleView.frame.minX } + if isCover { + leftEdge = 0.0 + rightEdge = size.width + } cursorFrame.origin.x = max(leftEdge - cursorPadding, cursorFrame.origin.x) - cursorFrame.origin.x = min(rightEdge - handleWidth + cursorPadding, cursorFrame.origin.x) + cursorFrame.origin.x = min(rightEdge - cursorWidth + cursorPadding, cursorFrame.origin.x) return cursorFrame } @@ -377,6 +404,7 @@ public final class MediaScrubberComponent: Component { updatedPosition = max(self.startPosition, min(self.endPosition, position + advance)) } self.cursorView.frame = cursorFrame(size: scrubberSize, height: self.effectiveCursorHeight, position: updatedPosition, duration: self.trimDuration) + self.cursorContentView.frame = self.cursorView.frame.insetBy(dx: 6.0, dy: 2.0).offsetBy(dx: -1.0 - UIScreenPixel, dy: 0.0) } public func update(component: MediaScrubberComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { @@ -384,11 +412,36 @@ public final class MediaScrubberComponent: Component { self.component = component self.state = state + if let portalView = component.portalView, portalView.view.superview == nil { + portalView.view.frame = CGRect(x: 0.0, y: 0.0, width: 30.0, height: 48.0) + portalView.view.clipsToBounds = true + self.cursorContentView.addSubview(portalView.view) + } + switch component.style { case .editor: self.cursorView.isHidden = false case .videoMessage: self.cursorView.isHidden = true + case .cover: + self.cursorView.isHidden = false + self.trimView.isHidden = true + self.ghostTrimView.isHidden = true + + if isFirstTime { + let positionImage = generateImage(CGSize(width: 30.0 + 12.0, height: 50.0), rotatedContext: { size, context in + context.clear(CGRect(origin: .zero, size: size)) + context.setStrokeColor(UIColor.white.cgColor) + let lineWidth = 2.0 - UIScreenPixel + context.setLineWidth(lineWidth) + context.setShadow(offset: .zero, blur: 2.0, color: UIColor(rgb: 0x000000, alpha: 0.55).cgColor) + + let path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: 6.0 - lineWidth / 2.0, y: 2.0 - lineWidth / 2.0), size: CGSize(width: 30.0 - lineWidth, height: 48.0 - lineWidth)), cornerRadius: 9.0) + context.addPath(path.cgPath) + context.strokePath() + }) + self.cursorView.image = positionImage + } } var totalHeight: CGFloat = 0.0 @@ -520,7 +573,7 @@ public final class MediaScrubberComponent: Component { let fullTrackHeight: CGFloat switch component.style { - case .editor: + case .editor, .cover: fullTrackHeight = trackHeight case .videoMessage: fullTrackHeight = 33.0 @@ -610,7 +663,9 @@ public final class MediaScrubberComponent: Component { if let offset = self.mainAudioTrackOffset { cursorPosition -= offset } - transition.setFrame(view: self.cursorView, frame: cursorFrame(size: scrubberSize, height: self.effectiveCursorHeight, position: cursorPosition, duration: trimDuration)) + let cursorFrame = cursorFrame(size: scrubberSize, height: self.effectiveCursorHeight, position: cursorPosition, duration: trimDuration) + transition.setFrame(view: self.cursorView, frame: cursorFrame) + transition.setFrame(view: self.cursorContentView, frame: cursorFrame.insetBy(dx: 6.0, dy: 2.0).offsetBy(dx: -1.0 - UIScreenPixel, dy: 0.0)) } else { if let (_, _, end, ended) = self.cursorPositionAnimation { if ended, component.position >= self.startPosition && component.position < end - 1.0 { @@ -839,7 +894,7 @@ private class TrackView: UIView, UIScrollViewDelegate, UIGestureRecognizerDelega let fullTrackHeight: CGFloat let framesCornerRadius: CGFloat switch style { - case .editor: + case .editor, .cover: fullTrackHeight = trackHeight framesCornerRadius = 9.0 case .videoMessage: @@ -1362,7 +1417,7 @@ private class TrimView: UIView { let highlightColor: UIColor switch style { - case .editor: + case .editor, .cover: effectiveHandleWidth = handleWidth fullTrackHeight = trackHeight capsuleOffset = 5.0 - UIScreenPixel diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift index 1d61323f75..8922ed2a8d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoAvatarTransformContainerNode.swift @@ -327,7 +327,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { markupNode.update(markup: markup, size: CGSize(width: 320.0, height: 320.0)) markupNode.updateVisibility(true) } else if threadInfo == nil, let video = videoRepresentations.last, let peerReference = PeerReference(peer) { - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil)])) let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled, storeAfterDownload: nil) if videoContent.id != self.videoContent?.id { self.videoNode?.removeFromSupernode() diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift index 909f535749..a2e196d1e4 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoEditingAvatarNode.swift @@ -162,7 +162,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { markupNode.removeFromSupernode() } - let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil)])) + let videoFileReference = FileMediaReference.avatarList(peer: peerReference, media: TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: video.representation.resource, previewRepresentations: representations.map { $0.representation }, videoThumbnails: [], immediateThumbnailData: immediateThumbnailData, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: video.representation.dimensions, flags: [], preloadSize: nil, coverTime: nil)])) let videoContent = NativeVideoContent(id: .profileVideo(videoId, nil), userLocation: .other, fileReference: videoFileReference, streamVideo: isMediaStreamable(resource: video.representation.resource) ? .conservative : .none, loopVideo: true, enableSound: false, fetchAutomatically: true, onlyFullSizeThumbnail: false, useLargeThumbnail: true, autoFetchFullSizeThumbnail: true, startTimestamp: video.representation.startTimestamp, continuePlayingWithoutSoundOnLostAudioSession: false, placeholderColor: .clear, captureProtected: peer.isCopyProtectionEnabled, storeAfterDownload: nil) if videoContent.id != self.videoContent?.id { self.videoNode?.removeFromSupernode() diff --git a/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift b/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift index fd926aaa0b..357e5a00ad 100644 --- a/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift +++ b/submodules/TelegramUI/Components/Resources/FetchVideoMediaResource/Sources/FetchVideoMediaResource.swift @@ -910,6 +910,7 @@ private extension MediaEditorValues { audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, + coverImageTimestamp: nil, qualityPreset: qualityPreset ) } @@ -1053,6 +1054,7 @@ private extension MediaEditorValues { audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, + coverImageTimestamp: nil, qualityPreset: qualityPreset ) } diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CoverListItemComponent.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CoverListItemComponent.swift new file mode 100644 index 0000000000..364a948ddc --- /dev/null +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/CoverListItemComponent.swift @@ -0,0 +1,144 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import MultilineTextComponent +import TelegramPresentationData +import SwitchComponent + +final class CoverListItemComponent: Component { + let theme: PresentationTheme + let title: String + let image: UIImage? + let hasNext: Bool + let action: () -> Void + + init( + theme: PresentationTheme, + title: String, + image: UIImage?, + hasNext: Bool, + action: @escaping () -> Void + ) { + self.theme = theme + self.title = title + self.image = image + self.hasNext = hasNext + self.action = action + } + + static func ==(lhs: CoverListItemComponent, rhs: CoverListItemComponent) -> Bool { + if lhs.theme !== rhs.theme { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.image !== rhs.image { + return false + } + if lhs.hasNext != rhs.hasNext { + return false + } + return true + } + + final class View: UIView { + private let containerButton: HighlightTrackingButton + + private let title = ComponentView() + private let icon = ComponentView() + private let separatorLayer: SimpleLayer + + private var component: CoverListItemComponent? + private weak var state: EmptyComponentState? + + override init(frame: CGRect) { + self.separatorLayer = SimpleLayer() + + self.containerButton = HighlightTrackingButton() + + super.init(frame: frame) + + self.layer.addSublayer(self.separatorLayer) + self.addSubview(self.containerButton) + + self.containerButton.addTarget(self, action: #selector(self.pressed), for: .touchUpInside) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func pressed() { + guard let component = self.component else { + return + } + component.action() + } + + func update(component: CoverListItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let themeUpdated = self.component?.theme !== component.theme + + self.component = component + self.state = state + + let height: CGFloat = 44.0 + let verticalInset: CGFloat = 0.0 + let leftInset: CGFloat = 16.0 + let rightInset: CGFloat = 16.0 + + let iconSize = self.icon.update( + transition: .immediate, + component: AnyComponent(Image(image: component.image, contentMode: .scaleAspectFill)), + environment: {}, + containerSize: CGSize(width: 30.0, height: 30.0) + ) + + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.title, font: Font.regular(17.0), textColor: component.theme.list.itemPrimaryTextColor)) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width - leftInset - rightInset, height: 100.0) + ) + + let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((height - titleSize.height) / 2.0)), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + titleView.isUserInteractionEnabled = false + self.containerButton.addSubview(titleView) + } + titleView.frame = titleFrame + } + if let iconView = self.icon.view { + if iconView.superview == nil { + iconView.clipsToBounds = true + iconView.layer.cornerRadius = 5.0 + self.containerButton.addSubview(iconView) + } + transition.setFrame(view: iconView, frame: CGRect(origin: CGPoint(x: availableSize.width - rightInset - iconSize.width, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize)) + } + + if themeUpdated { + self.separatorLayer.backgroundColor = component.theme.list.itemPlainSeparatorColor.cgColor + } + transition.setFrame(layer: self.separatorLayer, frame: CGRect(origin: CGPoint(x: leftInset, y: height), size: CGSize(width: availableSize.width - leftInset, height: UIScreenPixel))) + self.separatorLayer.isHidden = !component.hasNext + + let containerFrame = CGRect(origin: CGPoint(x: 0.0, y: verticalInset), size: CGSize(width: availableSize.width, height: height - verticalInset * 2.0)) + transition.setFrame(view: self.containerButton, frame: containerFrame) + + return CGSize(width: availableSize.width, height: height) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift index 130b156460..fbddedaa84 100644 --- a/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift +++ b/submodules/TelegramUI/Components/ShareWithPeersScreen/Sources/ShareWithPeersScreen.swift @@ -38,9 +38,11 @@ final class ShareWithPeersScreenComponent: Component { let mentions: [String] let categoryItems: [CategoryItem] let optionItems: [OptionItem] + let coverItem: CoverItem? let completion: (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void let editCategory: (EngineStoryPrivacy, Bool, Bool) -> Void let editBlockedPeers: (EngineStoryPrivacy, Bool, Bool) -> Void + let editCover: () -> Void let peerCompletion: (EnginePeer.Id) -> Void init( @@ -54,9 +56,11 @@ final class ShareWithPeersScreenComponent: Component { mentions: [String], categoryItems: [CategoryItem], optionItems: [OptionItem], + coverItem: CoverItem?, completion: @escaping (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void, editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void, editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void, + editCover: @escaping () -> Void, peerCompletion: @escaping (EnginePeer.Id) -> Void ) { self.context = context @@ -69,9 +73,11 @@ final class ShareWithPeersScreenComponent: Component { self.mentions = mentions self.categoryItems = categoryItems self.optionItems = optionItems + self.coverItem = coverItem self.completion = completion self.editCategory = editCategory self.editBlockedPeers = editBlockedPeers + self.editCover = editCover self.peerCompletion = peerCompletion } @@ -106,6 +112,9 @@ final class ShareWithPeersScreenComponent: Component { if lhs.optionItems != rhs.optionItems { return false } + if lhs.coverItem != rhs.coverItem { + return false + } return true } @@ -258,6 +267,33 @@ final class ShareWithPeersScreenComponent: Component { return false } } + + enum CoverId: Int, Hashable { + case choose = 0 + } + + final class CoverItem: Equatable { + let id: CoverId + let title: String + let image: UIImage? + + init( + id: CoverId, + title: String, + image: UIImage? + ) { + self.id = id + self.title = title + self.image = image + } + + static func ==(lhs: CoverItem, rhs: CoverItem) -> Bool { + if lhs === rhs { + return true + } + return false + } + } final class View: UIView, UIScrollViewDelegate { private let dimView: UIView @@ -1607,6 +1643,90 @@ final class ShareWithPeersScreenComponent: Component { footerText = isSendAsGroup ? environment.strings.Story_Privacy_KeepOnGroupPageInfo(footerValue).string : environment.strings.Story_Privacy_KeepOnChannelPageInfo(footerValue).string } + let footerSize = sectionFooter.update( + transition: sectionFooterTransition, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: footerText, font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)), + maximumNumberOfLines: 0, + lineSpacing: 0.2 + )), + environment: {}, + containerSize: CGSize(width: itemLayout.containerSize.width - 16.0 * 2.0, height: itemLayout.contentHeight) + ) + let footerFrame = CGRect(origin: CGPoint(x: itemLayout.sideInset + 16.0, y: sectionOffset + section.totalHeight + 7.0), size: footerSize) + if let footerView = sectionFooter.view { + if footerView.superview == nil { + self.itemContainerView.addSubview(footerView) + } + sectionFooterTransition.setFrame(view: footerView, frame: footerFrame) + } + sectionOffset += footerSize.height + } else if section.id == 4 && section.itemCount > 0 { + if let item = component.coverItem { + let itemFrame = CGRect(origin: CGPoint(x: itemLayout.sideInset, y: sectionOffset + section.insets.top + CGFloat(0) * section.itemHeight), size: CGSize(width: itemLayout.containerSize.width, height: section.itemHeight)) + if !visibleBounds.intersects(itemFrame) { + continue + } + + let itemId = AnyHashable(item.id) + validIds.append(itemId) + + var itemTransition = transition + let visibleItem: ComponentView + if let current = self.visibleItems[itemId] { + visibleItem = current + } else { + visibleItem = ComponentView() + if !transition.animation.isImmediate { + itemTransition = .immediate + } + self.visibleItems[itemId] = visibleItem + } + + let _ = visibleItem.update( + transition: itemTransition, + component: AnyComponent(CoverListItemComponent( + theme: environment.theme, + title: item.title, + image: item.image, + hasNext: false, + action: { + component.editCover() + } + )), + environment: {}, + containerSize: itemFrame.size + ) + if let itemView = visibleItem.view { + if itemView.superview == nil { + if let minSectionHeader { + self.itemContainerView.insertSubview(itemView, belowSubview: minSectionHeader) + } else { + self.itemContainerView.addSubview(itemView) + } + } + itemTransition.setFrame(view: itemView, frame: itemFrame) + } + } + + let sectionFooter: ComponentView + var sectionFooterTransition = transition + if let current = self.visibleSectionFooters[section.id] { + sectionFooter = current + } else { + if !transition.animation.isImmediate { + sectionFooterTransition = .immediate + } + sectionFooter = ComponentView() + self.visibleSectionFooters[section.id] = sectionFooter + } + + var footerText = "Choose a frame from the story to show in your Profile." + + if let sendAsPeerId = self.sendAsPeerId, sendAsPeerId.isGroupOrChannel == true { + footerText = isSendAsGroup ? "Choose a frame from the story to show in group profile.": "Choose a frame from the story to show in channel profile." + } + let footerSize = sectionFooter.update( transition: sectionFooterTransition, component: AnyComponent(MultilineTextComponent( @@ -1625,7 +1745,6 @@ final class ShareWithPeersScreenComponent: Component { sectionFooterTransition.setFrame(view: footerView, frame: footerFrame) } } - sectionOffset += section.totalHeight } @@ -1873,6 +1992,8 @@ final class ShareWithPeersScreenComponent: Component { } private var currentHasChannels: Bool? + private var currentHasCover: Bool? + func update(component: ShareWithPeersScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { guard !self.isDismissed else { return availableSize @@ -1886,19 +2007,28 @@ final class ShareWithPeersScreenComponent: Component { var hasCategories = false var hasChannels = false + var hasCover = false if case .stories = component.stateContext.subject { if let peerId = self.sendAsPeerId, peerId.isGroupOrChannel { } else { hasCategories = true } let sendAsPeersCount = component.stateContext.stateValue?.sendAsPeers.count ?? 1 - if sendAsPeersCount > 1 { + if sendAsPeersCount > 1 && !"".isEmpty { hasChannels = true } if let currentHasChannels = self.currentHasChannels, currentHasChannels != hasChannels { contentTransition = .spring(duration: 0.4) } self.currentHasChannels = hasChannels + + if self.selectedOptions.contains(.pin) { + hasCover = true + } + if let currentHasCover = self.currentHasCover, currentHasCover != hasCover { + contentTransition = .spring(duration: 0.4) + } + self.currentHasCover = hasCover } else if case .members = component.stateContext.subject { self.dismissPanGesture?.isEnabled = false } else if case .channels = component.stateContext.subject { @@ -2306,6 +2436,15 @@ final class ShareWithPeersScreenComponent: Component { itemHeight: optionItemSize.height, itemCount: component.optionItems.count )) + + if hasCover { + sections.append(ItemLayout.Section( + id: 4, + insets: UIEdgeInsets(top: 28.0, left: 0.0, bottom: 0.0, right: 0.0), + itemHeight: optionItemSize.height, + itemCount: 1 + )) + } } else { sections.append(ItemLayout.Section( id: 1, @@ -2489,8 +2628,12 @@ final class ShareWithPeersScreenComponent: Component { inset = 1000.0 } } else { - inset = 464.0 - inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight + if self.selectedOptions.contains(.pin) { + inset = 1000.0 + } else { + inset = 464.0 + inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight + } } } } @@ -2849,10 +2992,12 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer { pin: Bool = false, timeout: Int = 0, mentions: [String] = [], + coverImage: UIImage? = nil, stateContext: StateContext, completion: @escaping (EnginePeer.Id?, EngineStoryPrivacy, Bool, Bool, [EnginePeer], Bool) -> Void, editCategory: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void = { _, _, _ in }, editBlockedPeers: @escaping (EngineStoryPrivacy, Bool, Bool) -> Void = { _, _, _ in }, + editCover: @escaping () -> Void = { }, peerCompletion: @escaping (EnginePeer.Id) -> Void = { _ in } ) { self.context = context @@ -2861,6 +3006,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer { var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = [] var optionItems: [ShareWithPeersScreenComponent.OptionItem] = [] + var coverItem: ShareWithPeersScreenComponent.CoverItem? if case let .stories(editing) = stateContext.subject { var everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeople if (stateContext.stateValue?.savedSelectedPeers[.everyone]?.count ?? 0) > 0 { @@ -2994,6 +3140,10 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer { title: presentationData.strings.Story_Privacy_KeepOnMyPage )) } + + if !editing || pin { + coverItem = ShareWithPeersScreenComponent.CoverItem(id: .choose, title: "Choose Story Cover", image: coverImage) + } } var theme: ViewControllerComponentContainer.Theme = .dark @@ -3013,9 +3163,11 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer { mentions: mentions, categoryItems: categoryItems, optionItems: optionItems, + coverItem: coverItem, completion: completion, editCategory: editCategory, editBlockedPeers: editBlockedPeers, + editCover: editCover, peerCompletion: peerCompletion ), navigationBarAppearance: .none, theme: theme) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift index e5f5770769..a0f38bbe68 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift @@ -1820,7 +1820,7 @@ public func preloadStoryMedia(context: AccountContext, info: StoryPreloadInfo) - case let .file(file): var fetchRange: (Range, MediaBoxFetchPriority)? for attribute in file.attributes { - if case let .Video(_, _, _, preloadSize) = attribute { + if case let .Video(_, _, _, preloadSize, _) = attribute { if let preloadSize { fetchRange = (0 ..< Int64(preloadSize), .default) } @@ -2045,7 +2045,7 @@ public func waitUntilStoryMediaPreloaded(context: AccountContext, peerId: Engine case let .file(file): var fetchRange: (Range, MediaBoxFetchPriority)? for attribute in file.attributes { - if case let .Video(_, _, _, preloadSize) = attribute { + if case let .Video(_, _, _, preloadSize, _) = attribute { if let preloadSize { fetchRange = (0 ..< Int64(preloadSize), .default) } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index 8aebaa5df7..23b0c5d7d5 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -1136,7 +1136,7 @@ private final class StoryContainerScreenComponent: Component { var isSilentVideo = false if case let .file(file) = slice.item.storyItem.media { for attribute in file.attributes { - if case let .Video(_, _, flags, _) = attribute { + if case let .Video(_, _, flags, _, _) = attribute { if flags.contains(.isSilent) { isSilentVideo = true } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 5c49acf56c..ef0035d282 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -3798,7 +3798,7 @@ public final class StoryItemSetContainerComponent: Component { isVideo = true soundAlpha = 1.0 for attribute in file.attributes { - if case let .Video(_, _, flags, _) = attribute { + if case let .Video(_, _, flags, _, _) = attribute { if flags.contains(.isSilent) { isSilentVideo = true soundAlpha = 0.5 @@ -3831,7 +3831,7 @@ public final class StoryItemSetContainerComponent: Component { var isSilentVideo = false if case let .file(file) = component.slice.item.storyItem.media { for attribute in file.attributes { - if case let .Video(_, _, flags, _) = attribute { + if case let .Video(_, _, flags, _, _) = attribute { if flags.contains(.isSilent) { isSilentVideo = true } diff --git a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift index 4c67fe5171..debccdb94e 100644 --- a/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift +++ b/submodules/TelegramUI/Components/VideoMessageCameraScreen/Sources/VideoMessageCameraScreen.swift @@ -1793,7 +1793,7 @@ public class VideoMessageCameraScreen: ViewController { guard let self else { return } - let values = MediaEditorValues(peerId: self.context.account.peerId, originalDimensions: dimensions, cropOffset: .zero, cropRect: CGRect(origin: .zero, size: dimensions.cgSize), cropScale: 1.0, cropRotation: 0.0, cropMirroring: false, cropOrientation: nil, gradientColors: nil, videoTrimRange: self.node.previewState?.trimRange, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, videoVolume: nil, additionalVideoPath: nil, additionalVideoIsDual: false, additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, additionalVideoPositionChanges: [], additionalVideoTrimRange: nil, additionalVideoOffset: nil, additionalVideoVolume: nil, nightTheme: false, drawing: nil, maskDrawing: nil, entities: [], toolValues: [:], audioTrack: nil, audioTrackTrimRange: nil, audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, qualityPreset: .videoMessage) + let values = MediaEditorValues(peerId: self.context.account.peerId, originalDimensions: dimensions, cropOffset: .zero, cropRect: CGRect(origin: .zero, size: dimensions.cgSize), cropScale: 1.0, cropRotation: 0.0, cropMirroring: false, cropOrientation: nil, gradientColors: nil, videoTrimRange: self.node.previewState?.trimRange, videoIsMuted: false, videoIsFullHd: false, videoIsMirrored: false, videoVolume: nil, additionalVideoPath: nil, additionalVideoIsDual: false, additionalVideoPosition: nil, additionalVideoScale: nil, additionalVideoRotation: nil, additionalVideoPositionChanges: [], additionalVideoTrimRange: nil, additionalVideoOffset: nil, additionalVideoVolume: nil, nightTheme: false, drawing: nil, maskDrawing: nil, entities: [], toolValues: [:], audioTrack: nil, audioTrackTrimRange: nil, audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, coverImageTimestamp: nil, qualityPreset: .videoMessage) var resourceAdjustments: VideoMediaResourceAdjustments? = nil if let valuesData = try? JSONEncoder().encode(values) { @@ -1833,7 +1833,7 @@ public class VideoMessageCameraScreen: ViewController { context.account.postbox.mediaBox.storeCachedResourceRepresentation(resource, representation: CachedVideoFirstFrameRepresentation(), data: data) } - let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: finalDuration, size: video.dimensions, flags: [.instantRoundVideo], preloadSize: nil)]) + let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: previewRepresentations, videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.FileName(fileName: "video.mp4"), .Video(duration: finalDuration, size: video.dimensions, flags: [.instantRoundVideo], preloadSize: nil, coverTime: nil)]) var attributes: [MessageAttribute] = [] if self.cameraState.isViewOnceEnabled { diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift index 88a9c0d8aa..8eacbdcc2a 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerPaste.swift @@ -186,6 +186,7 @@ extension ChatControllerImpl { audioTrackOffset: nil, audioTrackVolume: nil, audioTrackSamples: nil, + coverImageTimestamp: nil, qualityPreset: nil ) @@ -210,7 +211,7 @@ extension ChatControllerImpl { var fileAttributes: [TelegramMediaFileAttribute] = [] fileAttributes.append(.FileName(fileName: "sticker.webm")) fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) - fileAttributes.append(.Video(duration: animatedImage.duration, size: PixelDimensions(width: 512, height: 512), flags: [], preloadSize: nil)) + fileAttributes.append(.Video(duration: animatedImage.duration, size: PixelDimensions(width: 512, height: 512), flags: [], preloadSize: nil, coverTime: nil)) let previewRepresentations: [TelegramMediaImageRepresentation] = [] // if let thumbnailResource { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index fd103a83cb..b31c40bc8c 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -397,7 +397,7 @@ func messageMediaEditingOptions(message: Message) -> MessageMediaEditingOptions return [] case .Animated: break - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if flags.contains(.instantRoundVideo) { return [] } else { diff --git a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift index 6d7b4d1150..d41b9923b6 100644 --- a/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift +++ b/submodules/TelegramUI/Sources/HorizontalListContextResultsChatInputPanelItem.swift @@ -260,7 +260,7 @@ final class HorizontalListContextResultsChatInputPanelItemNode: ListViewItemNode } imageDimensions = externalReference.content?.dimensions?.cgSize if externalReference.type == "gif", let thumbnailResource = externalReference.thumbnail?.resource, let content = externalReference.content, let dimensions = content.dimensions { - videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil)]) + videoFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: thumbnailResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil)]) imageResource = nil } diff --git a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift index a09f5b61de..9b28b54b49 100644 --- a/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift +++ b/submodules/TelegramUI/Sources/PeerMessagesMediaPlaylist.swift @@ -72,7 +72,7 @@ final class MessageMediaPlaylistItem: SharedMediaPlaylistItem { } else { return SharedMediaPlaybackData(type: .music, source: source) } - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if flags.contains(.instantRoundVideo) { return SharedMediaPlaybackData(type: .instantVideo, source: source) } else { @@ -129,7 +129,7 @@ final class MessageMediaPlaylistItem: SharedMediaPlaylistItem { displayData = SharedMediaPlaybackDisplayData.music(title: updatedTitle, performer: updatedPerformer, albumArt: albumArt, long: CGFloat(duration) > 10.0 * 60.0, caption: caption) } return displayData - case let .Video(_, _, flags, _): + case let .Video(_, _, flags, _, _): if flags.contains(.instantRoundVideo) { return SharedMediaPlaybackDisplayData.instantVideo(author: self.message.effectiveAuthor.flatMap(EnginePeer.init), peer: self.message.peers[self.message.id.peerId].flatMap(EnginePeer.init), timestamp: self.message.timestamp) } else { diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 4979df19a1..cd906b554d 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -671,7 +671,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon return nil } } - media = .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers) + media = .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers, coverTime: values.coverImageTimestamp) } default: break diff --git a/submodules/WatchBridge/Sources/WatchBridge.swift b/submodules/WatchBridge/Sources/WatchBridge.swift index 4cceb2c348..c7c75e42e1 100644 --- a/submodules/WatchBridge/Sources/WatchBridge.swift +++ b/submodules/WatchBridge/Sources/WatchBridge.swift @@ -172,7 +172,7 @@ func makeBridgeMedia(message: Message, strings: PresentationStrings, chatPeer: P for attribute in file.attributes { switch attribute { - case let .Video(duration, size, flags, _): + case let .Video(duration, size, flags, _, _): bridgeVideo.duration = Int32(duration) bridgeVideo.dimensions = size.cgSize bridgeVideo.round = flags.contains(.instantRoundVideo) diff --git a/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift b/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift index 2dd6561a20..f6b4715142 100644 --- a/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift +++ b/submodules/WebSearchUI/Sources/WebSearchGalleryController.swift @@ -37,7 +37,7 @@ struct WebSearchGalleryEntry: Equatable { switch self.result { case let .externalReference(externalReference): if let content = externalReference.content, externalReference.type == "gif", let thumbnailResource = externalReference.thumbnail?.resource, let dimensions = content.dimensions { - let fileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil)])) + let fileReference = FileMediaReference.standalone(media: TelegramMediaFile(fileId: EngineMedia.Id(namespace: Namespaces.Media.LocalFile, id: 0), partialReference: nil, resource: content.resource, previewRepresentations: [TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "video/mp4", size: nil, attributes: [.Animated, .Video(duration: 0, size: dimensions, flags: [], preloadSize: nil, coverTime: nil)])) return WebSearchVideoGalleryItem(context: context, presentationData: presentationData, index: self.index, result: self.result, content: NativeVideoContent(id: .contextResult(self.result.queryId, self.result.id), userLocation: .other, fileReference: fileReference, loopVideo: true, enableSound: false, fetchAutomatically: true, storeAfterDownload: nil), controllerInteraction: controllerInteraction) } case let .internalReference(internalReference): diff --git a/submodules/WidgetItemsUtils/Sources/WidgetItemsUtils.swift b/submodules/WidgetItemsUtils/Sources/WidgetItemsUtils.swift index a17977a4d4..e350f2ec7c 100644 --- a/submodules/WidgetItemsUtils/Sources/WidgetItemsUtils.swift +++ b/submodules/WidgetItemsUtils/Sources/WidgetItemsUtils.swift @@ -22,7 +22,7 @@ public extension WidgetDataPeer.Message { switch attribute { case let .Sticker(altText, _, _): content = .sticker(WidgetDataPeer.Message.Content.Sticker(altText: altText)) - case let .Video(duration, _, flags, _): + case let .Video(duration, _, flags, _, _): if flags.contains(.instantRoundVideo) { content = .videoMessage(WidgetDataPeer.Message.Content.VideoMessage(duration: Int32(duration))) } else {