WinterGram/submodules/TelegramUIPreferences/Sources/WinterGramBadgeManifest.swift

220 lines
8.1 KiB
Swift

import Foundation
public enum WinterGramBadgeLimits {
public static let maxBadges = 64
public static let maxLayers = 16
public static let maxAssetBytes = 512 * 1024
}
public enum WinterGramBadgeTint: Equatable {
case theme
case hex(UInt32)
case original
public init(parsing raw: String?) {
guard var s = raw?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased(), !s.isEmpty else {
self = .theme
return
}
if s == "theme" {
self = .theme
} else if s == "none" || s == "original" {
self = .original
} else {
if s.hasPrefix("#") {
s.removeFirst()
}
if s.count == 6, let value = UInt32(s, radix: 16) {
self = .hex(value)
} else {
self = .theme
}
}
}
}
public enum WinterGramBadgeAnimationType: String, Equatable {
case none
case rotate
case blink
case pulse
case bounce
case shake
case lottie
public init(parsing raw: String?) {
guard let raw = raw?.trimmingCharacters(in: .whitespacesAndNewlines).lowercased(), let value = WinterGramBadgeAnimationType(rawValue: raw) else {
self = .none
return
}
self = value
}
}
public struct WinterGramBadgeAnimation: Equatable, Decodable {
public let type: WinterGramBadgeAnimationType
public let duration: Double
public let loop: Bool
public let directionClockwise: Bool
public let amplitude: Double
public static let none = WinterGramBadgeAnimation(type: .none, duration: 0.0, loop: false, directionClockwise: true, amplitude: 0.0)
public init(type: WinterGramBadgeAnimationType, duration: Double, loop: Bool, directionClockwise: Bool, amplitude: Double) {
self.type = type
self.duration = duration
self.loop = loop
self.directionClockwise = directionClockwise
self.amplitude = amplitude
}
private enum CodingKeys: String, CodingKey {
case type, duration, loop, direction, amplitude
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.type = WinterGramBadgeAnimationType(parsing: try container.decodeIfPresent(String.self, forKey: .type))
self.duration = (try container.decodeIfPresent(Double.self, forKey: .duration)) ?? 1.0
self.loop = (try container.decodeIfPresent(Bool.self, forKey: .loop)) ?? true
let direction = (try container.decodeIfPresent(String.self, forKey: .direction))?.lowercased()
self.directionClockwise = (direction != "ccw")
self.amplitude = (try container.decodeIfPresent(Double.self, forKey: .amplitude)) ?? 0.1
}
}
public struct WinterGramBadgeLayer: Equatable, Decodable {
public let name: String
public let source: String
public let x: Double
public let y: Double
public let width: Double
public let height: Double
public let tint: WinterGramBadgeTint
public let animation: WinterGramBadgeAnimation
public var isLottie: Bool {
let lowered = self.source.lowercased()
return lowered.hasSuffix(".tgs") || lowered.hasSuffix(".json")
}
public init(name: String, source: String, x: Double, y: Double, width: Double, height: Double, tint: WinterGramBadgeTint, animation: WinterGramBadgeAnimation) {
self.name = name
self.source = source
self.x = x
self.y = y
self.width = width
self.height = height
self.tint = tint
self.animation = animation
}
private enum CodingKeys: String, CodingKey {
case name, source, x, y, width, height, tint, animation
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = (try container.decodeIfPresent(String.self, forKey: .name)) ?? ""
self.source = (try container.decodeIfPresent(String.self, forKey: .source)) ?? ""
self.x = (try container.decodeIfPresent(Double.self, forKey: .x)) ?? 0.0
self.y = (try container.decodeIfPresent(Double.self, forKey: .y)) ?? 0.0
self.width = (try container.decodeIfPresent(Double.self, forKey: .width)) ?? 0.0
self.height = (try container.decodeIfPresent(Double.self, forKey: .height)) ?? 0.0
self.tint = WinterGramBadgeTint(parsing: try container.decodeIfPresent(String.self, forKey: .tint))
self.animation = (try container.decodeIfPresent(WinterGramBadgeAnimation.self, forKey: .animation)) ?? .none
}
}
public struct WinterGramBadgePeers: Equatable, Decodable {
public let users: [Int64]
public let channels: [Int64]
public init(users: [Int64], channels: [Int64]) {
self.users = users
self.channels = channels
}
private enum CodingKeys: String, CodingKey {
case users, channels
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.users = (try container.decodeIfPresent([Int64].self, forKey: .users)) ?? []
self.channels = (try container.decodeIfPresent([Int64].self, forKey: .channels)) ?? []
}
}
public struct WinterGramBadgeDef: Equatable, Decodable {
public let id: String
public let peers: WinterGramBadgePeers
public let priority: Int
public let layers: [WinterGramBadgeLayer]
public let description: String
public init(id: String, peers: WinterGramBadgePeers, priority: Int, layers: [WinterGramBadgeLayer], description: String) {
self.id = id
self.peers = peers
self.priority = priority
self.layers = layers
self.description = description
}
private enum CodingKeys: String, CodingKey {
case id, peers, priority, layers, description
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = (try container.decodeIfPresent(String.self, forKey: .id)) ?? ""
self.peers = (try container.decodeIfPresent(WinterGramBadgePeers.self, forKey: .peers)) ?? WinterGramBadgePeers(users: [], channels: [])
self.priority = (try container.decodeIfPresent(Int.self, forKey: .priority)) ?? 0
let rawLayers = (try container.decodeIfPresent([WinterGramBadgeLayer].self, forKey: .layers)) ?? []
self.layers = Array(rawLayers.prefix(WinterGramBadgeLimits.maxLayers))
self.description = (try container.decodeIfPresent(String.self, forKey: .description)) ?? ""
}
}
public struct WinterGramBadgeManifest: Equatable, Decodable {
public let version: Int
public let canvas: Double
public let badges: [WinterGramBadgeDef]
public init(version: Int, canvas: Double, badges: [WinterGramBadgeDef]) {
self.version = version
self.canvas = canvas
self.badges = badges
}
private enum CodingKeys: String, CodingKey {
case version, canvas, badges
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.version = (try container.decodeIfPresent(Int.self, forKey: .version)) ?? 0
self.canvas = (try container.decodeIfPresent(Double.self, forKey: .canvas)) ?? 1024.0
let rawBadges = (try container.decodeIfPresent([WinterGramBadgeDef].self, forKey: .badges)) ?? []
self.badges = Array(rawBadges.prefix(WinterGramBadgeLimits.maxBadges))
}
public static let empty = WinterGramBadgeManifest(version: 0, canvas: 1024.0, badges: [])
public static func decode(from data: Data) -> WinterGramBadgeManifest? {
return try? JSONDecoder().decode(WinterGramBadgeManifest.self, from: data)
}
public var assetSources: [String] {
var seen = Set<String>()
var result: [String] = []
for badge in self.badges {
for layer in badge.layers where !layer.source.isEmpty {
if seen.insert(layer.source).inserted {
result.append(layer.source)
}
}
}
return result
}
}