You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/Session/Emoji/EmojiWithSkinTones.swift

139 lines
4.8 KiB
Swift

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import DifferenceKit
import SessionMessagingKit
public struct EmojiWithSkinTones: Hashable, Equatable, ContentEquatable, ContentIdentifiable {
let baseEmoji: Emoji?
let skinTones: [Emoji.SkinTone]?
let unsupportedValue: String?
init(baseEmoji: Emoji, skinTones: [Emoji.SkinTone]? = nil) {
self.baseEmoji = baseEmoji
// Deduplicate skin tones, while preserving order. This allows for
// multi-skin tone emoji, where if you have for example the permutation
// [.dark, .dark], it is consolidated to just [.dark], to be initialized
// with either variant and result in the correct emoji.
self.skinTones = skinTones?.reduce(into: [Emoji.SkinTone]()) { result, skinTone in
guard !result.contains(skinTone) else { return }
result.append(skinTone)
}
self.unsupportedValue = nil
}
init(unsupportedValue: String) {
self.unsupportedValue = unsupportedValue
self.baseEmoji = nil
self.skinTones = nil
}
var rawValue: String {
if let baseEmoji = baseEmoji {
if let skinTones = skinTones {
return baseEmoji.emojiPerSkinTonePermutation?[skinTones] ?? baseEmoji.rawValue
} else {
return baseEmoji.rawValue
}
}
if let unsupportedValue = unsupportedValue {
return unsupportedValue
}
return "" // Should not happen
}
var normalized: EmojiWithSkinTones {
if let baseEmoji = baseEmoji, baseEmoji.normalized != baseEmoji {
return EmojiWithSkinTones(baseEmoji: baseEmoji.normalized)
}
return self
}
var isNormalized: Bool { self == normalized }
}
extension Emoji {
static func getRecent(_ db: Database, withDefaultEmoji: Bool) throws -> [String] {
let recentReactionEmoji: [String] = (db[.recentReactionEmoji]?
.components(separatedBy: ","))
.defaulting(to: [])
// No need to continue if we don't want the default emoji to pad out the list
guard withDefaultEmoji else { return recentReactionEmoji }
// Add in our default emoji if desired
let defaultEmoji = ["😂", "🥰", "😢", "😡", "😮", "😈"] // stringlint:disable
.filter { !recentReactionEmoji.contains($0) }
return Array(recentReactionEmoji
.appending(contentsOf: defaultEmoji)
.prefix(6))
}
static func addRecent(_ db: Database, emoji: String) {
// Add/move the emoji to the start of the most recent list
db[.recentReactionEmoji] = (db[.recentReactionEmoji]?
.components(separatedBy: ","))
.defaulting(to: [])
.filter { $0 != emoji }
.inserting(emoji, at: 0)
.prefix(6)
.joined(separator: ",")
}
static func allSendableEmojiByCategoryWithPreferredSkinTones(_ db: Database) -> [Category: [EmojiWithSkinTones]] {
return Category.allCases
.reduce(into: [Category: [EmojiWithSkinTones]]()) { result, category in
result[category] = category.normalizedEmoji
.filter { $0.available }
.map { $0.withPreferredSkinTones(db) }
}
}
private func withPreferredSkinTones(_ db: Database) -> EmojiWithSkinTones {
guard let rawSkinTones: String = db[.emojiPreferredSkinTones(emoji: rawValue)] else {
return EmojiWithSkinTones(baseEmoji: self, skinTones: nil)
}
return EmojiWithSkinTones(
baseEmoji: self,
skinTones: rawSkinTones
.split(separator: ",")
.compactMap { SkinTone(rawValue: String($0)) }
)
}
func setPreferredSkinTones(_ db: Database, preferredSkinTonePermutation: [SkinTone]?) {
db[.emojiPreferredSkinTones(emoji: rawValue)] = preferredSkinTonePermutation
.map { preferredSkinTonePermutation in
preferredSkinTonePermutation
.map { $0.rawValue }
.joined(separator: ",")
}
}
init?(_ string: String) {
guard let emojiWithSkinTonePermutation = EmojiWithSkinTones(rawValue: string) else { return nil }
if let baseEmoji = emojiWithSkinTonePermutation.baseEmoji {
self = baseEmoji
} else {
return nil
}
}
}
// MARK: -
extension String {
// This is slightly more accurate than String.isSingleEmoji,
// but slower.
//
// * This will reject "lone modifiers".
// * This will reject certain edge cases such as 🌈.
var isSingleEmojiUsingEmojiWithSkinTones: Bool {
EmojiWithSkinTones(rawValue: self) != nil
}
}