Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Avatars/AvatarModel.swift
1 views
//
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation
public import UIKit

public struct AvatarModel: Equatable {
    public let identifier: String
    public var type: AvatarType
    public var theme: AvatarTheme

    public init(identifier: String? = nil, type: AvatarType, theme: AvatarTheme) {
        if let identifier {
            if case .icon(let icon) = type { owsAssertDebug(identifier == icon.rawValue) }
            self.identifier = identifier
        } else {
            switch type {
            case .icon(let icon): self.identifier = icon.rawValue
            default: self.identifier = UUID().uuidString
            }
        }
        self.type = type
        self.theme = theme
    }
}

public enum AvatarType: Equatable {
    case image(URL)
    case icon(AvatarIcon)
    case text(String)

    public var isEditable: Bool {
        switch self {
        case .image: return false
        case .icon: return true
        case .text: return true
        }
    }

    public var isDeletable: Bool {
        switch self {
        case .image: return true
        case .icon: return false
        case .text: return true
        }
    }

    public static func ==(lhs: AvatarType, rhs: AvatarType) -> Bool {
        switch (lhs, rhs) {
        case (.image(let lhs), .image(let rhs)):
            // We implement a custom "Equatable", since two URLs
            // are equatated with object equality, and even with
            // the same URL treated as different objects with the
            // compiler synthesized function.
            return lhs.path == rhs.path
        case (.icon(let lhs), .icon(let rhs)):
            return lhs == rhs
        case (.text(let lhs), .text(let rhs)):
            return lhs == rhs
        default: return false
        }
    }
}

public enum AvatarIcon: String, CaseIterable {
    case abstract01
    case abstract02
    case abstract03
    case cat
    case dinosaur
    case dog
    case fox
    case ghost
    case incognito
    case pig
    case sloth
    case tucan

    case heart
    case house
    case melon
    case drink
    case celebration
    case balloon
    case book
    case briefcase
    case sunset
    case surfboard
    case soccerball
    case football

    public var image: UIImage { UIImage(named: imageName)! }

    public var imageName: String { "avatar_\(rawValue)" }

    public static var defaultGroupIcons: [AvatarIcon] = [
        .heart,
        .house,
        .melon,
        .drink,
        .celebration,
        .balloon,
        .book,
        .briefcase,
        .sunset,
        .surfboard,
        .soccerball,
        .football,
    ]

    public static var defaultProfileIcons: [AvatarIcon] = [
        .abstract01,
        .abstract02,
        .abstract03,
        .cat,
        .dog,
        .fox,
        .tucan,
        .sloth,
        .dinosaur,
        .pig,
        .incognito,
        .ghost,
    ]
}

public enum AvatarTheme: String, CaseIterable {
    case A100
    case A110
    case A120
    case A130
    case A140
    case A150
    case A160
    case A170
    case A180
    case A190
    case A200
    case A210

    public static var `default`: AvatarTheme { .A100 }

    public var foregroundColor: UIColor {
        switch self {
        case .A100: return UIColor(rgbHex: 0x3838F5)
        case .A110: return UIColor(rgbHex: 0x1251D3)
        case .A120: return UIColor(rgbHex: 0x086DA0)
        case .A130: return UIColor(rgbHex: 0x067906)
        case .A140: return UIColor(rgbHex: 0x661AFF)
        case .A150: return UIColor(rgbHex: 0x9F00F0)
        case .A160: return UIColor(rgbHex: 0xB8057C)
        case .A170: return UIColor(rgbHex: 0xBE0404)
        case .A180: return UIColor(rgbHex: 0x836B01)
        case .A190: return UIColor(rgbHex: 0x7D6F40)
        case .A200: return UIColor(rgbHex: 0x4F4F6D)
        case .A210: return UIColor(rgbHex: 0x5C5C5C)
        }
    }

    public var backgroundColor: UIColor {
        switch self {
        case .A100: return UIColor(rgbHex: 0xE3E3FE)
        case .A110: return UIColor(rgbHex: 0xDDE7FC)
        case .A120: return UIColor(rgbHex: 0xD8E8F0)
        case .A130: return UIColor(rgbHex: 0xCDE4CD)
        case .A140: return UIColor(rgbHex: 0xEAE0FD)
        case .A150: return UIColor(rgbHex: 0xF5E3FE)
        case .A160: return UIColor(rgbHex: 0xF6D8EC)
        case .A170: return UIColor(rgbHex: 0xF5D7D7)
        case .A180: return UIColor(rgbHex: 0xFEF5D0)
        case .A190: return UIColor(rgbHex: 0xEAE6D5)
        case .A200: return UIColor(rgbHex: 0xD2D2DC)
        case .A210: return UIColor(rgbHex: 0xD7D7D9)
        }
    }

