Emoji picker: Handle Manu's comments.

This commit is contained in:
SBiOSoftWhare
2019-07-26 14:06:19 +02:00
parent 4a696887a2
commit dacee398cf
12 changed files with 191 additions and 59 deletions
@@ -0,0 +1,83 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
extension EmojiItem: Decodable {
/// JSON keys associated to EmojiItem properties.
/// See https://github.com/missive/emoji-mart/blob/master/src/utils/data.js for minified letters informations.
///
/// - shortName: The commonly-agreed short name for the emoji, as supported in GitHub and others via the :short_name: syntax.
/// - name: The offical Unicode name.
/// - codepoint: The Unicode codepoint, as 4-5 hex digits. Where an emoji needs 2 or more codepoints, they are specified like 1F1EA-1F1F8.
/// - shortNames: An array of all the other known short names.
/// - keywords: Associated emoji keywords.
enum CodingKeys: String, CodingKey {
case shortName
case name = "a"
case codepoint = "b"
case shortNames = "n"
case keywords = "j"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard let shortName = decoder.codingPath.last?.stringValue else {
throw DecodingError.dataCorruptedError(forKey: .shortName, in: container, debugDescription: "Cannot initialize short name")
}
let emojiUnicodeStringValue = try container.decode(String.self, forKey: .codepoint)
let unicodeStringComponents = emojiUnicodeStringValue.components(separatedBy: "-")
var emoji = ""
for unicodeStringComponent in unicodeStringComponents {
if let unicodeCodePoint = Int(unicodeStringComponent, radix: 16),
let emojiUnicodeScalar = UnicodeScalar(unicodeCodePoint) {
emoji.append(String(emojiUnicodeScalar))
} else {
throw DecodingError.dataCorruptedError(forKey: .codepoint, in: container, debugDescription: "Cannot initialize emoji")
}
}
let name = try container.decode(String.self, forKey: .name)
let shortNames: [String]
if let decodedShortNames = try container.decodeIfPresent([String].self, forKey: .shortNames) {
shortNames = decodedShortNames
} else {
shortNames = []
}
let keywords: [String]
if let decodedKeywords = try container.decodeIfPresent([String].self, forKey: .keywords) {
keywords = decodedKeywords
} else {
keywords = []
}
self.init(shortName: shortName,
value: emoji,
name: name,
shortNames: shortNames,
keywords: keywords)
}
}
@@ -0,0 +1,67 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
struct EmojiMartStore {
let categories: [EmojiJSONCategory]
let emojis: [EmojiItem]
}
// MARK: - Decodable
extension EmojiMartStore: Decodable {
/// JSON keys associated to EmojiJSONStore properties.
enum CodingKeys: String, CodingKey {
case categories
case emojis
}
/// JSON key associated to emoji short name.
struct EmojiKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let emojisContainer = try container.nestedContainer(keyedBy: EmojiKey.self, forKey: .emojis)
let emojis: [EmojiItem] = emojisContainer.allKeys.compactMap { (emojiKey) -> EmojiItem? in
let emojiItem: EmojiItem?
do {
emojiItem = try emojisContainer.decode(EmojiItem.self, forKey: emojiKey)
} catch {
print("[EmojiJSONStore] init(from decoder: Decoder) failed to parse emojiItem \(emojiKey) with error: \(error)")
emojiItem = nil
}
return emojiItem
}
let categories = try container.decode([EmojiJSONCategory].self, forKey: .categories)
self.init(categories: categories, emojis: emojis)
}
}
@@ -0,0 +1,40 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
struct EmojiMartCategory {
/// Emoji category identifier (e.g. "people")
let identifier: String
/// Emoji category name in english (e.g. "Smiley & People")
let name: String
/// List of emoji short names associated to the category (e.g. "people")
let emojiShortNames: [String]
}
// MARK: - Decodable
extension EmojiMartCategory: Decodable {
/// JSON keys associated to EmojiJSONCategory properties.
enum CodingKeys: String, CodingKey {
case identifier = "id"
case name
case emojiShortNames = "emojis"
}
}
@@ -0,0 +1,72 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
enum EmojiServiceError: Error {
case emojiJSONFileNotFound
}
/// Emoji service powered by Emoji Mart data (https://github.com/missive/emoji-mart/)
final class EmojiMartService: EmojiServiceType {
// MARK: - Constants
/// Emoji data coming from https://github.com/missive/emoji-mart/blob/master/data/apple.json
private static let jsonFilename = "apple_emojis_data"
// MARK: - Properties
private let serializationService: SerializationServiceType = SerializationService()
private let serviceQueue = DispatchQueue(label: "\(type(of: EmojiMartService.self))")
// MARK: - Public
func getEmojiCategories(completion: @escaping (MXResponse<[EmojiCategory]>) -> Void) {
self.serviceQueue.async {
do {
let emojiJSONData = try self.getEmojisJSONData()
let emojiJSONStore: EmojiMartStore = try self.serializationService.deserialize(emojiJSONData)
let emojiCategories = self.emojiCategories(from: emojiJSONStore)
completion(MXResponse.success(emojiCategories))
} catch {
completion(MXResponse.failure(error))
}
}
}
// MARK: - Private
private func getEmojisJSONData() throws -> Data {
guard let jsonDataURL = Bundle.main.url(forResource: EmojiMartService.jsonFilename, withExtension: "json") else {
throw EmojiServiceError.emojiJSONFileNotFound
}
let jsonData = try Data(contentsOf: jsonDataURL)
return jsonData
}
private func emojiCategories(from emojiJSONStore: EmojiMartStore) -> [EmojiCategory] {
let allEmojiItems = emojiJSONStore.emojis
return emojiJSONStore.categories.map { (jsonCategory) -> EmojiCategory in
let emojiItems = jsonCategory.emojiShortNames.compactMap({ (shortName) -> EmojiItem? in
return allEmojiItems.first(where: { $0.shortName == shortName })
})
return EmojiCategory(identifier: jsonCategory.identifier, emojis: emojiItems)
}
}
}
@@ -0,0 +1,67 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
struct EmojiMartStore {
let categories: [EmojiMartCategory]
let emojis: [EmojiItem]
}
// MARK: - Decodable
extension EmojiMartStore: Decodable {
/// JSON keys associated to EmojiMartStore properties.
enum CodingKeys: String, CodingKey {
case categories
case emojis
}
/// JSON key associated to emoji short name.
struct EmojiKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let emojisContainer = try container.nestedContainer(keyedBy: EmojiKey.self, forKey: .emojis)
let emojis: [EmojiItem] = emojisContainer.allKeys.compactMap { (emojiKey) -> EmojiItem? in
let emojiItem: EmojiItem?
do {
emojiItem = try emojisContainer.decode(EmojiItem.self, forKey: emojiKey)
} catch {
print("[EmojiJSONStore] init(from decoder: Decoder) failed to parse emojiItem \(emojiKey) with error: \(error)")
emojiItem = nil
}
return emojiItem
}
let categories = try container.decode([EmojiMartCategory].self, forKey: .categories)
self.init(categories: categories, emojis: emojis)
}
}