[WIP] Sticker editor

This commit is contained in:
Ilya Laktyushin
2024-03-09 03:19:31 +04:00
parent c78f407c02
commit 3bdcb7f223
86 changed files with 5498 additions and 1990 deletions

View File

@@ -388,7 +388,7 @@ func _internal_sendMessageShortcut(account: Account, peerId: PeerId, id: Int32)
guard let peer, let inputPeer = apiInputPeer(peer) else {
return .complete()
}
return account.network.request(Api.functions.messages.sendQuickReplyMessages(peer: inputPeer, shortcutId: id))
return account.network.request(Api.functions.messages.sendQuickReplyMessages(peer: inputPeer, shortcutId: id, id: [], randomId: []))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
return .single(nil)

View File

@@ -209,66 +209,8 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
return .generic
}
|> mapToSignal { result -> Signal<CreateStickerSetStatus, CreateStickerSetError> in
let info: StickerPackCollectionInfo
var items: [StickerPackItem] = []
switch result {
case .stickerSetNotModified:
guard let (info, items) = parseStickerSetInfoAndItems(apiStickerSet: result) else {
return .complete()
case let .stickerSet(set, packs, keywords, documents):
let namespace: ItemCollectionId.Namespace
switch set {
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _):
if (flags & (1 << 3)) != 0 {
namespace = Namespaces.ItemCollection.CloudMaskPacks
} else if (flags & (1 << 7)) != 0 {
namespace = Namespaces.ItemCollection.CloudEmojiPacks
} else {
namespace = Namespaces.ItemCollection.CloudStickerPacks
}
}
info = StickerPackCollectionInfo(apiSet: set, namespace: namespace)
var indexKeysByFile: [MediaId: [MemoryBuffer]] = [:]
for pack in packs {
switch pack {
case let .stickerPack(text, fileIds):
let key = ValueBoxKey(text).toMemoryBuffer()
for fileId in fileIds {
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
if indexKeysByFile[mediaId] == nil {
indexKeysByFile[mediaId] = [key]
} else {
indexKeysByFile[mediaId]!.append(key)
}
}
}
}
for keyword in keywords {
switch keyword {
case let .stickerKeyword(documentId, texts):
for text in texts {
let key = ValueBoxKey(text).toMemoryBuffer()
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId)
if indexKeysByFile[mediaId] == nil {
indexKeysByFile[mediaId] = [key]
} else {
indexKeysByFile[mediaId]!.append(key)
}
}
}
}
for apiDocument in documents {
if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id {
let fileIndexKeys: [MemoryBuffer]
if let indexKeys = indexKeysByFile[id] {
fileIndexKeys = indexKeys
} else {
fileIndexKeys = []
}
items.append(StickerPackItem(index: ItemCollectionItemIndex(index: Int32(items.count), id: id.id), file: file, indexKeys: fileIndexKeys))
}
}
}
return .single(.complete(info, items))
}
@@ -294,6 +236,279 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
}
}
public enum RenameStickerSetError {
case generic
}
func _internal_renameStickerSet(account: Account, packReference: StickerPackReference, title: String) -> Signal<Never, RenameStickerSetError> {
return account.network.request(Api.functions.stickers.renameStickerSet(stickerset: packReference.apiInputStickerSet, title: title))
|> mapError { error -> RenameStickerSetError in
return .generic
}
|> mapToSignal { result -> Signal<Never, RenameStickerSetError> in
guard let (info, items) = parseStickerSetInfoAndItems(apiStickerSet: result) else {
return .complete()
}
return account.postbox.transaction { transaction -> Void in
transaction.replaceItemCollectionInfos(namespace: Namespaces.ItemCollection.CloudStickerPacks, itemCollectionInfos: [(info.id, info)])
cacheStickerPack(transaction: transaction, info: info, items: items)
}
|> castError(RenameStickerSetError.self)
|> ignoreValues
}
}
public enum DeleteStickerSetError {
case generic
}
func _internal_deleteStickerSet(account: Account, packReference: StickerPackReference) -> Signal<Never, DeleteStickerSetError> {
return account.network.request(Api.functions.stickers.deleteStickerSet(stickerset: packReference.apiInputStickerSet))
|> mapError { error -> DeleteStickerSetError in
return .generic
}
|> mapToSignal { _ in
return account.postbox.transaction { transaction in
if case let .id(id, _) = packReference {
transaction.removeItemCollection(collectionId: ItemCollectionId(namespace: Namespaces.ItemCollection.CloudStickerPacks, id: id))
}
}
|> castError(DeleteStickerSetError.self)
}
|> ignoreValues
}
public enum AddStickerToSetError {
case generic
}
func _internal_addStickerToStickerSet(account: Account, packReference: StickerPackReference, sticker: ImportSticker) -> Signal<Bool, AddStickerToSetError> {
let uploadSticker: Signal<UploadStickerStatus, AddStickerToSetError>
if let resource = sticker.resource as? CloudDocumentMediaResource {
uploadSticker = .single(.complete(resource, sticker.mimeType))
} else {
uploadSticker = account.postbox.loadedPeerWithId(account.peerId)
|> castError(AddStickerToSetError.self)
|> mapToSignal { peer in
return _internal_uploadSticker(account: account, peer: peer, resource: sticker.resource, alt: sticker.emojis.first ?? "", dimensions: sticker.dimensions, mimeType: sticker.mimeType)
|> mapError { _ -> AddStickerToSetError in
return .generic
}
}
}
return uploadSticker
|> mapToSignal { uploadedSticker in
guard case let .complete(resource, _) = uploadedSticker else {
return .complete()
}
var flags: Int32 = 0
if sticker.keywords.count > 0 {
flags |= (1 << 1)
}
let inputSticker: Api.InputStickerSetItem = .inputStickerSetItem(flags: flags, document: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), emoji: sticker.emojis.first ?? "", maskCoords: nil, keywords: sticker.keywords)
return account.network.request(Api.functions.stickers.addStickerToSet(stickerset: packReference.apiInputStickerSet, sticker: inputSticker))
|> mapError { error -> AddStickerToSetError in
return .generic
}
|> mapToSignal { result -> Signal<Bool, AddStickerToSetError> in
guard let (info, items) = parseStickerSetInfoAndItems(apiStickerSet: result) else {
return .complete()
}
return account.postbox.transaction { transaction -> Bool in
if transaction.getItemCollectionInfo(collectionId: info.id) != nil {
transaction.replaceItemCollectionItems(collectionId: info.id, items: items)
}
cacheStickerPack(transaction: transaction, info: info, items: items)
return true
}
|> castError(AddStickerToSetError.self)
}
}
}
public enum ReorderStickerError {
case generic
}
func _internal_reorderSticker(account: Account, sticker: FileMediaReference, position: Int) -> Signal<Never, ReorderStickerError> {
guard let resource = sticker.media.resource as? CloudDocumentMediaResource else {
return .fail(.generic)
}
return account.network.request(Api.functions.stickers.changeStickerPosition(sticker: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference)), position: Int32(position)))
|> mapError { error -> ReorderStickerError in
return .generic
}
|> mapToSignal { result -> Signal<Never, ReorderStickerError> in
guard let (info, items) = parseStickerSetInfoAndItems(apiStickerSet: result) else {
return .complete()
}
return account.postbox.transaction { transaction -> Void in
if transaction.getItemCollectionInfo(collectionId: info.id) != nil {
transaction.replaceItemCollectionItems(collectionId: info.id, items: items)
}
cacheStickerPack(transaction: transaction, info: info, items: items)
}
|> castError(ReorderStickerError.self)
|> ignoreValues
}
}
public enum DeleteStickerError {
case generic
}
func _internal_deleteStickerFromStickerSet(account: Account, sticker: FileMediaReference) -> Signal<Never, DeleteStickerError> {
guard let resource = sticker.media.resource as? CloudDocumentMediaResource else {
return .fail(.generic)
}
return account.network.request(Api.functions.stickers.removeStickerFromSet(sticker: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference))))
|> mapError { error -> DeleteStickerError in
return .generic
}
|> mapToSignal { result -> Signal<Never, DeleteStickerError> in
guard let (info, items) = parseStickerSetInfoAndItems(apiStickerSet: result) else {
return .complete()
}
return account.postbox.transaction { transaction -> Void in
if transaction.getItemCollectionInfo(collectionId: info.id) != nil {
transaction.replaceItemCollectionItems(collectionId: info.id, items: items)
}
cacheStickerPack(transaction: transaction, info: info, items: items)
}
|> castError(DeleteStickerError.self)
|> ignoreValues
}
}
func _internal_getMyStickerSets(account: Account) -> Signal<[(StickerPackCollectionInfo, StickerPackItem?)], NoError> {
return account.network.request(Api.functions.messages.getMyStickers(offsetId: 0, limit: 100))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.MyStickers?, NoError> in
return .single(nil)
}
|> map { result -> [(StickerPackCollectionInfo, StickerPackItem?)] in
guard let result else {
return []
}
var infos: [(StickerPackCollectionInfo, StickerPackItem?)] = []
switch result {
case let .myStickers(_, sets):
for set in sets {
switch set {
case let .stickerSetCovered(set, cover):
let namespace: ItemCollectionId.Namespace
switch set {
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _):
if (flags & (1 << 3)) != 0 {
namespace = Namespaces.ItemCollection.CloudMaskPacks
} else if (flags & (1 << 7)) != 0 {
namespace = Namespaces.ItemCollection.CloudEmojiPacks
} else {
namespace = Namespaces.ItemCollection.CloudStickerPacks
}
}
let info = StickerPackCollectionInfo(apiSet: set, namespace: namespace)
var firstItem: StickerPackItem?
if let file = telegramMediaFileFromApiDocument(cover), let id = file.id {
firstItem = StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: id.id), file: file, indexKeys: [])
}
infos.append((info, firstItem))
case let .stickerSetFullCovered(set, _, _, documents):
let namespace: ItemCollectionId.Namespace
switch set {
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _):
if (flags & (1 << 3)) != 0 {
namespace = Namespaces.ItemCollection.CloudMaskPacks
} else if (flags & (1 << 7)) != 0 {
namespace = Namespaces.ItemCollection.CloudEmojiPacks
} else {
namespace = Namespaces.ItemCollection.CloudStickerPacks
}
}
let info = StickerPackCollectionInfo(apiSet: set, namespace: namespace)
var firstItem: StickerPackItem?
if let apiDocument = documents.first {
if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id {
firstItem = StickerPackItem(index: ItemCollectionItemIndex(index: 0, id: id.id), file: file, indexKeys: [])
}
}
infos.append((info, firstItem))
default:
break
}
}
}
return infos
}
}
private func parseStickerSetInfoAndItems(apiStickerSet: Api.messages.StickerSet) -> (StickerPackCollectionInfo, [StickerPackItem])? {
switch apiStickerSet {
case .stickerSetNotModified:
return nil
case let .stickerSet(set, packs, keywords, documents):
let namespace: ItemCollectionId.Namespace
switch set {
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _):
if (flags & (1 << 3)) != 0 {
namespace = Namespaces.ItemCollection.CloudMaskPacks
} else if (flags & (1 << 7)) != 0 {
namespace = Namespaces.ItemCollection.CloudEmojiPacks
} else {
namespace = Namespaces.ItemCollection.CloudStickerPacks
}
}
let info = StickerPackCollectionInfo(apiSet: set, namespace: namespace)
var indexKeysByFile: [MediaId: [MemoryBuffer]] = [:]
for pack in packs {
switch pack {
case let .stickerPack(text, fileIds):
let key = ValueBoxKey(text).toMemoryBuffer()
for fileId in fileIds {
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)
if indexKeysByFile[mediaId] == nil {
indexKeysByFile[mediaId] = [key]
} else {
indexKeysByFile[mediaId]!.append(key)
}
}
}
}
for keyword in keywords {
switch keyword {
case let .stickerKeyword(documentId, texts):
for text in texts {
let key = ValueBoxKey(text).toMemoryBuffer()
let mediaId = MediaId(namespace: Namespaces.Media.CloudFile, id: documentId)
if indexKeysByFile[mediaId] == nil {
indexKeysByFile[mediaId] = [key]
} else {
indexKeysByFile[mediaId]!.append(key)
}
}
}
}
var items: [StickerPackItem] = []
for apiDocument in documents {
if let file = telegramMediaFileFromApiDocument(apiDocument), let id = file.id {
let fileIndexKeys: [MemoryBuffer]
if let indexKeys = indexKeysByFile[id] {
fileIndexKeys = indexKeys
} else {
fileIndexKeys = []
}
items.append(StickerPackItem(index: ItemCollectionItemIndex(index: Int32(items.count), id: id.id), file: file, indexKeys: fileIndexKeys))
}
}
return (info, items)
}
}
func _internal_getStickerSetShortNameSuggestion(account: Account, title: String) -> Signal<String?, NoError> {
return account.network.request(Api.functions.stickers.suggestShortName(title: title))
|> map (Optional.init)

View File

@@ -55,6 +55,9 @@ extension StickerPackCollectionInfo {
if (flags & (1 << 10)) != 0 {
setFlags.insert(.isAvailableAsChannelStatus)
}
if (flags & (1 << 11)) != 0 {
setFlags.insert(.isCreator)
}
var thumbnailRepresentation: TelegramMediaImageRepresentation?
var immediateThumbnailData: Data?

View File

@@ -86,6 +86,30 @@ public extension TelegramEngine {
return _internal_createStickerSet(account: self.account, title: title, shortName: shortName, stickers: stickers, thumbnail: thumbnail, type: type, software: software)
}
public func renameStickerSet(packReference: StickerPackReference, title: String) -> Signal<Never, RenameStickerSetError> {
return _internal_renameStickerSet(account: self.account, packReference: packReference, title: title)
}
public func deleteStickerSet(packReference: StickerPackReference) -> Signal<Never, DeleteStickerSetError> {
return _internal_deleteStickerSet(account: self.account, packReference: packReference)
}
public func addStickerToStickerSet(packReference: StickerPackReference, sticker: ImportSticker) -> Signal<Bool, AddStickerToSetError> {
return _internal_addStickerToStickerSet(account: self.account, packReference: packReference, sticker: sticker)
}
public func reorderSticker(sticker: FileMediaReference, position: Int) -> Signal<Never, ReorderStickerError> {
return _internal_reorderSticker(account: self.account, sticker: sticker, position: position)
}
public func deleteStickerFromStickerSet(sticker: FileMediaReference) -> Signal<Never, DeleteStickerError> {
return _internal_deleteStickerFromStickerSet(account: self.account, sticker: sticker)
}
public func getMyStickerSets() -> Signal<[(StickerPackCollectionInfo, StickerPackItem?)], NoError> {
return _internal_getMyStickerSets(account: self.account)
}
public func getStickerSetShortNameSuggestion(title: String) -> Signal<String?, NoError> {
return _internal_getStickerSetShortNameSuggestion(account: self.account, title: title)
}