[WIP] Stickers editor

This commit is contained in:
Ilya Laktyushin 2024-04-06 23:08:44 +04:00
parent ee2b7be5e2
commit a6b5f0f96e
7 changed files with 80 additions and 41 deletions

View File

@ -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:

View File

@ -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

View File

@ -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 {

View File

@ -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<UploadStickerStatus, UploadStickerError> {
func _internal_uploadSticker(account: Account, peer: Peer, resource: MediaResource, alt: String, dimensions: PixelDimensions, duration: Double?, mimeType: String) -> Signal<UploadStickerStatus, UploadStickerError> {
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
}

View File

@ -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<UploadStickerStatus, UploadStickerError> {
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<UploadStickerStatus, UploadStickerError> {
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<CreateStickerSetStatus, CreateStickerSetError> {

View File

@ -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<UploadStickerStatus, UploadStickerError> 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))

View File

@ -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)
}