mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 15:22:39 +00:00
Emoji picker: Handle Emoji parsing with EmojiService.
This commit is contained in:
parent
8dca707d82
commit
01b9b483c6
10 changed files with 380 additions and 0 deletions
1
Riot/Assets/apple_emojis_data.json
Normal file
1
Riot/Assets/apple_emojis_data.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -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";
|
||||
|
|
|
@ -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")
|
||||
|
|
70
Riot/Modules/Room/EmojiPicker/Data/EmojiService.swift
Normal file
70
Riot/Modules/Room/EmojiPicker/Data/EmojiService.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
21
Riot/Modules/Room/EmojiPicker/Data/EmojiServiceType.swift
Normal file
21
Riot/Modules/Room/EmojiPicker/Data/EmojiServiceType.swift
Normal 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)
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
65
Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONStore.swift
Normal file
65
Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONStore.swift
Normal 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)
|
||||
}
|
||||
}
|
27
Riot/Modules/Room/EmojiPicker/Data/Store/EmojiCategory.swift
Normal file
27
Riot/Modules/Room/EmojiPicker/Data/Store/EmojiCategory.swift
Normal 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)
|
||||
}
|
||||
}
|
41
Riot/Modules/Room/EmojiPicker/Data/Store/EmojiItem.swift
Normal file
41
Riot/Modules/Room/EmojiPicker/Data/Store/EmojiItem.swift
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue