iOS/WAStickersThirdParty/StickerPackManager.swift (118 lines of code) (raw):

// // Copyright (c) WhatsApp Inc. and its affiliates. // All rights reserved. // // This source code is licensed under the BSD-style license found in the // LICENSE file in the root directory of this source tree. // import UIKit extension Dictionary { func bytesSize() -> Int { let data: NSMutableData = NSMutableData() let encoder: NSKeyedArchiver = NSKeyedArchiver(forWritingWith: data) encoder.encode(self, forKey: "dictionary") encoder.finishEncoding() return data.length } } class StickerPackManager { static let queue: DispatchQueue = DispatchQueue(label: "stickerPackQueue") static func stickersJSON(contentsOfFile filename: String) throws -> [String: Any] { if let path = Bundle.main.path(forResource: filename, ofType: "wasticker") { let data: Data = try Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped) return try JSONSerialization.jsonObject(with: data) as! [String: Any] } throw StickerPackError.fileNotFound } /** * Retrieves sticker packs from a JSON dictionary. * If the processing of a certain sticker pack encounters an exception (see methods in StickerPack.swift), * that sticker pack won't be returned along with the rest (eg if identifer isn't unique or stickers have * invalid image dimensions) * * - Parameter dict: JSON dictionary * - Parameter completionHandler: called on the main queue */ static func fetchStickerPacks(fromJSON dict: [String: Any], completionHandler: @escaping ([StickerPack]) -> Void) { queue.async { let packs: [[String: Any]] = dict["sticker_packs"] as! [[String: Any]] var stickerPacks: [StickerPack] = [] var currentIdentifiers: [String: Bool] = [:] let iosAppStoreLink: String? = dict["ios_app_store_link"] as? String let androidAppStoreLink: String? = dict["android_play_store_link"] as? String Interoperability.iOSAppStoreLink = iosAppStoreLink != "" ? iosAppStoreLink : nil Interoperability.AndroidStoreLink = androidAppStoreLink != "" ? androidAppStoreLink : nil for pack in packs { let packName: String = pack["name"] as! String let packPublisher: String = pack["publisher"] as! String let packTrayImageFileName: String = pack["tray_image_file"] as! String var packPublisherWebsite: String? = pack["publisher_website"] as? String var packPrivacyPolicyWebsite: String? = pack["privacy_policy_website"] as? String var packLicenseAgreementWebsite: String? = pack["license_agreement_website"] as? String // If the strings are empty, consider them as nil packPublisherWebsite = packPublisherWebsite != "" ? packPublisherWebsite : nil packPrivacyPolicyWebsite = packPrivacyPolicyWebsite != "" ? packPrivacyPolicyWebsite : nil packLicenseAgreementWebsite = packLicenseAgreementWebsite != "" ? packLicenseAgreementWebsite : nil // Pack identifier has to be a valid string and be unique let packIdentifier: String? = pack["identifier"] as? String if packIdentifier != nil && currentIdentifiers[packIdentifier!] == nil { currentIdentifiers[packIdentifier!] = true } else { if let packIdentifier = packIdentifier { fatalError("Missing identifier or a sticker pack already has the identifier \(packIdentifier).") } fatalError("\(packName) must have an identifier and it must be unique.") } let animatedStickerPack: Bool? = pack["animated_sticker_pack"] as? Bool var stickerPack: StickerPack? do { stickerPack = try StickerPack(identifier: packIdentifier!, name: packName, publisher: packPublisher, trayImageFileName: packTrayImageFileName, animatedStickerPack: animatedStickerPack, publisherWebsite: packPublisherWebsite, privacyPolicyWebsite: packPrivacyPolicyWebsite, licenseAgreementWebsite: packLicenseAgreementWebsite) } catch StickerPackError.fileNotFound { fatalError("\(packTrayImageFileName) not found.") } catch StickerPackError.emptyString { fatalError("The name, identifier, and publisher strings can't be empty.") } catch StickerPackError.unsupportedImageFormat(let imageFormat) { fatalError("\(packTrayImageFileName): \(imageFormat) is not a supported format.") } catch StickerPackError.invalidImage { fatalError("Tray image file size is 0 KB.") } catch StickerPackError.imageTooBig(let imageFileSize, _) { let roundedSize = round((Double(imageFileSize) / 1024) * 100) / 100; fatalError("\(packTrayImageFileName): \(roundedSize) KB is bigger than the max tray image file size (\(Limits.MaxTrayImageFileSize / 1024) KB).") } catch StickerPackError.incorrectImageSize(let imageDimensions) { fatalError("\(packTrayImageFileName): \(imageDimensions) is not compliant with tray dimensions requirements, \(Limits.TrayImageDimensions).") } catch StickerPackError.animatedImagesNotSupported { fatalError("\(packTrayImageFileName) is an animated image. Animated images are not supported.") } catch StickerPackError.stringTooLong { fatalError("Name, identifier, and publisher of sticker pack must be less than \(Limits.MaxCharLimit128) characters.") } catch { fatalError(error.localizedDescription) } let stickers: [[String: Any]] = pack["stickers"] as! [[String: Any]] for sticker in stickers { let emojis: [String]? = sticker["emojis"] as? [String] let filename = sticker["image_file"] as! String do { try stickerPack!.addSticker(contentsOfFile: filename, emojis: emojis) } catch StickerPackError.stickersNumOutsideAllowableRange { fatalError("Sticker count outside the allowable limit (\(Limits.MaxStickersPerPack) stickers per pack).") } catch StickerPackError.fileNotFound { fatalError("\(filename) not found.") } catch StickerPackError.unsupportedImageFormat(let imageFormat) { fatalError("\(filename): \(imageFormat) is not a supported format.") } catch StickerPackError.invalidImage { fatalError("Image file size is 0 KB.") } catch StickerPackError.imageTooBig(let imageFileSize, let animated) { let roundedSize = round((Double(imageFileSize) / 1024) * 100) / 100; let maxSize = animated ? Limits.MaxAnimatedStickerFileSize : Limits.MaxStaticStickerFileSize fatalError("\(filename): \(roundedSize) KB is bigger than the max file size (\(maxSize / 1024) KB).") } catch StickerPackError.incorrectImageSize(let imageDimensions) { fatalError("\(filename): \(imageDimensions) is not compliant with sticker images dimensions, \(Limits.ImageDimensions).") } catch StickerPackError.tooManyEmojis { fatalError("\(filename) has too many emojis. \(Limits.MaxEmojisCount) is the maximum number.") } catch StickerPackError.minFrameDurationTooShort(let minFrameDuration) { let roundedDuration = round(minFrameDuration) fatalError("\(filename): \(roundedDuration) ms is shorter than the min frame duration (\(Limits.MinAnimatedStickerFrameDurationMS) ms).") } catch StickerPackError.totalAnimationDurationTooLong(let totalFrameDuration) { let roundedDuration = round(totalFrameDuration) fatalError("\(filename): \(roundedDuration) ms is longer than the max total animation duration (\(Limits.MaxAnimatedStickerTotalDurationMS) ms).") } catch StickerPackError.animatedStickerPackWithStaticStickers { fatalError("Animated sticker pack contains static stickers.") } catch StickerPackError.staticStickerPackWithAnimatedStickers { fatalError("Static sticker pack contains animated stickers.") } catch { fatalError(error.localizedDescription) } } if stickers.count < Limits.MinStickersPerPack { fatalError("Sticker count smaller that the allowable limit (\(Limits.MinStickersPerPack) stickers per pack).") } stickerPacks.append(stickerPack!) } DispatchQueue.main.async { completionHandler(stickerPacks) } } } }