mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-20 00:24:43 +02:00
Emoji picker: Handle Manu's comments.
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user