Emoji picker: Handle Emoji parsing with EmojiService.

This commit is contained in:
SBiOSoftWhare 2019-07-25 16:32:24 +02:00
parent 8dca707d82
commit 01b9b483c6
10 changed files with 380 additions and 0 deletions

File diff suppressed because one or more lines are too long

View file

@ -916,3 +916,15 @@
// MARK: File upload
"file_upload_error_title" = "File upload";
"file_upload_error_unsupported_file_type_message" = "File type not supported.";
// MARK: Emoji picker
"emoji_picker_title" = "Reactions";
"emoji_picker_people_category" = "Smileys & People";
"emoji_picker_nature_category" = "Animals & Nature";
"emoji_picker_foods_category" = "Food & Drink";
"emoji_picker_activity_category" = "Activities";
"emoji_picker_places_category" = "Travel & Places";
"emoji_picker_objects_category" = "Objects";
"emoji_picker_symbols_category" = "Symbols";
"emoji_picker_flags_category" = "Flags";

View file

@ -914,6 +914,42 @@ internal enum VectorL10n {
internal static var e2eRoomKeyRequestTitle: String {
return VectorL10n.tr("Vector", "e2e_room_key_request_title")
}
/// Activities
internal static var emojiPickerActivityCategory: String {
return VectorL10n.tr("Vector", "emoji_picker_activity_category")
}
/// Flags
internal static var emojiPickerFlagsCategory: String {
return VectorL10n.tr("Vector", "emoji_picker_flags_category")
}
/// Food & Drink
internal static var emojiPickerFoodsCategory: String {
return VectorL10n.tr("Vector", "emoji_picker_foods_category")
}
/// Animals & Nature
internal static var emojiPickerNatureCategory: String {
return VectorL10n.tr("Vector", "emoji_picker_nature_category")
}
/// Objects
internal static var emojiPickerObjectsCategory: String {
return VectorL10n.tr("Vector", "emoji_picker_objects_category")
}
/// Smileys & People
internal static var emojiPickerPeopleCategory: String {
return VectorL10n.tr("Vector", "emoji_picker_people_category")
}
/// Travel & Places
internal static var emojiPickerPlacesCategory: String {
return VectorL10n.tr("Vector", "emoji_picker_places_category")
}
/// Symbols
internal static var emojiPickerSymbolsCategory: String {
return VectorL10n.tr("Vector", "emoji_picker_symbols_category")
}
/// Reactions
internal static var emojiPickerTitle: String {
return VectorL10n.tr("Vector", "emoji_picker_title")
}
/// Send an encrypted message
internal static var encryptedRoomMessagePlaceholder: String {
return VectorL10n.tr("Vector", "encrypted_room_message_placeholder")

View file

@ -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)
}
}
}

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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"
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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
}
}