mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-01 12:17:53 +00:00
Emoji Keywords
This commit is contained in:
parent
e2de09dc70
commit
27b25d20b5
@ -16,6 +16,7 @@
|
||||
0962E66F21B6147600245FD9 /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66E21B6147600245FD9 /* AppConfiguration.swift */; };
|
||||
0962E67521B6437600245FD9 /* SplitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E67421B6437600245FD9 /* SplitTest.swift */; };
|
||||
0962E68121BAA20E00245FD9 /* SearchBotsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E68021BAA20E00245FD9 /* SearchBotsConfiguration.swift */; };
|
||||
09E4A80F223F1FBF0038140F /* EmojiKeywords.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E4A80E223F1FBF0038140F /* EmojiKeywords.swift */; };
|
||||
09EDAD382213120C0012A50B /* AutodownloadSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD372213120C0012A50B /* AutodownloadSettings.swift */; };
|
||||
09EDAD3A22131D010012A50B /* ManagedAutodownloadSettingsUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09EDAD3922131D010012A50B /* ManagedAutodownloadSettingsUpdates.swift */; };
|
||||
9F06831021A40DEC001D8EDB /* NotificationExceptionsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830F21A40DEC001D8EDB /* NotificationExceptionsList.swift */; };
|
||||
@ -816,6 +817,7 @@
|
||||
0962E66E21B6147600245FD9 /* AppConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfiguration.swift; sourceTree = "<group>"; };
|
||||
0962E67421B6437600245FD9 /* SplitTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitTest.swift; sourceTree = "<group>"; };
|
||||
0962E68021BAA20E00245FD9 /* SearchBotsConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchBotsConfiguration.swift; sourceTree = "<group>"; };
|
||||
09E4A80E223F1FBF0038140F /* EmojiKeywords.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiKeywords.swift; sourceTree = "<group>"; };
|
||||
09EDAD372213120C0012A50B /* AutodownloadSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutodownloadSettings.swift; sourceTree = "<group>"; };
|
||||
09EDAD3922131D010012A50B /* ManagedAutodownloadSettingsUpdates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedAutodownloadSettingsUpdates.swift; sourceTree = "<group>"; };
|
||||
9F06830F21A40DEC001D8EDB /* NotificationExceptionsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationExceptionsList.swift; sourceTree = "<group>"; };
|
||||
@ -1764,6 +1766,7 @@
|
||||
D08CAA831ED8164B0000FDA8 /* Localization.swift */,
|
||||
D08CAA861ED81DD40000FDA8 /* LocalizationInfo.swift */,
|
||||
D05D8B362192F8AF0064586F /* LocalizationListState.swift */,
|
||||
09E4A80E223F1FBF0038140F /* EmojiKeywords.swift */,
|
||||
);
|
||||
name = Localization;
|
||||
sourceTree = "<group>";
|
||||
@ -2320,6 +2323,7 @@
|
||||
D00D34421E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift in Sources */,
|
||||
D08984FB2118816A00918162 /* Reachability.m in Sources */,
|
||||
D0DA1D321F7043D50034E892 /* ManagedPendingPeerNotificationSettings.swift in Sources */,
|
||||
09E4A80F223F1FBF0038140F /* EmojiKeywords.swift in Sources */,
|
||||
D099D7491EEF418D00A3128C /* HistoryViewChannelStateValidation.swift in Sources */,
|
||||
C23BC3871E9BE3CA00D79F92 /* ImportContact.swift in Sources */,
|
||||
D00422D321677F4500719B67 /* ManagedAccountPresence.swift in Sources */,
|
||||
|
||||
@ -132,6 +132,7 @@ private var declaredEncodables: Void = {
|
||||
declareEncodable(TelegramMediaPoll.self, f: { TelegramMediaPoll(decoder: $0) })
|
||||
declareEncodable(TelegramMediaUnsupported.self, f: { TelegramMediaUnsupported(decoder: $0) })
|
||||
declareEncodable(ContactsSettings.self, f: { ContactsSettings(decoder: $0) })
|
||||
declareEncodable(EmojiKeywordsMap.self, f: { EmojiKeywordsMap(decoder: $0) })
|
||||
|
||||
return
|
||||
}()
|
||||
|
||||
357
TelegramCore/EmojiKeywords.swift
Normal file
357
TelegramCore/EmojiKeywords.swift
Normal file
@ -0,0 +1,357 @@
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public enum EmojiKeyword: Equatable {
|
||||
case keyword(String, [String])
|
||||
case keywordSubtrahend(String, [String])
|
||||
|
||||
var name: String {
|
||||
switch self {
|
||||
case let .keyword(name, _), let .keywordSubtrahend(name, _):
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
func union(_ emojiKeyword: EmojiKeyword) -> EmojiKeyword {
|
||||
if case let .keyword(name, lhsEmoticons) = self, name == emojiKeyword.name {
|
||||
switch emojiKeyword {
|
||||
case let .keyword(_, rhsEmoticons):
|
||||
var existingEmoticons = Set(lhsEmoticons)
|
||||
var updatedEmoticons = lhsEmoticons
|
||||
for emoticon in rhsEmoticons {
|
||||
if !existingEmoticons.contains(emoticon) {
|
||||
existingEmoticons.insert(emoticon)
|
||||
updatedEmoticons.append(emoticon)
|
||||
}
|
||||
}
|
||||
return .keyword(name, updatedEmoticons)
|
||||
case let .keywordSubtrahend(_, rhsEmoticons):
|
||||
let substractedEmoticons = Set(rhsEmoticons)
|
||||
let updatedEmoticons = lhsEmoticons.filter { !substractedEmoticons.contains($0) }
|
||||
return .keyword(name, updatedEmoticons)
|
||||
}
|
||||
} else {
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func writeString(_ buffer: WriteBuffer, _ string: String) {
|
||||
if let data = string.data(using: .utf8) {
|
||||
var length: Int32 = Int32(data.count)
|
||||
buffer.write(&length, offset: 0, length: 4)
|
||||
buffer.write(data)
|
||||
} else {
|
||||
var length: Int32 = 0
|
||||
buffer.write(&length, offset: 0, length: 4)
|
||||
}
|
||||
}
|
||||
|
||||
private func writeStringArray(_ buffer: WriteBuffer, _ array: [String]) {
|
||||
var length = Int32(array.count)
|
||||
buffer.write(&length, offset: 0, length: 4)
|
||||
for string in array {
|
||||
writeString(buffer, string)
|
||||
}
|
||||
}
|
||||
|
||||
public final class EmojiKeywords: PostboxCoding, Equatable {
|
||||
public let languageCode: String
|
||||
public let inputLanguageCode: String
|
||||
public let version: Int32
|
||||
public let timestamp: Int32
|
||||
public let entries: [String: EmojiKeyword]
|
||||
|
||||
public init(languageCode: String, inputLanguageCode: String, version: Int32, timestamp: Int32, entries: [String: EmojiKeyword]) {
|
||||
self.languageCode = languageCode
|
||||
self.inputLanguageCode = inputLanguageCode
|
||||
self.version = version
|
||||
self.timestamp = timestamp
|
||||
self.entries = entries
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.languageCode = decoder.decodeStringForKey("l", orElse: "")
|
||||
self.inputLanguageCode = decoder.decodeStringForKey("i", orElse: "")
|
||||
self.version = decoder.decodeInt32ForKey("v", orElse: 0)
|
||||
self.timestamp = decoder.decodeInt32ForKey("t", orElse: 0)
|
||||
|
||||
let count = decoder.decodeInt32ForKey("c", orElse: 0)
|
||||
var entries: [String: EmojiKeyword] = [:]
|
||||
if let data = decoder.decodeBytesForKey("d") {
|
||||
for _ in 0 ..< count {
|
||||
var length: Int32 = 0
|
||||
data.read(&length, offset: 0, length: 4)
|
||||
|
||||
let nameData = Data(bytes: data.memory.advanced(by: data.offset), count: Int(length))
|
||||
let name = String(data: nameData, encoding: .utf8)
|
||||
data.skip(Int(length))
|
||||
|
||||
var emoticonsCount: Int32 = 0
|
||||
data.read(&emoticonsCount, offset: 0, length: 4)
|
||||
|
||||
var emoticons: [String] = []
|
||||
for _ in 0 ..< emoticonsCount {
|
||||
var length: Int32 = 0
|
||||
data.read(&length, offset: 0, length: 4)
|
||||
|
||||
let emoticonData = Data(bytes: data.memory.advanced(by: data.offset), count: Int(length))
|
||||
let emoticon = String(data: emoticonData, encoding: .utf8)
|
||||
data.skip(Int(length))
|
||||
|
||||
if let emoticon = emoticon {
|
||||
emoticons.append(emoticon)
|
||||
}
|
||||
}
|
||||
|
||||
if let name = name {
|
||||
entries[name] = .keyword(name, emoticons)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.entries = entries
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeString(self.languageCode, forKey: "l")
|
||||
encoder.encodeString(self.inputLanguageCode, forKey: "i")
|
||||
encoder.encodeInt32(self.version, forKey: "v")
|
||||
encoder.encodeInt32(self.timestamp, forKey: "t")
|
||||
|
||||
encoder.encodeInt32(Int32(self.entries.count), forKey: "c")
|
||||
|
||||
let buffer = WriteBuffer()
|
||||
for case let .keyword(name, emoticons) in self.entries.values {
|
||||
writeString(buffer, name)
|
||||
writeStringArray(buffer, emoticons)
|
||||
}
|
||||
encoder.encodeBytes(buffer, forKey: "d")
|
||||
}
|
||||
|
||||
public static func ==(lhs: EmojiKeywords, rhs: EmojiKeywords) -> Bool {
|
||||
if lhs === rhs {
|
||||
return true
|
||||
}
|
||||
|
||||
if lhs.languageCode == rhs.languageCode && lhs.inputLanguageCode == rhs.inputLanguageCode && lhs.entries == rhs.entries {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
extension EmojiKeyword {
|
||||
init(apiEmojiKeyword: Api.EmojiKeyword) {
|
||||
switch apiEmojiKeyword {
|
||||
case let .emojiKeyword(keyword, emoticons):
|
||||
self = .keyword(keyword, emoticons)
|
||||
case let .emojiKeywordDeleted(keyword, emoticons):
|
||||
self = .keywordSubtrahend(keyword, emoticons)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class EmojiKeywordsMap: PreferencesEntry, Equatable {
|
||||
public let entries: [String: EmojiKeywords]
|
||||
|
||||
public static var defaultValue: EmojiKeywordsMap {
|
||||
return EmojiKeywordsMap(entries: [:])
|
||||
}
|
||||
|
||||
public init(entries: [String: EmojiKeywords]) {
|
||||
self.entries = entries
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.entries = decoder.decodeObjectDictionaryForKey("entries", keyDecoder: { decoder in
|
||||
return decoder.decodeStringForKey("k", orElse: "")
|
||||
}, valueDecoder: { decoder in
|
||||
return EmojiKeywords(decoder: decoder)
|
||||
})
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeObjectDictionary(self.entries, forKey: "entries", keyEncoder: { key, encoder in
|
||||
encoder.encodeString(key, forKey: "k")
|
||||
})
|
||||
}
|
||||
|
||||
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||
if let to = to as? EmojiKeywordsMap {
|
||||
return self == to
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: EmojiKeywordsMap, rhs: EmojiKeywordsMap) -> Bool {
|
||||
return lhs.entries == rhs.entries
|
||||
}
|
||||
}
|
||||
|
||||
private func updateEmojiKeywordsList(accountManager: AccountManager, _ f: @escaping (EmojiKeywordsMap) -> EmojiKeywordsMap) -> Void {
|
||||
let _ = accountManager.transaction({ transaction -> Void in
|
||||
transaction.updateSharedData(SharedDataKeys.emojiKeywords, { entry in
|
||||
let current: EmojiKeywordsMap
|
||||
if let entry = entry as? EmojiKeywordsMap {
|
||||
current = entry
|
||||
} else {
|
||||
current = .defaultValue
|
||||
}
|
||||
return f(current)
|
||||
})
|
||||
}).start()
|
||||
}
|
||||
|
||||
private let refreshTimeout: Int32 = 60 * 60
|
||||
|
||||
public enum DownloadEmojiKeywordsError {
|
||||
case generic
|
||||
case invalidLanguageCode
|
||||
}
|
||||
|
||||
private func downloadEmojiKeywords(network: Network, inputLanguageCode: String) -> Signal<EmojiKeywords, DownloadEmojiKeywordsError> {
|
||||
return network.request(Api.functions.messages.getEmojiKeywords(langCode: inputLanguageCode))
|
||||
|> mapError { _ -> DownloadEmojiKeywordsError in
|
||||
return .generic
|
||||
}
|
||||
|> map { result -> EmojiKeywords in
|
||||
switch result {
|
||||
case let .emojiKeywordsDifference(langCode, _, version, keywords):
|
||||
var entries: [String: EmojiKeyword] = [:]
|
||||
for apiEmojiKeyword in keywords {
|
||||
let emojiKeyword = EmojiKeyword(apiEmojiKeyword: apiEmojiKeyword)
|
||||
entries[emojiKeyword.name] = emojiKeyword
|
||||
}
|
||||
return EmojiKeywords(languageCode: langCode, inputLanguageCode: inputLanguageCode, version: version, timestamp: Int32(CFAbsoluteTimeGetCurrent()), entries: entries)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func downloadEmojiKeywordsDifference(network: Network, languageCode: String, inputLanguageCode: String, fromVersion: Int32) -> Signal<EmojiKeywords, DownloadEmojiKeywordsError> {
|
||||
return network.request(Api.functions.messages.getEmojiKeywordsDifference(langCode: languageCode, fromVersion: fromVersion))
|
||||
|> mapError { _ -> DownloadEmojiKeywordsError in
|
||||
return .generic
|
||||
}
|
||||
|> mapToSignal { result -> Signal<EmojiKeywords, DownloadEmojiKeywordsError> in
|
||||
switch result {
|
||||
case let .emojiKeywordsDifference(langCode, _, version, keywords):
|
||||
if langCode == languageCode {
|
||||
var entries: [String: EmojiKeyword] = [:]
|
||||
for apiEmojiKeyword in keywords {
|
||||
let emojiKeyword = EmojiKeyword(apiEmojiKeyword: apiEmojiKeyword)
|
||||
entries[emojiKeyword.name] = emojiKeyword
|
||||
}
|
||||
return .single(EmojiKeywords(languageCode: langCode, inputLanguageCode: inputLanguageCode, version: version, timestamp: Int32(CFAbsoluteTimeGetCurrent()), entries: entries))
|
||||
} else {
|
||||
return .fail(.invalidLanguageCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func emojiKeywords(accountManager: AccountManager, network: Network, inputLanguageCode: String) -> Signal<EmojiKeywords?, NoError> {
|
||||
return accountManager.sharedData(keys: [SharedDataKeys.emojiKeywords])
|
||||
|> take(1)
|
||||
|> map { sharedData in
|
||||
return sharedData.entries[SharedDataKeys.emojiKeywords] as? EmojiKeywordsMap ?? .defaultValue
|
||||
}
|
||||
|> mapToSignal { keywordsMap -> Signal<EmojiKeywords?, NoError> in
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent())
|
||||
|
||||
let downloadEmojiKeywordsSignal: Signal<EmojiKeywords?, NoError> = downloadEmojiKeywords(network: network, inputLanguageCode: inputLanguageCode)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<EmojiKeywords?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { keywords -> Signal<EmojiKeywords?, NoError> in
|
||||
if let keywords = keywords {
|
||||
updateEmojiKeywordsList(accountManager: accountManager, { keywordsMap -> EmojiKeywordsMap in
|
||||
var entries = keywordsMap.entries
|
||||
entries[inputLanguageCode] = keywords
|
||||
return EmojiKeywordsMap(entries: entries)
|
||||
})
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
|
||||
if let emojiKeywords = keywordsMap.entries[inputLanguageCode] {
|
||||
if emojiKeywords.timestamp + refreshTimeout > timestamp {
|
||||
return .single(emojiKeywords)
|
||||
} else {
|
||||
return downloadEmojiKeywordsDifference(network: network, languageCode: emojiKeywords.languageCode, inputLanguageCode: inputLanguageCode, fromVersion: emojiKeywords.version)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<EmojiKeywords?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { differenceKeywords -> Signal<EmojiKeywords?, NoError> in
|
||||
if let differenceKeywords = differenceKeywords {
|
||||
var updatedKeywords = emojiKeywords
|
||||
var updatedKeywordEntries: [String: EmojiKeyword] = emojiKeywords.entries
|
||||
for differenceKeywordEntry in differenceKeywords.entries.values {
|
||||
let name = differenceKeywordEntry.name
|
||||
if let existingKeyword = updatedKeywordEntries[name] {
|
||||
updatedKeywordEntries[name] = existingKeyword.union(differenceKeywordEntry)
|
||||
} else if case .keyword = differenceKeywordEntry {
|
||||
updatedKeywordEntries[name] = differenceKeywordEntry
|
||||
}
|
||||
}
|
||||
updatedKeywords = EmojiKeywords(languageCode: differenceKeywords.languageCode, inputLanguageCode: inputLanguageCode, version: differenceKeywords.version, timestamp: Int32(CFAbsoluteTimeGetCurrent()), entries: updatedKeywordEntries)
|
||||
|
||||
updateEmojiKeywordsList(accountManager: accountManager, { keywordsMap -> EmojiKeywordsMap in
|
||||
var entries = keywordsMap.entries
|
||||
entries[inputLanguageCode] = updatedKeywords
|
||||
return EmojiKeywordsMap(entries: entries)
|
||||
})
|
||||
return .complete()
|
||||
} else {
|
||||
return downloadEmojiKeywordsSignal
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return downloadEmojiKeywordsSignal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func searchEmojiKeywords(keywords: EmojiKeywords, query: String, completeMatch: Bool) -> Signal<[String], NoError> {
|
||||
return Signal { subscriber in
|
||||
let query = query.lowercased()
|
||||
|
||||
var existing = Set<String>()
|
||||
var matched: [String] = []
|
||||
if completeMatch {
|
||||
if let keyword = keywords.entries[query], case let .keyword(_, emoticons) = keyword {
|
||||
for emoticon in emoticons {
|
||||
if !existing.contains(emoticon) {
|
||||
existing.insert(emoticon)
|
||||
matched.append(emoticon)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for case let .keyword(name, emoticons) in keywords.entries.values {
|
||||
if name.hasPrefix(query) {
|
||||
for emoticon in emoticons {
|
||||
if !existing.contains(emoticon) {
|
||||
existing.insert(emoticon)
|
||||
matched.append(emoticon)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subscriber.putNext(matched)
|
||||
subscriber.putCompletion()
|
||||
|
||||
return EmptyDisposable
|
||||
} |> runOn(Queue.concurrentDefaultQueue())
|
||||
}
|
||||
@ -452,9 +452,9 @@ extension JSON {
|
||||
var dictionary = dictionary
|
||||
switch value {
|
||||
case let .jsonObjectValue(key, value):
|
||||
if let value = JSON(apiJson: value, root: false) {
|
||||
dictionary[key] = value
|
||||
}
|
||||
if let value = JSON(apiJson: value, root: false) {
|
||||
dictionary[key] = value
|
||||
}
|
||||
}
|
||||
return dictionary
|
||||
})
|
||||
|
||||
@ -252,6 +252,7 @@ private enum SharedDataKeyValues: Int32 {
|
||||
case localizationSettings = 3
|
||||
case proxySettings = 4
|
||||
case autodownloadSettings = 5
|
||||
case emojiKeywords = 6
|
||||
}
|
||||
|
||||
public struct SharedDataKeys {
|
||||
@ -284,6 +285,12 @@ public struct SharedDataKeys {
|
||||
key.setInt32(0, value: SharedDataKeyValues.autodownloadSettings.rawValue)
|
||||
return key
|
||||
}()
|
||||
|
||||
public static let emojiKeywords: ValueBoxKey = {
|
||||
let key = ValueBoxKey(length: 4)
|
||||
key.setInt32(0, value: SharedDataKeyValues.emojiKeywords.rawValue)
|
||||
return key
|
||||
}()
|
||||
}
|
||||
|
||||
public func applicationSpecificItemCacheCollectionId(_ value: Int8) -> Int8 {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user