Emoji picker: Handle Emoji parsing with EmojiService.

This commit is contained in:
SBiOSoftWhare
2019-07-25 16:32:24 +02:00
parent 289ef99f98
commit 555eafc1fb
10 changed files with 380 additions and 0 deletions
@@ -0,0 +1,70 @@
/*
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
}
final class EmojiService: EmojiServiceType {
// MARK: - Constants
private static let jsonFilename = "apple_emojis_data"
// MARK: - Properties
private let serializationService: SerializationServiceType = SerializationService()
private let serviceQueue = DispatchQueue(label: "\(type(of: EmojiService.self))")
// MARK: - Public
func getEmojiCategories(completion: @escaping (MXResponse<[EmojiCategory]>) -> Void) {
self.serviceQueue.async {
do {
let emojiJSONData = try self.getEmojisJSONData()
let emojiJSONStore: EmojiJSONStore = 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: EmojiService.jsonFilename, withExtension: "json") else {
throw EmojiServiceError.emojiJSONFileNotFound
}
let jsonData = try Data(contentsOf: jsonDataURL)
return jsonData
}
private func emojiCategories(from emojiJSONStore: EmojiJSONStore) -> [EmojiCategory] {
let allEmojiItems = emojiJSONStore.emojis
return emojiJSONStore.categories.map { (jsonCategory) -> EmojiCategory in
let emojiItems = jsonCategory.emojiIdentifiers.compactMap({ (emojiIdentifier) -> EmojiItem? in
return allEmojiItems.first(where: { $0.identifier == emojiIdentifier })
})
return EmojiCategory(identifier: jsonCategory.identifier, emojis: emojiItems)
}
}
}
@@ -0,0 +1,21 @@
/*
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
protocol EmojiServiceType {
func getEmojiCategories(completion: @escaping (MXResponse<[EmojiCategory]>) -> Void)
}
@@ -0,0 +1,74 @@
/*
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 {
enum CodingKeys: String, CodingKey {
case code = "b"
case name = "a"
case shortNames = "n"
case keywords = "j"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard let identifier = decoder.codingPath.last?.stringValue else {
throw DecodingError.dataCorruptedError(forKey: .code, in: container, debugDescription: "Cannot initialize identifier")
}
let emojiUnicodeStringValue = try container.decode(String.self, forKey: .code)
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: .code, 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(identifier: identifier,
value: emoji,
name: name,
shortNames: shortNames,
keywords: keywords)
}
}
@@ -0,0 +1,33 @@
/*
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 EmojiJSONCategory {
let identifier: String
let name: String
let emojiIdentifiers: [String]
}
// MARK: - Decodable
extension EmojiJSONCategory: Decodable {
enum CodingKeys: String, CodingKey {
case identifier = "id"
case name
case emojiIdentifiers = "emojis"
}
}
@@ -0,0 +1,65 @@
/*
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 EmojiJSONStore {
let categories: [EmojiJSONCategory]
let emojis: [EmojiItem]
}
// MARK: - Decodable
extension EmojiJSONStore: Decodable {
enum CodingKeys: String, CodingKey {
case categories
case emojis
}
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(error)
emojiItem = nil
}
return emojiItem
}
let categories = try container.decode([EmojiJSONCategory].self, forKey: .categories)
self.init(categories: categories, emojis: emojis)
}
}
@@ -0,0 +1,27 @@
/*
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 EmojiCategory {
let identifier: String
let emojis: [EmojiItem]
var name: String {
let categoryNameLocalizationKey = "emoji_picker_\(self.identifier)_category"
return VectorL10n.tr("Vector", categoryNameLocalizationKey)
}
}
@@ -0,0 +1,41 @@
/*
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 EmojiItem {
let identifier: String
let value: String
let name: String
let shortNames: [String]
let keywords: [String]
let variations: [EmojiItem]
init(identifier: String,
value: String,
name: String,
shortNames: [String] = [],
keywords: [String] = [],
variations: [EmojiItem] = []) {
self.identifier = identifier
self.value = value
self.name = name
self.shortNames = shortNames
self.keywords = keywords
self.variations = variations
}
}