    public static func forIcon(_ icon: AvatarIcon) -> AvatarTheme {
        switch icon {
        case .abstract01: return .A130
        case .abstract02: return .A120
        case .abstract03: return .A170
        case .cat: return .A190
        case .dog: return .A140
        case .fox: return .A190
        case .tucan: return .A120
        case .sloth: return .A160
        case .dinosaur: return .A130
        case .pig: return .A180
        case .incognito: return .A210
        case .ghost: return .A100
        case .heart: return .A180
        case .house: return .A120
        case .melon: return .A110
        case .drink: return .A170
        case .celebration: return .A100
        case .balloon: return .A210
        case .book: return .A100
        case .briefcase: return .A180
        case .sunset: return .A120
        case .surfboard: return .A110
        case .soccerball: return .A130
        case .football: return .A210
        }
    }
}

// MARK: -

extension AvatarTheme {
    var asStorageServiceProtoAvatarColor: StorageServiceProtoAvatarColor {
        return switch self {
        case .A100: .a100
        case .A110: .a110
        case .A120: .a120
        case .A130: .a130
        case .A140: .a140
        case .A150: .a150
        case .A160: .a160
        case .A170: .a170
        case .A180: .a180
        case .A190: .a190
        case .A200: .a200
        case .A210: .a210
        }
    }

    static func from(storageServiceProtoAvatarColor: StorageServiceProtoAvatarColor) -> AvatarTheme? {
        return switch storageServiceProtoAvatarColor {
        case .a100: .A100
        case .a110: .A110
        case .a120: .A120
        case .a130: .A130
        case .a140: .A140
        case .a150: .A150
        case .a160: .A160
        case .a170: .A170
        case .a180: .A180
        case .a190: .A190
        case .a200: .A200
        case .a210: .A210
        case .UNRECOGNIZED: nil
        }
    }
}

// MARK: - Avatar Gradients

public struct AvatarGradient: Equatable {
    let id: Int
    let topColor: UIColor
    let bottomColor: UIColor

    init(id: Int, topHex: UInt32, bottomHex: UInt32) {
        self.id = id
        self.topColor = UIColor(rgbHex: topHex)
        self.bottomColor = UIColor(rgbHex: bottomHex)
    }

    static let gradients: [AvatarGradient] = [
        AvatarGradient(id: 00, topHex: 0x252568, bottomHex: 0x9C8F8F),
        AvatarGradient(id: 01, topHex: 0x2A4275, bottomHex: 0x9D9EA1),
        AvatarGradient(id: 02, topHex: 0x2E4B5F, bottomHex: 0x8AA9B1),
        AvatarGradient(id: 03, topHex: 0x2E426C, bottomHex: 0x7A9377),
        AvatarGradient(id: 04, topHex: 0x1A341A, bottomHex: 0x807F6E),
        AvatarGradient(id: 05, topHex: 0x464E42, bottomHex: 0xD5C38F),
        AvatarGradient(id: 06, topHex: 0x595643, bottomHex: 0x93A899),
        AvatarGradient(id: 07, topHex: 0x2C2F36, bottomHex: 0x687466),
        AvatarGradient(id: 08, topHex: 0x2B1E18, bottomHex: 0x968980),
        AvatarGradient(id: 09, topHex: 0x7B7067, bottomHex: 0xA5A893),
        AvatarGradient(id: 10, topHex: 0x706359, bottomHex: 0xBDA194),
        AvatarGradient(id: 11, topHex: 0x383331, bottomHex: 0xA48788),
        AvatarGradient(id: 12, topHex: 0x924F4F, bottomHex: 0x897A7A),
        AvatarGradient(id: 13, topHex: 0x663434, bottomHex: 0xC58D77),
        AvatarGradient(id: 14, topHex: 0x8F4B02, bottomHex: 0xAA9274),
        AvatarGradient(id: 15, topHex: 0x784747, bottomHex: 0x8C8F6F),
        AvatarGradient(id: 16, topHex: 0x747474, bottomHex: 0xACACAC),
        AvatarGradient(id: 17, topHex: 0x49484C, bottomHex: 0xA5A6B5),
        AvatarGradient(id: 18, topHex: 0x4A4E4D, bottomHex: 0xABAFAE),
        AvatarGradient(id: 19, topHex: 0x3A3A3A, bottomHex: 0x929887),
    ]
}

// MARK: -

extension AvatarTheme {
    var asBackupProtoAvatarColor: BackupProto_AvatarColor {
        return switch self {
        case .A100: .a100
        case .A110: .a110
        case .A120: .a120
        case .A130: .a130
        case .A140: .a140
        case .A150: .a150
        case .A160: .a160
        case .A170: .a170
        case .A180: .a180
        case .A190: .a190
        case .A200: .a200
        case .A210: .a210
        }
    }

    static func from(backupProtoAvatarColor: BackupProto_AvatarColor) -> AvatarTheme? {
        return switch backupProtoAvatarColor {
        case .a100: .A100
        case .a110: .A110
        case .a120: .A120
        case .a130: .A130
        case .a140: .A140
        case .a150: .A150
        case .a160: .A160
        case .a170: .A170
        case .a180: .A180
        case .a190: .A190
        case .a200: .A200
        case .a210: .A210
        case .UNRECOGNIZED: nil
        }
    }
}