diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift index 01c79d7df3..dff3fadfcc 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackController.swift @@ -89,7 +89,7 @@ public final class ImportStickerPackController: ViewController, StandalonePresen var signals: [Signal<(UUID, StickerVerificationStatus, EngineMediaResource?), NoError>] = [] for sticker in strongSelf.stickerPack.stickers { if let resource = strongSelf.controllerNode.stickerResources[sticker.uuid] { - signals.append(strongSelf.context.engine.stickers.uploadSticker(peer: peer, resource: resource._asResource(), alt: sticker.emojis.first ?? "", dimensions: PixelDimensions(width: 512, height: 512), mimeType: sticker.mimeType) + signals.append(strongSelf.context.engine.stickers.uploadSticker(peer: peer, resource: resource._asResource(), alt: sticker.emojis.first ?? "", dimensions: PixelDimensions(width: 512, height: 512), duration: nil, mimeType: sticker.mimeType) |> map { result -> (UUID, StickerVerificationStatus, EngineMediaResource?) in switch result { case .progress: diff --git a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift index f988ba1df5..84c0bff8bf 100644 --- a/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift +++ b/submodules/ImportStickerPackUI/Sources/ImportStickerPackControllerNode.swift @@ -625,9 +625,9 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, ASScroll if let localResource = item.stickerItem.resource { self.context.account.postbox.mediaBox.copyResourceData(from: localResource._asResource().id, to: resource._asResource().id) } - stickers.append(ImportSticker(resource: .standalone(resource: resource._asResource()), emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords)) + stickers.append(ImportSticker(resource: .standalone(resource: resource._asResource()), emojis: item.stickerItem.emojis, dimensions: dimensions, duration: nil, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords)) } else if let resource = item.stickerItem.resource { - stickers.append(ImportSticker(resource: .standalone(resource: resource._asResource()), emojis: item.stickerItem.emojis, dimensions: dimensions, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords)) + stickers.append(ImportSticker(resource: .standalone(resource: resource._asResource()), emojis: item.stickerItem.emojis, dimensions: dimensions, duration: nil, mimeType: item.stickerItem.mimeType, keywords: item.stickerItem.keywords)) } } var thumbnailSticker: ImportSticker? @@ -638,7 +638,7 @@ final class ImportStickerPackControllerNode: ViewControllerTracingNode, ASScroll } let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max)) self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: thumbnail.data) - thumbnailSticker = ImportSticker(resource: .standalone(resource: resource), emojis: [], dimensions: dimensions, mimeType: thumbnail.mimeType, keywords: thumbnail.keywords) + thumbnailSticker = ImportSticker(resource: .standalone(resource: resource), emojis: [], dimensions: dimensions, duration: nil, mimeType: thumbnail.mimeType, keywords: thumbnail.keywords) } let firstStickerItem = thumbnailSticker ?? stickers.first diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 983ddcda93..da8c3454b4 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -717,6 +717,16 @@ private final class StickerPackContainer: ASDisplayNode { if let reorderPosition = self.reorderPosition, let file = itemNode.stickerPackItem?.file { let _ = self.context.engine.stickers.reorderSticker(sticker: .standalone(media: file), position: reorderPosition).startStandalone() + + if let (info, items, isInstalled) = self.currentStickerPack { + var updatedItems = items + if let index = items.firstIndex(where: { $0.file.fileId == file.fileId }) { + let item = items[index] + updatedItems.remove(at: index) + updatedItems.insert(item, at: reorderPosition) + } + self.currentStickerPack = (info, updatedItems, isInstalled) + } } } else { reorderNode.removeFromSupernode() @@ -1256,6 +1266,7 @@ private final class StickerPackContainer: ASDisplayNode { resource: .standalone(resource: file.resource), emojis: emoji, dimensions: file.dimensions ?? PixelDimensions(width: 512, height: 512), + duration: file.duration, mimeType: file.mimeType, keywords: "" ) @@ -1305,6 +1316,7 @@ private final class StickerPackContainer: ASDisplayNode { resource: file.resourceReference(file.media.resource), emojis: [emoji], dimensions: file.media.dimensions ?? PixelDimensions(width: 512, height: 512), + duration: file.media.duration, mimeType: file.media.mimeType, keywords: "" ) @@ -1347,6 +1359,7 @@ private final class StickerPackContainer: ASDisplayNode { resource: .standalone(resource: file.resource), emojis: emoji, dimensions: file.dimensions ?? PixelDimensions(width: 512, height: 512), + duration: file.duration, mimeType: file.mimeType, keywords: "" ) @@ -1856,12 +1869,14 @@ private final class StickerPackContainer: ASDisplayNode { entries.append(.sticker(index: entries.count, stableId: resolvedStableId, stickerItem: item, isEmpty: false, isPremium: isPremium, isLocked: isLocked, isEditing: false, isAdd: false)) } + var addedReorderItem = false var currentIndex: Int = 0 for item in generalItems { - if self.isReordering, let reorderNode = self.reorderNode, let reorderItem = reorderNode.itemNode?.stickerPackItem, let reorderPosition = self.reorderPosition { + if self.isReordering, let reorderItem = self.reorderNode?.itemNode?.stickerPackItem, let reorderPosition = self.reorderPosition { if currentIndex == reorderPosition { addItem(reorderItem, false, false) currentIndex += 1 + addedReorderItem = true } if item.file.fileId == reorderItem.file.fileId { @@ -1875,6 +1890,11 @@ private final class StickerPackContainer: ASDisplayNode { currentIndex += 1 } } + if !addedReorderItem, let reorderItem = self.reorderNode?.itemNode?.stickerPackItem, let reorderPosition = self.reorderPosition, currentIndex == reorderPosition { + addItem(reorderItem, false, false) + currentIndex += 1 + addedReorderItem = true + } if !premiumConfiguration.isPremiumDisabled { if !premiumItems.isEmpty { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift index bd0e213582..c107e8635e 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/ImportStickers.swift @@ -33,7 +33,7 @@ private func uploadedSticker(postbox: Postbox, network: Network, resource: Media } } -func _internal_uploadSticker(account: Account, peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, mimeType: String) -> Signal { +func _internal_uploadSticker(account: Account, peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, duration: Double?, mimeType: String) -> Signal { guard let inputPeer = apiInputPeer(peer) else { return .fail(.generic) } @@ -51,6 +51,9 @@ func _internal_uploadSticker(account: Account, peer: Peer, resource: MediaResour let flags: Int32 = 0 var attributes: [Api.DocumentAttribute] = [] attributes.append(.documentAttributeSticker(flags: 0, alt: alt, stickerset: .inputStickerSetEmpty, maskCoords: nil)) + if let duration { + attributes.append(.documentAttributeVideo(flags: 0, duration: duration, w: dimensions.width, h: dimensions.height, preloadPrefixSize: nil)) + } attributes.append(.documentAttributeImageSize(w: dimensions.width, h: dimensions.height)) return account.network.request(Api.functions.messages.uploadMedia(flags: 0, businessConnectionId: nil, peer: inputPeer, media: Api.InputMedia.inputMediaUploadedDocument(flags: flags, file: file, thumb: nil, mimeType: mimeType, attributes: attributes, stickers: nil, ttlSeconds: nil))) |> mapError { _ -> UploadStickerError in return .generic } @@ -80,13 +83,15 @@ public struct ImportSticker { public let resource: MediaResourceReference let emojis: [String] public let dimensions: PixelDimensions + public let duration: Double? public let mimeType: String public let keywords: String - public init(resource: MediaResourceReference, emojis: [String], dimensions: PixelDimensions, mimeType: String, keywords: String) { + public init(resource: MediaResourceReference, emojis: [String], dimensions: PixelDimensions, duration: Double?, mimeType: String, keywords: String) { self.resource = resource self.emojis = emojis self.dimensions = dimensions + self.duration = duration self.mimeType = mimeType self.keywords = keywords } @@ -102,6 +107,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)) } else if self.mimeType == "application/x-tgsticker" { fileAttributes.append(.FileName(fileName: "sticker.tgs")) fileAttributes.append(.Animated) @@ -153,7 +159,7 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri if let resource = sticker.resource.resource as? CloudDocumentMediaResource { uploadStickers.append(.single(.complete(resource, sticker.mimeType))) } else { - uploadStickers.append(_internal_uploadSticker(account: account, peer: peer, resource: sticker.resource.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, mimeType: sticker.mimeType) + uploadStickers.append(_internal_uploadSticker(account: account, peer: peer, resource: sticker.resource.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, duration: sticker.duration, mimeType: sticker.mimeType) |> mapError { _ -> CreateStickerSetError in return .generic }) @@ -300,7 +306,7 @@ func _internal_addStickerToStickerSet(account: Account, packReference: StickerPa uploadSticker = account.postbox.loadedPeerWithId(account.peerId) |> castError(AddStickerToSetError.self) |> mapToSignal { peer in - return _internal_uploadSticker(account: account, peer: peer, resource: sticker.resource.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, mimeType: sticker.mimeType) + return _internal_uploadSticker(account: account, peer: peer, resource: sticker.resource.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, duration: sticker.duration, mimeType: sticker.mimeType) |> mapError { _ -> AddStickerToSetError in return .generic } @@ -428,7 +434,7 @@ func _internal_replaceSticker(account: Account, previousSticker: FileMediaRefere uploadSticker = account.postbox.loadedPeerWithId(account.peerId) |> castError(ReplaceStickerError.self) |> mapToSignal { peer in - return _internal_uploadSticker(account: account, peer: peer, resource: sticker.resource.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, mimeType: sticker.mimeType) + return _internal_uploadSticker(account: account, peer: peer, resource: sticker.resource.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, duration: sticker.duration, mimeType: sticker.mimeType) |> mapError { _ -> ReplaceStickerError in return .generic } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift index 01db7419d8..4300d76c83 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Stickers/TelegramEngineStickers.swift @@ -78,8 +78,8 @@ public extension TelegramEngine { return _internal_stickerPacksAttachedToMedia(account: self.account, media: media) } - public func uploadSticker(peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, mimeType: String) -> Signal { - return _internal_uploadSticker(account: self.account, peer: peer, resource: resource, alt: alt, dimensions: dimensions, mimeType: mimeType) + public func uploadSticker(peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, duration: Double?, mimeType: String) -> Signal { + return _internal_uploadSticker(account: self.account, peer: peer, resource: resource, alt: alt, dimensions: dimensions, duration: duration, mimeType: mimeType) } public func createStickerSet(title: String, shortName: String, stickers: [ImportSticker], thumbnail: ImportSticker?, type: CreateStickerSetType, software: String?) -> Signal { diff --git a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift index 494f8c2c4c..15f25e9f43 100644 --- a/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift +++ b/submodules/TelegramUI/Components/MediaEditorScreen/Sources/MediaEditorScreen.swift @@ -6231,6 +6231,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate private var stickerRecommendedEmoji: [String] = [] private var stickerSelectedEmoji: [String] = [] + private func effectiveStickerEmoji() -> [String] { let filtered = self.stickerSelectedEmoji.filter { !$0.isEmpty } guard !filtered.isEmpty else { @@ -6238,6 +6239,23 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } return filtered } + + private func preferredStickerDuration() -> Double { + var duration: Double = 3.0 + var stickerDurations: [Double] = [] + self.node.entitiesView.eachView { entityView in + if let stickerEntityView = entityView as? DrawingStickerEntityView { + if let duration = stickerEntityView.duration, duration > 0.0 { + stickerDurations.append(duration) + } + } + } + if !stickerDurations.isEmpty { + duration = stickerDurations.max() ?? 3.0 + } + return duration + } + private weak var stickerResultController: PeekController? func presentStickerPreview(image: UIImage) { guard let mediaEditor = self.node.mediaEditor else { @@ -6257,8 +6275,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate self.context.account.postbox.mediaBox.storeResourceData(isVideo ? thumbnailResource.id : resource.id, data: data) } } - var file = stickerFile(resource: resource, thumbnailResource: thumbnailResource, size: Int64(0), dimensions: PixelDimensions(image.size), isVideo: isVideo) - let emoji = self.stickerSelectedEmoji + var file = stickerFile(resource: resource, thumbnailResource: thumbnailResource, size: Int64(0), dimensions: PixelDimensions(image.size), duration: self.preferredStickerDuration(), isVideo: isVideo) var menuItems: [ContextMenuItem] = [] if case let .stickerEditor(mode) = self.mode { @@ -6274,7 +6291,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate } else { self.stickerResultController?.disappeared = nil self.completion(MediaEditorScreen.Result( - media: .sticker(file: file, emoji: emoji), + media: .sticker(file: file, emoji: self.effectiveStickerEmoji()), mediaAreas: [], caption: NSAttributedString(), options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false), @@ -6465,8 +6482,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate ImportSticker( resource: .standalone(resource: file.resource), emojis: self.effectiveStickerEmoji(), - dimensions: PixelDimensions(width: 512, height: 512), - mimeType: "image/webp", + dimensions: file.dimensions ?? PixelDimensions(width: 512, height: 512), + duration: file.duration, + mimeType: file.mimeType, keywords: "" ) ], @@ -6512,9 +6530,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate private func uploadSticker(_ file: TelegramMediaFile, action: StickerAction) { let context = self.context let dimensions = PixelDimensions(width: 512, height: 512) + let duration = file.duration let mimeType = file.mimeType let isVideo = file.mimeType == "video/webm" - let emoji = self.stickerSelectedEmoji + let emojis = self.effectiveStickerEmoji() var isUpdate = false if case .update = action { @@ -6578,13 +6597,13 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate if let resource = resource as? CloudDocumentMediaResource { return .single(.progress(1.0)) |> then(.single(.complete(resource, mimeType))) } else { - return context.engine.stickers.uploadSticker(peer: peer._asPeer(), resource: resource, alt: "", dimensions: dimensions, mimeType: mimeType) + return context.engine.stickers.uploadSticker(peer: peer._asPeer(), resource: resource, alt: "", dimensions: dimensions, duration: duration, mimeType: mimeType) |> mapToSignal { status -> Signal in switch status { case let .progress(progress): return .single(.progress(isVideo ? 0.5 + progress * 0.5 : progress)) case let .complete(resource, _): - let file = stickerFile(resource: resource, thumbnailResource: file.previewRepresentations.first?.resource, size: file.size ?? 0, dimensions: dimensions, isVideo: isVideo) + let file = stickerFile(resource: resource, thumbnailResource: file.previewRepresentations.first?.resource, size: file.size ?? 0, dimensions: dimensions, duration: file.duration, isVideo: isVideo) switch action { case .send: return .single(status) @@ -6599,8 +6618,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate case let .createStickerPack(title): let sticker = ImportSticker( resource: .standalone(resource: resource), - emojis: self.effectiveStickerEmoji(), + emojis: emojis, dimensions: dimensions, + duration: duration, mimeType: mimeType, keywords: "" ) @@ -6618,8 +6638,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate case let .addToStickerPack(pack, _): let sticker = ImportSticker( resource: .standalone(resource: resource), - emojis: self.effectiveStickerEmoji(), + emojis: emojis, dimensions: dimensions, + duration: duration, mimeType: mimeType, keywords: "" ) @@ -6659,10 +6680,10 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate let result: MediaEditorScreen.Result switch action { case .update: - result = MediaEditorScreen.Result(media: .sticker(file: file, emoji: emoji)) + result = MediaEditorScreen.Result(media: .sticker(file: file, emoji: emojis)) case .upload, .send: - let file = stickerFile(resource: resource, thumbnailResource: file.previewRepresentations.first?.resource, size: resource.size ?? 0, dimensions: dimensions, isVideo: isVideo) - result = MediaEditorScreen.Result(media: .sticker(file: file, emoji: emoji)) + let file = stickerFile(resource: resource, thumbnailResource: file.previewRepresentations.first?.resource, size: resource.size ?? 0, dimensions: dimensions, duration: self.preferredStickerDuration(), isVideo: isVideo) + result = MediaEditorScreen.Result(media: .sticker(file: file, emoji: emojis)) default: result = MediaEditorScreen.Result() } @@ -6844,18 +6865,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate duration = video.duration.seconds } if isSticker { - duration = 3.0 - var stickerDurations: [Double] = [] - self.node.entitiesView.eachView { entityView in - if let stickerEntityView = entityView as? DrawingStickerEntityView { - if let duration = stickerEntityView.duration, duration > 0.0 { - stickerDurations.append(duration) - } - } - } - if !stickerDurations.isEmpty { - duration = stickerDurations.max() ?? 3.0 - } + duration = self.preferredStickerDuration() } let configuration = recommendedVideoExportConfiguration(values: mediaEditor.values, duration: duration, forceFullHd: true, frameRate: 60.0, isSticker: isSticker) let outputPath = NSTemporaryDirectory() + "\(Int64.random(in: 0 ..< .max)).\(fileExtension)" @@ -7610,12 +7620,15 @@ extension MediaScrubberComponent.Track { } } -private func stickerFile(resource: TelegramMediaResource, thumbnailResource: TelegramMediaResource?, size: Int64, dimensions: PixelDimensions, isVideo: Bool) -> TelegramMediaFile { +private func stickerFile(resource: TelegramMediaResource, thumbnailResource: TelegramMediaResource?, size: Int64, dimensions: PixelDimensions, duration: Double?, isVideo: Bool) -> TelegramMediaFile { var fileAttributes: [TelegramMediaFileAttribute] = [] fileAttributes.append(.FileName(fileName: isVideo ? "sticker.webm" : "sticker.webp")) fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil)) - fileAttributes.append(.ImageSize(size: dimensions)) - + if isVideo { + fileAttributes.append(.Video(duration: duration ?? 3.0, size: dimensions, flags: [], preloadSize: nil)) + } else { + fileAttributes.append(.ImageSize(size: dimensions)) + } var previewRepresentations: [TelegramMediaImageRepresentation] = [] if let thumbnailResource { previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: dimensions, resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil)) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 78386b6264..80944b6120 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -761,7 +761,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { if file.isAnimatedSticker { thumbnailItem = .animated(EngineMediaResource(file.resource)) resourceReference = MediaResourceReference.media(media: .standalone(media: file), resource: file.resource) - } else if let dimensions = file.dimensions, let resource = chatMessageStickerResource(file: file, small: true) as? TelegramMediaResource { + } else if let dimensions = file.dimensions, let resource = chatMessageStickerResource(file: file, small: false) as? TelegramMediaResource { thumbnailItem = .still(TelegramMediaImageRepresentation(dimensions: dimensions, resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)) resourceReference = MediaResourceReference.media(media: .standalone(media: file), resource: resource) }