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

123 lines
4.3 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 (c) 2022 Open Whisper Systems. 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]?
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)
}
}
var rawValue: String {
if let skinTones = skinTones {
return baseEmoji.emojiPerSkinTonePermutation?[skinTones] ?? baseEmoji.rawValue
} else {
return baseEmoji.rawValue
}
}
var normalized: EmojiWithSkinTones {
switch (baseEmoji, skinTones) {
case (let base, nil) where base.normalized != base:
return EmojiWithSkinTones(baseEmoji: base.normalized)
default:
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 = ["🙈", "🙉", "🙊", "😈", "🥸", "🐀"]
.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 }
self = emojiWithSkinTonePermutation.baseEmoji
}
}
// 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
}
}