mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-04-17 23:18:27 +02:00
Emoji picker: Handle Manu's comments.
This commit is contained in:
@@ -508,7 +508,7 @@
|
||||
B1DCC62422E60CA900625807 /* EmojiPickerCategoryViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC62322E60CA800625807 /* EmojiPickerCategoryViewData.swift */; };
|
||||
B1DCC62622E60CC600625807 /* EmojiItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC62522E60CC600625807 /* EmojiItem.swift */; };
|
||||
B1DCC62822E60CE300625807 /* EmojiCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC62722E60CE300625807 /* EmojiCategory.swift */; };
|
||||
B1DCC62A22E60D1000625807 /* EmojiService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC62922E60D1000625807 /* EmojiService.swift */; };
|
||||
B1DCC62A22E60D1000625807 /* EmojiMartService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC62922E60D1000625807 /* EmojiMartService.swift */; };
|
||||
B1DCC62D22E61EAF00625807 /* EmojiPickerViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC62B22E61EAF00625807 /* EmojiPickerViewCell.swift */; };
|
||||
B1DCC62E22E61EAF00625807 /* EmojiPickerViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1DCC62C22E61EAF00625807 /* EmojiPickerViewCell.xib */; };
|
||||
B1DCC63122E7026F00625807 /* EmojiPickerHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC62F22E7026F00625807 /* EmojiPickerHeaderView.swift */; };
|
||||
@@ -516,9 +516,9 @@
|
||||
B1DCC63422E72C1B00625807 /* UISearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC63322E72C1B00625807 /* UISearchBar.swift */; };
|
||||
B1DCC63522E72D2200625807 /* UISearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC63322E72C1B00625807 /* UISearchBar.swift */; };
|
||||
B1DCC63722E8541700625807 /* EmojiStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC63622E8541700625807 /* EmojiStore.swift */; };
|
||||
B1DCC63922E85E9A00625807 /* EmojiJSONStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC63822E85E9A00625807 /* EmojiJSONStore.swift */; };
|
||||
B1DCC63B22E85EF800625807 /* EmojiJSONCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC63A22E85EF800625807 /* EmojiJSONCategory.swift */; };
|
||||
B1DCC63F22E9A3AE00625807 /* EmojiItem+Decodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC63E22E9A3AE00625807 /* EmojiItem+Decodable.swift */; };
|
||||
B1DCC63922E85E9A00625807 /* EmojiMartStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC63822E85E9A00625807 /* EmojiMartStore.swift */; };
|
||||
B1DCC63B22E85EF800625807 /* EmojiMartCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC63A22E85EF800625807 /* EmojiMartCategory.swift */; };
|
||||
B1DCC63F22E9A3AE00625807 /* EmojiItem+EmojiMart.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC63E22E9A3AE00625807 /* EmojiItem+EmojiMart.swift */; };
|
||||
B1E5368921FB1E20001F3AFF /* UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1E5368821FB1E20001F3AFF /* UIButton.swift */; };
|
||||
B1E5368D21FB7245001F3AFF /* KeyBackupRecoverFromPassphraseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1E5368C21FB7245001F3AFF /* KeyBackupRecoverFromPassphraseViewController.swift */; };
|
||||
B1E5368F21FB7258001F3AFF /* KeyBackupRecoverFromPassphraseViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1E5368E21FB7258001F3AFF /* KeyBackupRecoverFromPassphraseViewController.storyboard */; };
|
||||
@@ -1293,16 +1293,16 @@
|
||||
B1DCC62322E60CA800625807 /* EmojiPickerCategoryViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerCategoryViewData.swift; sourceTree = "<group>"; };
|
||||
B1DCC62522E60CC600625807 /* EmojiItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItem.swift; sourceTree = "<group>"; };
|
||||
B1DCC62722E60CE300625807 /* EmojiCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiCategory.swift; sourceTree = "<group>"; };
|
||||
B1DCC62922E60D1000625807 /* EmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiService.swift; sourceTree = "<group>"; };
|
||||
B1DCC62922E60D1000625807 /* EmojiMartService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartService.swift; sourceTree = "<group>"; };
|
||||
B1DCC62B22E61EAF00625807 /* EmojiPickerViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerViewCell.swift; sourceTree = "<group>"; };
|
||||
B1DCC62C22E61EAF00625807 /* EmojiPickerViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EmojiPickerViewCell.xib; sourceTree = "<group>"; };
|
||||
B1DCC62F22E7026F00625807 /* EmojiPickerHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerHeaderView.swift; sourceTree = "<group>"; };
|
||||
B1DCC63022E7026F00625807 /* EmojiPickerHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EmojiPickerHeaderView.xib; sourceTree = "<group>"; };
|
||||
B1DCC63322E72C1B00625807 /* UISearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISearchBar.swift; sourceTree = "<group>"; };
|
||||
B1DCC63622E8541700625807 /* EmojiStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiStore.swift; sourceTree = "<group>"; };
|
||||
B1DCC63822E85E9A00625807 /* EmojiJSONStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiJSONStore.swift; sourceTree = "<group>"; };
|
||||
B1DCC63A22E85EF800625807 /* EmojiJSONCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiJSONCategory.swift; sourceTree = "<group>"; };
|
||||
B1DCC63E22E9A3AE00625807 /* EmojiItem+Decodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EmojiItem+Decodable.swift"; sourceTree = "<group>"; };
|
||||
B1DCC63822E85E9A00625807 /* EmojiMartStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartStore.swift; sourceTree = "<group>"; };
|
||||
B1DCC63A22E85EF800625807 /* EmojiMartCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartCategory.swift; sourceTree = "<group>"; };
|
||||
B1DCC63E22E9A3AE00625807 /* EmojiItem+EmojiMart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EmojiItem+EmojiMart.swift"; sourceTree = "<group>"; };
|
||||
B1E5368821FB1E20001F3AFF /* UIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButton.swift; sourceTree = "<group>"; };
|
||||
B1E5368C21FB7245001F3AFF /* KeyBackupRecoverFromPassphraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewController.swift; sourceTree = "<group>"; };
|
||||
B1E5368E21FB7258001F3AFF /* KeyBackupRecoverFromPassphraseViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = KeyBackupRecoverFromPassphraseViewController.storyboard; sourceTree = "<group>"; };
|
||||
@@ -1855,9 +1855,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B1B9DEDD22E9D9890065E677 /* EmojiServiceType.swift */,
|
||||
B1DCC62922E60D1000625807 /* EmojiService.swift */,
|
||||
B1DCC64022E9B37400625807 /* Store */,
|
||||
B1B9DED722E9B56C0065E677 /* JSON */,
|
||||
B1B9DED722E9B56C0065E677 /* EmojiMart */,
|
||||
);
|
||||
path = Data;
|
||||
sourceTree = "<group>";
|
||||
@@ -3243,14 +3242,15 @@
|
||||
path = Analytics;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B1B9DED722E9B56C0065E677 /* JSON */ = {
|
||||
B1B9DED722E9B56C0065E677 /* EmojiMart */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B1DCC63822E85E9A00625807 /* EmojiJSONStore.swift */,
|
||||
B1DCC63A22E85EF800625807 /* EmojiJSONCategory.swift */,
|
||||
B1DCC63E22E9A3AE00625807 /* EmojiItem+Decodable.swift */,
|
||||
B1DCC62922E60D1000625807 /* EmojiMartService.swift */,
|
||||
B1DCC63822E85E9A00625807 /* EmojiMartStore.swift */,
|
||||
B1DCC63A22E85EF800625807 /* EmojiMartCategory.swift */,
|
||||
B1DCC63E22E9A3AE00625807 /* EmojiItem+EmojiMart.swift */,
|
||||
);
|
||||
path = JSON;
|
||||
path = EmojiMart;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B1B9DED822E9B7120065E677 /* Serialization */ = {
|
||||
@@ -4123,7 +4123,7 @@
|
||||
3232ABA3225730E100AD6A5C /* DeviceVerificationStartCoordinatorType.swift in Sources */,
|
||||
3232AB4D2256558300AD6A5C /* TemplateScreenCoordinatorType.swift in Sources */,
|
||||
B1B5581720EF625800210D55 /* PreviewRoomTitleView.m in Sources */,
|
||||
B1DCC63F22E9A3AE00625807 /* EmojiItem+Decodable.swift in Sources */,
|
||||
B1DCC63F22E9A3AE00625807 /* EmojiItem+EmojiMart.swift in Sources */,
|
||||
B1DCC61C22E5E17100625807 /* EmojiPickerViewAction.swift in Sources */,
|
||||
B1098BDF21ECE09F000DDA48 /* Strings.swift in Sources */,
|
||||
B1B558C420EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */,
|
||||
@@ -4218,14 +4218,14 @@
|
||||
32F6B96B2270623100BBA352 /* DeviceVerificationDataLoadingViewAction.swift in Sources */,
|
||||
B1B558C920EF768F00210D55 /* RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.m in Sources */,
|
||||
B1B5571B20EE6C4D00210D55 /* DeactivateAccountViewController.m in Sources */,
|
||||
B1DCC63922E85E9A00625807 /* EmojiJSONStore.swift in Sources */,
|
||||
B1DCC63922E85E9A00625807 /* EmojiMartStore.swift in Sources */,
|
||||
B1B5590620EF768F00210D55 /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.m in Sources */,
|
||||
B139C21D21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift in Sources */,
|
||||
F083BE031E7009ED00A9B29C /* EventFormatter.m in Sources */,
|
||||
B1DCC62422E60CA900625807 /* EmojiPickerCategoryViewData.swift in Sources */,
|
||||
324A2056225FC571004FE8B0 /* DeviceVerificationIncomingCoordinator.swift in Sources */,
|
||||
B16932F720F3C50E00746532 /* RecentsDataSource.m in Sources */,
|
||||
B1DCC63B22E85EF800625807 /* EmojiJSONCategory.swift in Sources */,
|
||||
B1DCC63B22E85EF800625807 /* EmojiMartCategory.swift in Sources */,
|
||||
3232AB4F2256558300AD6A5C /* TemplateScreenViewController.swift in Sources */,
|
||||
B1B558FC20EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleBubbleCell.m in Sources */,
|
||||
B1B5572920EE6C4D00210D55 /* RoomFilesViewController.m in Sources */,
|
||||
@@ -4327,7 +4327,7 @@
|
||||
B1B5575A20EE6C4D00210D55 /* UnifiedSearchViewController.m in Sources */,
|
||||
3232AB492256558300AD6A5C /* FlowTemplateCoordinatorBridgePresenter.swift in Sources */,
|
||||
B1B5572820EE6C4D00210D55 /* RoomViewController.m in Sources */,
|
||||
B1DCC62A22E60D1000625807 /* EmojiService.swift in Sources */,
|
||||
B1DCC62A22E60D1000625807 /* EmojiMartService.swift in Sources */,
|
||||
B1B558C720EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */,
|
||||
B1B558F020EF768F00210D55 /* RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m in Sources */,
|
||||
926FA53F1F4C132000F826C2 /* MXSession+Riot.m in Sources */,
|
||||
|
||||
@@ -1359,6 +1359,23 @@
|
||||
href="https://www.apache.org/licenses/LICENSE-2.0">https://www.apache.org/licenses/LICENSE-2.0</a>
|
||||
<br/><br/>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.
|
||||
</li>
|
||||
<li>
|
||||
<p><b>Emoji Mart</b> (<a href="https://github.com/missive/emoji-mart">https://github.com/missive/emoji-mart</a>)</p>
|
||||
<pre>
|
||||
Copyright (c) 2016, Missive
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
</pre>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -17,10 +17,19 @@
|
||||
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 code = "b"
|
||||
case shortName
|
||||
case name = "a"
|
||||
case codepoint = "b"
|
||||
case shortNames = "n"
|
||||
case keywords = "j"
|
||||
}
|
||||
@@ -28,11 +37,11 @@ extension EmojiItem: Decodable {
|
||||
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")
|
||||
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: .code)
|
||||
let emojiUnicodeStringValue = try container.decode(String.self, forKey: .codepoint)
|
||||
|
||||
let unicodeStringComponents = emojiUnicodeStringValue.components(separatedBy: "-")
|
||||
|
||||
@@ -43,7 +52,7 @@ extension EmojiItem: Decodable {
|
||||
let emojiUnicodeScalar = UnicodeScalar(unicodeCodePoint) {
|
||||
emoji.append(String(emojiUnicodeScalar))
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(forKey: .code, in: container, debugDescription: "Cannot initialize emoji")
|
||||
throw DecodingError.dataCorruptedError(forKey: .codepoint, in: container, debugDescription: "Cannot initialize emoji")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +74,7 @@ extension EmojiItem: Decodable {
|
||||
keywords = []
|
||||
}
|
||||
|
||||
self.init(identifier: identifier,
|
||||
self.init(shortName: shortName,
|
||||
value: emoji,
|
||||
name: name,
|
||||
shortNames: shortNames,
|
||||
@@ -16,19 +16,21 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct EmojiJSONStore {
|
||||
struct EmojiMartStore {
|
||||
let categories: [EmojiJSONCategory]
|
||||
let emojis: [EmojiItem]
|
||||
}
|
||||
|
||||
// MARK: - Decodable
|
||||
extension EmojiJSONStore: 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
|
||||
|
||||
@@ -51,7 +53,7 @@ extension EmojiJSONStore: Decodable {
|
||||
do {
|
||||
emojiItem = try emojisContainer.decode(EmojiItem.self, forKey: emojiKey)
|
||||
} catch {
|
||||
print(error)
|
||||
print("[EmojiJSONStore] init(from decoder: Decoder) failed to parse emojiItem \(emojiKey) with error: \(error)")
|
||||
emojiItem = nil
|
||||
}
|
||||
|
||||
@@ -16,18 +16,25 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct EmojiJSONCategory {
|
||||
struct EmojiMartCategory {
|
||||
|
||||
/// Emoji category identifier (e.g. "people")
|
||||
let identifier: String
|
||||
|
||||
/// Emoji category name in english (e.g. "Smiley & People")
|
||||
let name: String
|
||||
let emojiIdentifiers: [String]
|
||||
|
||||
/// List of emoji short names associated to the category (e.g. "people")
|
||||
let emojiShortNames: [String]
|
||||
}
|
||||
|
||||
// MARK: - Decodable
|
||||
extension EmojiJSONCategory: Decodable {
|
||||
extension EmojiMartCategory: Decodable {
|
||||
|
||||
/// JSON keys associated to EmojiJSONCategory properties.
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case identifier = "id"
|
||||
case name
|
||||
case emojiIdentifiers = "emojis"
|
||||
case emojiShortNames = "emojis"
|
||||
}
|
||||
}
|
||||
@@ -20,16 +20,18 @@ enum EmojiServiceError: Error {
|
||||
case emojiJSONFileNotFound
|
||||
}
|
||||
|
||||
final class EmojiService: EmojiServiceType {
|
||||
/// 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: EmojiService.self))")
|
||||
private let serviceQueue = DispatchQueue(label: "\(type(of: EmojiMartService.self))")
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
@@ -37,7 +39,7 @@ final class EmojiService: EmojiServiceType {
|
||||
self.serviceQueue.async {
|
||||
do {
|
||||
let emojiJSONData = try self.getEmojisJSONData()
|
||||
let emojiJSONStore: EmojiJSONStore = try self.serializationService.deserialize(emojiJSONData)
|
||||
let emojiJSONStore: EmojiMartStore = try self.serializationService.deserialize(emojiJSONData)
|
||||
let emojiCategories = self.emojiCategories(from: emojiJSONStore)
|
||||
completion(MXResponse.success(emojiCategories))
|
||||
} catch {
|
||||
@@ -49,19 +51,19 @@ final class EmojiService: EmojiServiceType {
|
||||
// MARK: - Private
|
||||
|
||||
private func getEmojisJSONData() throws -> Data {
|
||||
guard let jsonDataURL = Bundle.main.url(forResource: EmojiService.jsonFilename, withExtension: "json") else {
|
||||
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: EmojiJSONStore) -> [EmojiCategory] {
|
||||
private func emojiCategories(from emojiJSONStore: EmojiMartStore) -> [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 })
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,14 @@
|
||||
import Foundation
|
||||
|
||||
struct EmojiCategory {
|
||||
|
||||
/// Emoji category identifier (e.g. "people")
|
||||
let identifier: String
|
||||
|
||||
/// Emoji list associated to category
|
||||
let emojis: [EmojiItem]
|
||||
|
||||
/// Emoji category localized name
|
||||
var name: String {
|
||||
let categoryNameLocalizationKey = "emoji_picker_\(self.identifier)_category"
|
||||
return VectorL10n.tr("Vector", categoryNameLocalizationKey)
|
||||
|
||||
@@ -17,21 +17,37 @@
|
||||
import Foundation
|
||||
|
||||
struct EmojiItem {
|
||||
let identifier: String
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
/// The commonly-agreed short name for the emoji, as supported in GitHub and others via the :short_name: syntax (e.g. "grinning" for 😀).
|
||||
let shortName: String
|
||||
|
||||
/// The emoji string (e.g. 😀)
|
||||
let value: String
|
||||
|
||||
/// The offical Unicode name (e.g. "Grinning Face" for 😀)
|
||||
let name: String
|
||||
|
||||
/// An array of all the other known short names (e.g. ["running"] for 🏃♂️).
|
||||
let shortNames: [String]
|
||||
let keywords: [String]
|
||||
|
||||
/// Associated emoji keywords (e.g. ["face","smile","happy"] for 😀).
|
||||
let keywords: [String]
|
||||
|
||||
/// For emoji with multiple skin tone variations, a list of alternative emoji items.
|
||||
let variations: [EmojiItem]
|
||||
|
||||
init(identifier: String,
|
||||
// MARK: - Setup
|
||||
|
||||
init(shortName: String,
|
||||
value: String,
|
||||
name: String,
|
||||
shortNames: [String] = [],
|
||||
keywords: [String] = [],
|
||||
variations: [EmojiItem] = []) {
|
||||
|
||||
self.identifier = identifier
|
||||
self.shortName = shortName
|
||||
self.value = value
|
||||
self.name = name
|
||||
self.shortNames = shortNames
|
||||
|
||||
@@ -24,7 +24,7 @@ protocol EmojiPickerCoordinatorDelegate: class {
|
||||
func emojiPickerCoordinatorDidCancel(_ coordinator: EmojiPickerCoordinatorType)
|
||||
}
|
||||
|
||||
/// `EmojiPickerCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow.
|
||||
/// `EmojiPickerCoordinatorType` is a protocol describing a Coordinator that handle emoji picker navigation flow.
|
||||
protocol EmojiPickerCoordinatorType: Coordinator, Presentable {
|
||||
var delegate: EmojiPickerCoordinatorDelegate? { get }
|
||||
}
|
||||
|
||||
@@ -27,8 +27,9 @@ final class EmojiPickerViewModel: EmojiPickerViewModelType {
|
||||
private let session: MXSession
|
||||
private let roomId: String
|
||||
private let eventId: String
|
||||
private let emojiService: EmojiService
|
||||
private let emojiService: EmojiServiceType
|
||||
private let emojiStore: EmojiStore
|
||||
private let processingQueue: DispatchQueue
|
||||
|
||||
private lazy var aggregatedReactionsByEmoji: [String: MXReactionCount] = {
|
||||
return self.buildAggregatedReactionsByEmoji()
|
||||
@@ -45,8 +46,9 @@ final class EmojiPickerViewModel: EmojiPickerViewModelType {
|
||||
self.session = session
|
||||
self.roomId = roomId
|
||||
self.eventId = eventId
|
||||
self.emojiService = EmojiService()
|
||||
self.emojiService = EmojiMartService()
|
||||
self.emojiStore = EmojiStore.shared
|
||||
self.processingQueue = DispatchQueue(label: "\(type(of: self))")
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
@@ -99,16 +101,21 @@ final class EmojiPickerViewModel: EmojiPickerViewModelType {
|
||||
}
|
||||
|
||||
private func searchEmojis(with searchText: String?) {
|
||||
let filteredEmojiCategories: [EmojiCategory]
|
||||
|
||||
if let searchText = searchText, searchText.isEmpty == false {
|
||||
filteredEmojiCategories = self.emojiStore.findEmojiItemsSortedByCategory(with: searchText)
|
||||
} else {
|
||||
filteredEmojiCategories = self.emojiStore.getAll()
|
||||
self.processingQueue.async {
|
||||
let filteredEmojiCategories: [EmojiCategory]
|
||||
|
||||
if let searchText = searchText, searchText.isEmpty == false {
|
||||
filteredEmojiCategories = self.emojiStore.findEmojiItemsSortedByCategory(with: searchText)
|
||||
} else {
|
||||
filteredEmojiCategories = self.emojiStore.getAll()
|
||||
}
|
||||
|
||||
let emojiCatagoryViewDataList = self.emojiCatagoryViewDataList(from: filteredEmojiCategories)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.update(viewState: .loaded(emojiCategories: emojiCatagoryViewDataList))
|
||||
}
|
||||
}
|
||||
|
||||
let emojiCatagoryViewDataList = self.emojiCatagoryViewDataList(from: filteredEmojiCategories)
|
||||
self.update(viewState: .loaded(emojiCategories: emojiCatagoryViewDataList))
|
||||
}
|
||||
|
||||
private func update(viewState: EmojiPickerViewState) {
|
||||
@@ -119,7 +126,7 @@ final class EmojiPickerViewModel: EmojiPickerViewModelType {
|
||||
return emojiCategories.map { (emojiCategory) -> EmojiPickerCategoryViewData in
|
||||
let emojiPickerViewDataList = emojiCategory.emojis.map({ (emojiItem) -> EmojiPickerItemViewData in
|
||||
let isSelected = self.isUserReacted(with: emojiItem.value)
|
||||
return EmojiPickerItemViewData(identifier: emojiItem.identifier, emoji: emojiItem.value, isSelected: isSelected)
|
||||
return EmojiPickerItemViewData(identifier: emojiItem.shortName, emoji: emojiItem.value, isSelected: isSelected)
|
||||
})
|
||||
return EmojiPickerCategoryViewData(identifier: emojiCategory.identifier, name: emojiCategory.name, emojiViewDataList: emojiPickerViewDataList)
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ class EmojiServiceTests: XCTestCase {
|
||||
|
||||
let expectation = self.expectation(description: "get Emoji categories")
|
||||
|
||||
let emojiService = EmojiService()
|
||||
let emojiService = EmojiMartService()
|
||||
emojiService.getEmojiCategories { (response) in
|
||||
switch response {
|
||||
case .success(let emojiCategories):
|
||||
@@ -125,7 +125,7 @@ class EmojiServiceTests: XCTestCase {
|
||||
|
||||
let grinningEmoji = peopleEmojiCategory.emojis[0]
|
||||
|
||||
XCTAssertEqual(grinningEmoji.identifier, "grinning")
|
||||
XCTAssertEqual(grinningEmoji.shortName, "grinning")
|
||||
XCTAssertEqual(grinningEmoji.value, "😀")
|
||||
XCTAssertEqual(grinningEmoji.keywords.count, 6)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user