From d0d9de0bfece48cdb9963766821952543f30e95a Mon Sep 17 00:00:00 2001 From: Szimszon Date: Tue, 16 Jul 2019 06:19:20 +0000 Subject: [PATCH 01/75] Translated using Weblate (Hungarian) Currently translated at 100.0% (716 of 716 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 668b332b2..1a0be2dff 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -756,7 +756,7 @@ "device_verification_emoji_thumbs up" = "Hüvelykujj fel"; "device_verification_emoji_umbrella" = "Esernyő"; "device_verification_emoji_hourglass" = "Homokóra"; -"device_verification_emoji_clock" = "Osztály"; +"device_verification_emoji_clock" = "Óra"; "device_verification_emoji_gift" = "Ajándék"; "device_verification_emoji_light bulb" = "Égő"; "device_verification_emoji_book" = "Könyv"; From 07505b08dc2c2d4599ee22b6367f6c2ff6e00aaf Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 19 Jul 2019 14:25:45 +0200 Subject: [PATCH 02/75] Soft logout: Support soft logout #2540 --- CHANGES.rst | 7 ++++ Riot/AppDelegate.m | 10 +++++ .../AuthenticationViewController.m | 10 +++++ .../Authentication/Views/AuthInputsView.m | 23 ++++++++++- Riot/Modules/TabBar/MasterTabBarController.h | 7 ++++ Riot/Modules/TabBar/MasterTabBarController.m | 41 ++++++++++++++++++- 6 files changed, 96 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 0b1fb0bed..a09fb1333 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +Changes in 0.9.2 (2019-07-) +=============================================== + +Improvements: + * Upgrade MatrixKit version ([v0.10.2](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.10.2)). + * Soft logout: Support soft logout (#2540). + Changes in 0.9.1 (2019-07-17) =============================================== diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index db380b2ec..10297a090 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -2781,6 +2781,16 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN [self logoutWithConfirmation:NO completion:nil]; } }]; + + // Add observer to handle soft logout + [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountManagerDidSoftlogoutAccountNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + MXKAccount *account = notif.object; + [self removeMatrixSession:account.mxSession]; + + // Return to authentication screen + [self.masterTabBarController showAuthenticationScreenAfterSoftLogout:account.mxCredentials]; + }]; [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionIgnoredUsersDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull notif) { diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index fb73984d6..af8240796 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -395,6 +395,16 @@ } } +- (void)setSoftLogoutCredentials:(MXCredentials *)softLogoutCredentials +{ + [super setSoftLogoutCredentials:softLogoutCredentials]; + + // Customise the screen for soft logout + // TODO + self.customServersTickButton.hidden = YES; +} + + - (void)handleAuthenticationSession:(MXAuthenticationSession *)authSession { [super handleAuthenticationSession:authSession]; diff --git a/Riot/Modules/Authentication/Views/AuthInputsView.m b/Riot/Modules/Authentication/Views/AuthInputsView.m index 5685a9f43..81795f22d 100644 --- a/Riot/Modules/Authentication/Views/AuthInputsView.m +++ b/Riot/Modules/Authentication/Views/AuthInputsView.m @@ -54,6 +54,7 @@ @end @implementation AuthInputsView +@synthesize softLogoutCredentials; + (UINib *)nib { @@ -487,6 +488,15 @@ } } } + + // For soft logout, pass the device_id currently used + if (parameters && self.softLogoutCredentials) + { + NSMutableDictionary *parametersWithDeviceId = [parameters mutableCopy]; + parametersWithDeviceId[@"device_id"] = self.softLogoutCredentials.deviceId; + parameters = parametersWithDeviceId; + } + } else if (type == MXKAuthenticationTypeRegister) { @@ -725,7 +735,12 @@ { // Note: this use case was not tested yet. parameters = @{ - @"auth": @{@"session":currentSession.session, @"username": self.userLoginTextField.text, @"password": self.passWordTextField.text, @"type": kMXLoginFlowTypePassword} + @"auth": @{ + @"session":currentSession.session, + @"username": self.userLoginTextField.text, + @"password": self.passWordTextField.text, + @"type": kMXLoginFlowTypePassword + } }; } else if ([self isFlowSupported:kMXLoginFlowTypeTerms] && ![self isFlowCompleted:kMXLoginFlowTypeTerms]) @@ -936,6 +951,12 @@ return YES; } +- (void)setSoftLogoutCredentials:(MXCredentials *)credentials +{ + softLogoutCredentials = credentials; + self.userLoginTextField.text = softLogoutCredentials.userId; +} + - (BOOL)areAllRequiredFieldsSet { // Keep enable the submit button. diff --git a/Riot/Modules/TabBar/MasterTabBarController.h b/Riot/Modules/TabBar/MasterTabBarController.h index 2a5f59710..8f17d2d0e 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.h +++ b/Riot/Modules/TabBar/MasterTabBarController.h @@ -63,6 +63,13 @@ */ - (void)showAuthenticationScreenWithRegistrationParameters:(NSDictionary*)parameters; +/** + Display the authentication screen in order to login back a soft logout session. + + @param softLogoutCredentials the credentials of the soft logout session. + */ +- (void)showAuthenticationScreenAfterSoftLogout:(MXCredentials*)softLogoutCredentials; + /** Open the room with the provided identifier in a specific matrix session. diff --git a/Riot/Modules/TabBar/MasterTabBarController.m b/Riot/Modules/TabBar/MasterTabBarController.m index c85c8b969..634df71bc 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.m +++ b/Riot/Modules/TabBar/MasterTabBarController.m @@ -42,6 +42,7 @@ // The parameters to pass to the Authentification view controller. NSDictionary *authViewControllerRegistrationParameters; + MXCredentials *softLogoutCredentials; // The recents data source shared between all the view controllers of the tab bar. RecentsDataSource *recentsDataSource; @@ -142,11 +143,25 @@ [super viewDidAppear:animated]; // Check whether we're not logged in + BOOL authIsShown = NO; if (![MXKAccountManager sharedManager].accounts.count) { [self showAuthenticationScreen]; + authIsShown = YES; } - else + else if (![MXKAccountManager sharedManager].activeAccounts.count) + { + // Display a login screen if the account is soft logout + // Note: We support only one account + MXKAccount *account = [MXKAccountManager sharedManager].accounts.firstObject; + if (account.isSoftLogout) + { + [self showAuthenticationScreenAfterSoftLogout:account.mxCredentials]; + authIsShown = YES; + } + } + + if (!authIsShown) { // Check whether the user has been already prompted to send crash reports. // (Check whether 'enableCrashReport' flag has been set once) @@ -401,6 +416,25 @@ } } +- (void)showAuthenticationScreenAfterSoftLogout:(MXCredentials*)credentials; +{ + NSLog(@"[MasterTabBarController] showAuthenticationScreenAfterSoftLogout"); + + softLogoutCredentials = credentials; + + // Check whether an authentication screen is not already shown or preparing + if (!self.authViewController && !isAuthViewControllerPreparing) + { + isAuthViewControllerPreparing = YES; + + [[AppDelegate theDelegate] restoreInitialDisplay:^{ + + [self performSegueWithIdentifier:@"showAuth" sender:self]; + + }]; + } +} + - (void)selectRoomWithId:(NSString*)roomId andEventId:(NSString*)eventId inMatrixSession:(MXSession*)matrixSession { [self selectRoomWithId:roomId andEventId:eventId inMatrixSession:matrixSession completion:nil]; @@ -637,6 +671,11 @@ _authViewController.externalRegistrationParameters = authViewControllerRegistrationParameters; authViewControllerRegistrationParameters = nil; } + if (softLogoutCredentials) + { + _authViewController.softLogoutCredentials = softLogoutCredentials; + softLogoutCredentials = nil; + } } else if ([[segue identifier] isEqualToString:@"showUnifiedSearch"]) { From 20cc2fac39a1001d5a9fa8bfe3d22292b2a435e1 Mon Sep 17 00:00:00 2001 From: Osoitz Date: Sat, 20 Jul 2019 06:56:50 +0000 Subject: [PATCH 03/75] Translated using Weblate (Basque) Currently translated at 100.0% (716 of 716 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/eu/ --- Riot/Assets/eu.lproj/Vector.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index a605cebfb..58f9f6d3f 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -740,7 +740,7 @@ "device_verification_cancelled" = "Beste aldeak egiaztaketa ezeztatu du."; "device_verification_cancelled_by_me" = "Egiaztaketa ezeztatu da. Arrazoia: %@"; "device_verification_emoji_hourglass" = "Harea-erlojua"; -"device_verification_emoji_clock" = "Klasea"; +"device_verification_emoji_clock" = "Erlojua"; "device_verification_emoji_gift" = "Oparia"; "device_verification_emoji_light bulb" = "Bonbilla"; "device_verification_emoji_book" = "Liburua"; From 400b9c140e5ef43f7e9d70cceb7ad0bd93647a90 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 23 Jul 2019 11:51:46 +0200 Subject: [PATCH 04/75] Soft logout: Implement design This is an adapted version of the zeplin design. It uses the current app login look and feel with the copy of the zeplin design #2540 --- Riot/Assets/en.lproj/Vector.strings | 4 ++ Riot/Generated/Strings.swift | 16 ++++++ .../AuthenticationViewController.m | 10 ++-- .../Authentication/Views/AuthInputsView.m | 53 +++++++++++++++++++ 4 files changed, 80 insertions(+), 3 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index f13c54309..6d0fd62f1 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -115,6 +115,10 @@ "auth_add_email_and_phone_warning" = "Registration with email and phone number at once is not supported yet until the api exists. Only the phone number will be taken into account. You may add your email to your profile in settings."; "auth_accept_policies" = "Please review and accept the policies of this homeserver:"; "auth_autodiscover_invalid_response" = "Invalid homeserver discovery response"; +"auth_softlogout_signed_out" = "You’re signed out"; +"auth_softlogout_sign_in" = "Sign In"; +"auth_softlogout_reason" = "Your homeserver (%1$@) admin has signed you out of your account %2$@ (%3$@)."; +"auth_softlogout_recover_encryption_keys" = "Sign in to recover encryption keys stored exclusively on this device. You need them to read all of your secure messages on any device."; // Chat creation "room_creation_title" = "New Chat"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 0738f3685..5c1a54486 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -226,6 +226,22 @@ internal enum VectorL10n { internal static var authSkip: String { return VectorL10n.tr("Vector", "auth_skip") } + /// Your homeserver (%1$@) admin has signed you out of your account %2$@ (%3$@). + internal static func authSoftlogoutReason(_ p1: String, _ p2: String, _ p3: String) -> String { + return VectorL10n.tr("Vector", "auth_softlogout_reason", p1, p2, p3) + } + /// Sign in to recover encryption keys stored exclusively on this device. You need them to read all of your secure messages on any device. + internal static var authSoftlogoutRecoverEncryptionKeys: String { + return VectorL10n.tr("Vector", "auth_softlogout_recover_encryption_keys") + } + /// Sign In + internal static var authSoftlogoutSignIn: String { + return VectorL10n.tr("Vector", "auth_softlogout_sign_in") + } + /// You’re signed out + internal static var authSoftlogoutSignedOut: String { + return VectorL10n.tr("Vector", "auth_softlogout_signed_out") + } /// Submit internal static var authSubmit: String { return VectorL10n.tr("Vector", "auth_submit") diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index af8240796..590bd20f9 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -1,7 +1,8 @@ /* Copyright 2015 OpenMarket Ltd Copyright 2017 Vector Creations Ltd - + 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 @@ -366,7 +367,7 @@ // The right bar button is used to switch the authentication type. if (self.authType == MXKAuthenticationTypeLogin) { - if (!authInputsview.isSingleSignOnRequired) + if (!authInputsview.isSingleSignOnRequired && !self.softLogoutCredentials) { self.rightBarButtonItem.title = NSLocalizedStringFromTable(@"auth_register", @"Vector", nil); } @@ -400,8 +401,11 @@ [super setSoftLogoutCredentials:softLogoutCredentials]; // Customise the screen for soft logout - // TODO self.customServersTickButton.hidden = YES; + self.rightBarButtonItem.title = nil; + self.mainNavigationItem.title = NSLocalizedStringFromTable(@"auth_softlogout_signed_out", @"Vector", nil); + + // TODO: Clear all data button } diff --git a/Riot/Modules/Authentication/Views/AuthInputsView.m b/Riot/Modules/Authentication/Views/AuthInputsView.m index 81795f22d..60b584f69 100644 --- a/Riot/Modules/Authentication/Views/AuthInputsView.m +++ b/Riot/Modules/Authentication/Views/AuthInputsView.m @@ -1,6 +1,7 @@ /* Copyright 2016 OpenMarket Ltd Copyright 2017 Vector Creations Ltd + 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. @@ -955,6 +956,58 @@ { softLogoutCredentials = credentials; self.userLoginTextField.text = softLogoutCredentials.userId; + self.userLoginContainer.hidden = YES; + self.phoneContainer.hidden = YES; + + [self displaySoftLogoutMessage]; +} + +- (void)displaySoftLogoutMessage +{ + // Take some shortcuts and make some assumptions (Riot uses MXFileStore) to + // retrieve my user display name as quick as possible + MXFileStore *fileStore = [[MXFileStore alloc] initWithCredentials:softLogoutCredentials]; + [fileStore asyncUsersWithUserIds:@[softLogoutCredentials.userId] success:^(NSArray * _Nonnull users) { + + MXUser *myUser = users.firstObject; + [self displaySoftLogoutMessageWithUserDisplayname:myUser.displayname]; + + } failure:^(NSError * _Nonnull error) { + NSLog(@"[AuthInputsView] displaySoftLogoutMessage: Cannot load displayname. Error: %@", error); + [self displaySoftLogoutMessageWithUserDisplayname:nil]; + }]; +} + +- (void)displaySoftLogoutMessageWithUserDisplayname:(NSString*)userDisplayname +{ + // Use messageLabel for this message + self.messageLabelTopConstraint.constant = 8; + self.messageLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + self.messageLabel.hidden = NO; + + NSMutableAttributedString *message = [[NSMutableAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"auth_softlogout_sign_in", @"Vector", nil) + attributes:@{ + NSFontAttributeName: [UIFont boldSystemFontOfSize:14] + }]; + + [message appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\n"]]; + + NSString *string = [NSString stringWithFormat:NSLocalizedStringFromTable(@"auth_softlogout_reason", @"Vector", nil), + softLogoutCredentials.homeServerName, userDisplayname, softLogoutCredentials.userId]; + [message appendAttributedString:[[NSAttributedString alloc] initWithString:string + attributes:@{ + NSFontAttributeName: [UIFont systemFontOfSize:14] + }]]; + + // TODO: Do not display this message if no e2e keys + [message appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\n"]]; + string = NSLocalizedStringFromTable(@"auth_softlogout_recover_encryption_keys", @"Vector", nil); + [message appendAttributedString:[[NSAttributedString alloc] initWithString:string + attributes:@{ + NSFontAttributeName: [UIFont systemFontOfSize:14] + }]]; + + self.messageLabel.attributedText = message; } - (BOOL)areAllRequiredFieldsSet From f18207355133d090172d9912884ad71f0847ac88 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 23 Jul 2019 14:45:51 +0200 Subject: [PATCH 05/75] Soft logout: Implement design for the clear data section #2540 --- Riot/Assets/en.lproj/Vector.strings | 4 ++ Riot/Generated/Strings.swift | 16 ++++++ .../AuthenticationViewController.h | 4 ++ .../AuthenticationViewController.m | 46 ++++++++++++++-- .../AuthenticationViewController.xib | 53 ++++++++++++++++++- 5 files changed, 118 insertions(+), 5 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 6d0fd62f1..b28e4807f 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -119,6 +119,10 @@ "auth_softlogout_sign_in" = "Sign In"; "auth_softlogout_reason" = "Your homeserver (%1$@) admin has signed you out of your account %2$@ (%3$@)."; "auth_softlogout_recover_encryption_keys" = "Sign in to recover encryption keys stored exclusively on this device. You need them to read all of your secure messages on any device."; +"auth_softlogout_clear_data" = "Clear personal data"; +"auth_softlogout_clear_data_message_1" = "Warning: Your personal data (including encryption keys) is still stored on this device."; +"auth_softlogout_clear_data_message_2" = "Clear it if you're finished using this device, or want to sign in to another account."; +"auth_softlogout_clear_data_button" = "Clear all data"; // Chat creation "room_creation_title" = "New Chat"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 5c1a54486..3b2f545ea 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -226,6 +226,22 @@ internal enum VectorL10n { internal static var authSkip: String { return VectorL10n.tr("Vector", "auth_skip") } + /// Clear personal data + internal static var authSoftlogoutClearData: String { + return VectorL10n.tr("Vector", "auth_softlogout_clear_data") + } + /// Clear all data + internal static var authSoftlogoutClearDataButton: String { + return VectorL10n.tr("Vector", "auth_softlogout_clear_data_button") + } + /// Warning: Your personal data (including encryption keys) is still stored on this device. + internal static var authSoftlogoutClearDataMessage1: String { + return VectorL10n.tr("Vector", "auth_softlogout_clear_data_message_1") + } + /// Clear it if you're finished using this device, or want to sign in to another account. + internal static var authSoftlogoutClearDataMessage2: String { + return VectorL10n.tr("Vector", "auth_softlogout_clear_data_message_2") + } /// Your homeserver (%1$@) admin has signed you out of your account %2$@ (%3$@). internal static func authSoftlogoutReason(_ p1: String, _ p2: String, _ p3: String) -> String { return VectorL10n.tr("Vector", "auth_softlogout_reason", p1, p2, p3) diff --git a/Riot/Modules/Authentication/AuthenticationViewController.h b/Riot/Modules/Authentication/AuthenticationViewController.h index 5358f20f6..9c24ea4f1 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.h +++ b/Riot/Modules/Authentication/AuthenticationViewController.h @@ -41,5 +41,9 @@ @property (weak, nonatomic) IBOutlet UIView *homeServerSeparator; @property (weak, nonatomic) IBOutlet UIView *identityServerSeparator; +@property (weak, nonatomic) IBOutlet UIView *softLogoutClearDataContainer; +@property (weak, nonatomic) IBOutlet UILabel *softLogoutClearDataLabel; +@property (weak, nonatomic) IBOutlet UIButton *softLogoutClearDataButton; + @end diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index 590bd20f9..22e855528 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -108,6 +108,14 @@ [self.customServersTickButton setImage:[UIImage imageNamed:@"selection_untick"] forState:UIControlStateHighlighted]; [self hideCustomServers:YES]; + + // Soft logout section + self.softLogoutClearDataButton.layer.cornerRadius = 5; + self.softLogoutClearDataButton.clipsToBounds = YES; + [self.softLogoutClearDataButton setTitle:NSLocalizedStringFromTable(@"auth_softlogout_clear_data_button", @"Vector", nil) forState:UIControlStateNormal]; + [self.softLogoutClearDataButton setTitle:NSLocalizedStringFromTable(@"auth_softlogout_clear_data_button", @"Vector", nil) forState:UIControlStateHighlighted]; + self.softLogoutClearDataButton.enabled = YES; + self.softLogoutClearDataContainer.hidden = YES; // The view controller dismiss itself on successful login. self.delegate = self; @@ -195,7 +203,10 @@ self.identityServerLabel.textColor = ThemeService.shared.theme.textSecondaryColor; self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor; - + + self.softLogoutClearDataLabel.textColor = ThemeService.shared.theme.textPrimaryColor; + self.softLogoutClearDataButton.backgroundColor = ThemeService.shared.theme.warningColor; + [self.authInputsView customizeViewRendering]; [self setNeedsStatusBarAppearanceUpdate]; @@ -405,7 +416,30 @@ self.rightBarButtonItem.title = nil; self.mainNavigationItem.title = NSLocalizedStringFromTable(@"auth_softlogout_signed_out", @"Vector", nil); - // TODO: Clear all data button + [self showLogoutClearDataContainer]; +} + +- (void)showLogoutClearDataContainer +{ + NSMutableAttributedString *message = [[NSMutableAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"auth_softlogout_clear_data", @"Vector", nil) + attributes:@{ + NSFontAttributeName: [UIFont boldSystemFontOfSize:14] + }]; + + [message appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\n"]]; + + NSString *string = [NSString stringWithFormat:@"%@\n\n%@", + NSLocalizedStringFromTable(@"auth_softlogout_clear_data_message_1", @"Vector", nil), + NSLocalizedStringFromTable(@"auth_softlogout_clear_data_message_2", @"Vector", nil)]; + + [message appendAttributedString:[[NSAttributedString alloc] initWithString:string + attributes:@{ + NSFontAttributeName: [UIFont systemFontOfSize:14] + }]]; + self.softLogoutClearDataLabel.attributedText = message; + + self.softLogoutClearDataContainer.hidden = NO; + [self refreshContentViewHeightConstraint]; } @@ -724,7 +758,13 @@ } } } - + + if (!self.softLogoutClearDataContainer.isHidden) + { + // The soft logout clear data section adds more height + constant += self.softLogoutClearDataContainer.frame.size.height; + } + self.contentViewHeightConstraint.constant = constant; } diff --git a/Riot/Modules/Authentication/AuthenticationViewController.xib b/Riot/Modules/Authentication/AuthenticationViewController.xib index 71887099d..a875b9470 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.xib +++ b/Riot/Modules/Authentication/AuthenticationViewController.xib @@ -1,11 +1,11 @@ - + - + @@ -42,6 +42,9 @@ + + + @@ -345,17 +348,63 @@ + + + + + + + + + + + + + + + + + + + + From f7e2da7b1b0c707a8fbddd6f08ec242bba6b4af5 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 23 Jul 2019 16:20:07 +0200 Subject: [PATCH 06/75] Soft logout: Implement the clear data button #2540 --- Riot/AppDelegate.m | 5 ++- Riot/Assets/en.lproj/Vector.strings | 4 ++ Riot/Generated/Strings.swift | 12 +++++ .../AuthenticationViewController.m | 45 +++++++++++++++++++ .../AuthenticationViewController.xib | 3 ++ Riot/Modules/TabBar/MasterTabBarController.m | 15 +++++++ 6 files changed, 83 insertions(+), 1 deletion(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 10297a090..9e6cf8f5b 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -2770,7 +2770,10 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Remove inApp notifications toggle change MXKAccount *account = notif.object; - [account removeObserver:self forKeyPath:@"enableInAppNotifications"]; + if (!account.isSoftLogout) + { + [account removeObserver:self forKeyPath:@"enableInAppNotifications"]; + } // Clear Modular data [[WidgetManager sharedManager] deleteDataForUser:account.mxCredentials.userId]; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index b28e4807f..6dc4d294c 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -123,6 +123,10 @@ "auth_softlogout_clear_data_message_1" = "Warning: Your personal data (including encryption keys) is still stored on this device."; "auth_softlogout_clear_data_message_2" = "Clear it if you're finished using this device, or want to sign in to another account."; "auth_softlogout_clear_data_button" = "Clear all data"; +"auth_softlogout_clear_data_sign_out_title" = "Are you sure?"; +"auth_softlogout_clear_data_sign_out_msg" = "Are you sure you want to clear all data currently stored on this device? Sign in again to access your account data and messages."; +"auth_softlogout_clear_data_sign_out" = "Sign out"; + // Chat creation "room_creation_title" = "New Chat"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 3b2f545ea..b1f9b8633 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -242,6 +242,18 @@ internal enum VectorL10n { internal static var authSoftlogoutClearDataMessage2: String { return VectorL10n.tr("Vector", "auth_softlogout_clear_data_message_2") } + /// Sign out + internal static var authSoftlogoutClearDataSignOut: String { + return VectorL10n.tr("Vector", "auth_softlogout_clear_data_sign_out") + } + /// Are you sure you want to clear all data currently stored on this device? Sign in again to access your account data and messages. + internal static var authSoftlogoutClearDataSignOutMsg: String { + return VectorL10n.tr("Vector", "auth_softlogout_clear_data_sign_out_msg") + } + /// Are you sure? + internal static var authSoftlogoutClearDataSignOutTitle: String { + return VectorL10n.tr("Vector", "auth_softlogout_clear_data_sign_out_title") + } /// Your homeserver (%1$@) admin has signed you out of your account %2$@ (%3$@). internal static func authSoftlogoutReason(_ p1: String, _ p2: String, _ p3: String) -> String { return VectorL10n.tr("Vector", "auth_softlogout_reason", p1, p2, p3) diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index 22e855528..0f757cfd5 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -442,6 +442,47 @@ [self refreshContentViewHeightConstraint]; } +- (void)showClearDataAfterSoftLogoutConfirmation +{ + // Request confirmation + if (alert) + { + [alert dismissViewControllerAnimated:NO completion:nil]; + } + + alert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"auth_softlogout_clear_data_sign_out_title", @"Vector", nil) + message:NSLocalizedStringFromTable(@"auth_softlogout_clear_data_sign_out_msg", @"Vector", nil) + preferredStyle:UIAlertControllerStyleAlert]; + + + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"auth_softlogout_clear_data_sign_out", @"Vector", nil) style:UIAlertActionStyleDestructive + handler:^(UIAlertAction * action) + { + [self clearDataAfterSoftLogout]; + }]]; + + MXWeakify(self); + [alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) + { + MXStrongifyAndReturnIfNil(self); + self->alert = nil; + }]]; + + [self presentViewController:alert animated:YES completion:nil]; +} + +- (void)clearDataAfterSoftLogout +{ + NSLog(@"[AuthenticationVC] clearDataAfterSoftLogout %@", self.softLogoutCredentials.userId); + + // Use AppDelegate so that we reset app settings and this auth screen + [[AppDelegate theDelegate] logoutSendingRequestServer:YES completion:^(BOOL isLoggedOut) { + NSLog(@"[AuthenticationVC] Complete. isLoggedOut: %@", @(isLoggedOut)); + }]; +} + - (void)handleAuthenticationSession:(MXAuthenticationSession *)authSession { @@ -577,6 +618,10 @@ [ThemeService.shared.theme applyStyleOnNavigationBar:self.navigationController.navigationBar]; } + else if (sender == self.softLogoutClearDataButton) + { + [self showClearDataAfterSoftLogoutConfirmation]; + } else { [super onButtonPressed:sender]; diff --git a/Riot/Modules/Authentication/AuthenticationViewController.xib b/Riot/Modules/Authentication/AuthenticationViewController.xib index a875b9470..aa204d4d7 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.xib +++ b/Riot/Modules/Authentication/AuthenticationViewController.xib @@ -378,6 +378,9 @@ Clear it if you're finished using this device, or want to sign in to another acc + + + diff --git a/Riot/Modules/TabBar/MasterTabBarController.m b/Riot/Modules/TabBar/MasterTabBarController.m index 634df71bc..218101ca5 100644 --- a/Riot/Modules/TabBar/MasterTabBarController.m +++ b/Riot/Modules/TabBar/MasterTabBarController.m @@ -39,6 +39,7 @@ // Observer that checks when the Authentification view controller has gone. id authViewControllerObserver; + id authViewRemovedAccountObserver; // The parameters to pass to the Authentification view controller. NSDictionary *authViewControllerRegistrationParameters; @@ -231,6 +232,11 @@ [[NSNotificationCenter defaultCenter] removeObserver:authViewControllerObserver]; authViewControllerObserver = nil; } + if (authViewRemovedAccountObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:authViewRemovedAccountObserver]; + authViewRemovedAccountObserver = nil; + } if (kThemeServiceDidChangeThemeNotificationObserver) { @@ -664,6 +670,15 @@ [[NSNotificationCenter defaultCenter] removeObserver:authViewControllerObserver]; authViewControllerObserver = nil; }]; + + authViewRemovedAccountObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountManagerDidRemoveAccountNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + // The user has cleared data for their soft logged out account + _authViewController = nil; + + [[NSNotificationCenter defaultCenter] removeObserver:authViewRemovedAccountObserver]; + authViewRemovedAccountObserver = nil; + }]; // Forward parameters if any if (authViewControllerRegistrationParameters) From 18d7ca6b62669ba0852e2ce6847d68e4b60f7b1c Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 23 Jul 2019 17:03:24 +0200 Subject: [PATCH 07/75] Soft logout: Do not try to log against matrix.org if the password was wrong --- Riot/Modules/Authentication/AuthenticationViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index 0f757cfd5..2d3b1dcef 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -633,7 +633,7 @@ // Homeserver migration: When the default homeserver url is different from matrix.org, // the login (or forgot pwd) process with an existing matrix.org accounts will then fail. // Patch: Falling back to matrix.org HS so we don't break everyone's logins - if ([self.homeServerTextField.text isEqualToString:self.defaultHomeServerUrl] && ![self.defaultHomeServerUrl isEqualToString:@"https://matrix.org"]) + if ([self.homeServerTextField.text isEqualToString:self.defaultHomeServerUrl] && ![self.defaultHomeServerUrl isEqualToString:@"https://matrix.org"] && !self.softLogoutCredentials) { MXError *mxError = [[MXError alloc] initWithNSError:error]; From 3fc4ed9884b33e24726de5d153fefe4a37b17b1e Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 23 Jul 2019 17:15:16 +0200 Subject: [PATCH 08/75] Soft logout: Do not show the clear data section on the forgot password flow --- .../AuthenticationViewController.m | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index 2d3b1dcef..03d34371e 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -300,6 +300,7 @@ } [self updateForgotPwdButtonVisibility]; + [self updateSoftLogoutClearDataContainerVisibility]; } - (void)setAuthInputsView:(MXKAuthInputsView *)authInputsView @@ -416,10 +417,10 @@ self.rightBarButtonItem.title = nil; self.mainNavigationItem.title = NSLocalizedStringFromTable(@"auth_softlogout_signed_out", @"Vector", nil); - [self showLogoutClearDataContainer]; + [self showSoftLogoutClearDataContainer]; } -- (void)showLogoutClearDataContainer +- (void)showSoftLogoutClearDataContainer { NSMutableAttributedString *message = [[NSMutableAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"auth_softlogout_clear_data", @"Vector", nil) attributes:@{ @@ -442,6 +443,19 @@ [self refreshContentViewHeightConstraint]; } +- (void)updateSoftLogoutClearDataContainerVisibility +{ + // Do not display it in case of forget password flow + if (self.softLogoutCredentials && self.authType == MXKAuthenticationTypeLogin) + { + self.softLogoutClearDataContainer.hidden = NO; + } + else + { + self.softLogoutClearDataContainer.hidden = YES; + } +} + - (void)showClearDataAfterSoftLogoutConfirmation { // Request confirmation @@ -490,6 +504,7 @@ // Hide "Forgot password" and "Log in" buttons in case of SSO [self updateForgotPwdButtonVisibility]; + [self updateSoftLogoutClearDataContainerVisibility]; AuthInputsView *authInputsview; if ([self.authInputsView isKindOfClass:AuthInputsView.class]) @@ -626,6 +641,8 @@ { [super onButtonPressed:sender]; } + + [self updateSoftLogoutClearDataContainerVisibility]; } - (void)onFailureDuringAuthRequest:(NSError *)error From 45f864b90c3dd3e24fd9aee3ccfe621cca1a0186 Mon Sep 17 00:00:00 2001 From: manuroe Date: Tue, 23 Jul 2019 17:36:10 +0200 Subject: [PATCH 09/75] Soft logout: Display the message about keys only if there are keys not yet backed ip --- .../Authentication/Views/AuthInputsView.m | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/Riot/Modules/Authentication/Views/AuthInputsView.m b/Riot/Modules/Authentication/Views/AuthInputsView.m index 60b584f69..e7ec0c0e0 100644 --- a/Riot/Modules/Authentication/Views/AuthInputsView.m +++ b/Riot/Modules/Authentication/Views/AuthInputsView.m @@ -964,21 +964,25 @@ - (void)displaySoftLogoutMessage { - // Take some shortcuts and make some assumptions (Riot uses MXFileStore) to - // retrieve my user display name as quick as possible + // Take some shortcuts and make some assumptions (Riot uses MXFileStore and MXRealmCryptoStore) to + // retrieve data to display as quick as possible + MXRealmCryptoStore *cryptoStore = [[MXRealmCryptoStore alloc] initWithCredentials:self.softLogoutCredentials]; + BOOL keyBackupNeeded = [cryptoStore inboundGroupSessionsToBackup:1].count > 0; + MXFileStore *fileStore = [[MXFileStore alloc] initWithCredentials:softLogoutCredentials]; [fileStore asyncUsersWithUserIds:@[softLogoutCredentials.userId] success:^(NSArray * _Nonnull users) { MXUser *myUser = users.firstObject; - [self displaySoftLogoutMessageWithUserDisplayname:myUser.displayname]; + + [self displaySoftLogoutMessageWithUserDisplayname:myUser.displayname andKeyBackupNeeded:keyBackupNeeded]; } failure:^(NSError * _Nonnull error) { NSLog(@"[AuthInputsView] displaySoftLogoutMessage: Cannot load displayname. Error: %@", error); - [self displaySoftLogoutMessageWithUserDisplayname:nil]; + [self displaySoftLogoutMessageWithUserDisplayname:nil andKeyBackupNeeded:keyBackupNeeded]; }]; } -- (void)displaySoftLogoutMessageWithUserDisplayname:(NSString*)userDisplayname +- (void)displaySoftLogoutMessageWithUserDisplayname:(NSString*)userDisplayname andKeyBackupNeeded:(BOOL)keyBackupNeeded { // Use messageLabel for this message self.messageLabelTopConstraint.constant = 8; @@ -999,13 +1003,15 @@ NSFontAttributeName: [UIFont systemFontOfSize:14] }]]; - // TODO: Do not display this message if no e2e keys - [message appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\n"]]; - string = NSLocalizedStringFromTable(@"auth_softlogout_recover_encryption_keys", @"Vector", nil); - [message appendAttributedString:[[NSAttributedString alloc] initWithString:string - attributes:@{ - NSFontAttributeName: [UIFont systemFontOfSize:14] - }]]; + if (keyBackupNeeded) + { + [message appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\n"]]; + string = NSLocalizedStringFromTable(@"auth_softlogout_recover_encryption_keys", @"Vector", nil); + [message appendAttributedString:[[NSAttributedString alloc] initWithString:string + attributes:@{ + NSFontAttributeName: [UIFont systemFontOfSize:14] + }]]; + } self.messageLabel.attributedText = message; } From 3fa4de6722f5c9d03dad1bd9a3543e0ae3c8e4cd Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 24 Jul 2019 12:14:31 +0200 Subject: [PATCH 10/75] Soft logout: Retain MXFileStore instance while getting user display name --- Riot/Modules/Authentication/Views/AuthInputsView.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Modules/Authentication/Views/AuthInputsView.m b/Riot/Modules/Authentication/Views/AuthInputsView.m index e7ec0c0e0..64341f17d 100644 --- a/Riot/Modules/Authentication/Views/AuthInputsView.m +++ b/Riot/Modules/Authentication/Views/AuthInputsView.m @@ -973,6 +973,7 @@ [fileStore asyncUsersWithUserIds:@[softLogoutCredentials.userId] success:^(NSArray * _Nonnull users) { MXUser *myUser = users.firstObject; + [fileStore close]; [self displaySoftLogoutMessageWithUserDisplayname:myUser.displayname andKeyBackupNeeded:keyBackupNeeded]; From d126db5e49ac88149e6bb8ed48ae0de4b6e269a0 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 24 Jul 2019 14:59:40 +0200 Subject: [PATCH 11/75] Soft logout: SSO support: Display the clear data section upper --- .../AuthenticationViewController.m | 19 +++++++++++++++---- .../AuthenticationViewController.xib | 1 + 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index 03d34371e..d557ec443 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -502,16 +502,27 @@ { [super handleAuthenticationSession:authSession]; - // Hide "Forgot password" and "Log in" buttons in case of SSO - [self updateForgotPwdButtonVisibility]; - [self updateSoftLogoutClearDataContainerVisibility]; - AuthInputsView *authInputsview; if ([self.authInputsView isKindOfClass:AuthInputsView.class]) { authInputsview = (AuthInputsView*)self.authInputsView; } + + // Hide "Forgot password" and "Log in" buttons in case of SSO + [self updateForgotPwdButtonVisibility]; + [self updateSoftLogoutClearDataContainerVisibility]; + self.submitButton.hidden = authInputsview.isSingleSignOnRequired; + + // Bind ssoButton again if self.authInputsView has changed + [authInputsview.ssoButton addTarget:self action:@selector(onButtonPressed:) forControlEvents:UIControlEventTouchUpInside]; + + if (authInputsview.isSingleSignOnRequired && self.softLogoutCredentials) + { + // Remove submitButton so that the 2nd contraint on softLogoutClearDataContainer.top will be applied + // That makes softLogoutClearDataContainer appear upper in the screen + [self.submitButton removeFromSuperview]; + } } - (IBAction)onButtonPressed:(id)sender diff --git a/Riot/Modules/Authentication/AuthenticationViewController.xib b/Riot/Modules/Authentication/AuthenticationViewController.xib index aa204d4d7..9d131c9dd 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.xib +++ b/Riot/Modules/Authentication/AuthenticationViewController.xib @@ -401,6 +401,7 @@ Clear it if you're finished using this device, or want to sign in to another acc + From 9579b59bba29d327931a8f9222fe1f8757b3b4c6 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 24 Jul 2019 15:24:37 +0200 Subject: [PATCH 12/75] Soft logout: SSO support: Make sure the "Sign in with SSO" button does not overlap the explanation of the soft logout --- Riot/Modules/Authentication/Views/AuthInputsView.xib | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Riot/Modules/Authentication/Views/AuthInputsView.xib b/Riot/Modules/Authentication/Views/AuthInputsView.xib index f24ca9a98..d0164c007 100644 --- a/Riot/Modules/Authentication/Views/AuthInputsView.xib +++ b/Riot/Modules/Authentication/Views/AuthInputsView.xib @@ -1,11 +1,11 @@ - + - + @@ -15,7 +15,7 @@ - + + + + + + - + + + + diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewAction.swift b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewAction.swift index 3608a9187..d9210daff 100644 --- a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewAction.swift +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewAction.swift @@ -20,4 +20,5 @@ import UIKit enum ReactionsMenuViewAction { case loadData case tap(reaction: String) + case moreReactions } diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModel.swift b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModel.swift index c35678cdb..80ccb7122 100644 --- a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModel.swift +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModel.swift @@ -56,6 +56,8 @@ import Foundation self.coordinatorDelegate?.reactionsMenuViewModel(self, didAddReaction: reaction, forEventId: self.eventId) } } + case .moreReactions: + self.coordinatorDelegate?.reactionsMenuViewModelDidTapMoreReactions(self, forEventId: self.eventId) } } diff --git a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModelType.swift b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModelType.swift index 0e162f6c1..fee8b41d9 100644 --- a/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModelType.swift +++ b/Riot/Modules/Room/ContextualMenu/ReactionsMenu/ReactionsMenuViewModelType.swift @@ -23,6 +23,7 @@ protocol ReactionsMenuViewModelViewDelegate: class { @objc protocol ReactionsMenuViewModelCoordinatorDelegate: class { func reactionsMenuViewModel(_ viewModel: ReactionsMenuViewModel, didAddReaction reaction: String, forEventId eventId: String) func reactionsMenuViewModel(_ viewModel: ReactionsMenuViewModel, didRemoveReaction reaction: String, forEventId eventId: String) + func reactionsMenuViewModelDidTapMoreReactions(_ viewModel: ReactionsMenuViewModel, forEventId eventId: String) } protocol ReactionsMenuViewModelType { From 62dabebdd8c3ccbef4b32d156b03619b5c20de25 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 25 Jul 2019 16:22:17 +0200 Subject: [PATCH 15/75] Themes: Handle search bar text color. --- Riot/Categories/UISearchBar.swift | 25 +++++++++++++++++++ Riot/Managers/Theme/Themes/DarkTheme.swift | 6 ++++- Riot/Managers/Theme/Themes/DefaultTheme.swift | 4 +++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 Riot/Categories/UISearchBar.swift diff --git a/Riot/Categories/UISearchBar.swift b/Riot/Categories/UISearchBar.swift new file mode 100644 index 000000000..2158d20ba --- /dev/null +++ b/Riot/Categories/UISearchBar.swift @@ -0,0 +1,25 @@ +/* + 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 UIKit + +extension UISearchBar { + + /// Returns internal UITextField + var vc_searchTextField: UITextField? { + return self.value(forKey: "searchField") as? UITextField + } +} diff --git a/Riot/Managers/Theme/Themes/DarkTheme.swift b/Riot/Managers/Theme/Themes/DarkTheme.swift index c98b27a24..1fb141b3c 100644 --- a/Riot/Managers/Theme/Themes/DarkTheme.swift +++ b/Riot/Managers/Theme/Themes/DarkTheme.swift @@ -93,7 +93,11 @@ class DarkTheme: NSObject, Theme { func applyStyle(onSearchBar searchBar: UISearchBar) { searchBar.barStyle = .black searchBar.tintColor = self.searchPlaceholderColor - searchBar.barTintColor = self.headerBackgroundColor + searchBar.barTintColor = self.headerBackgroundColor + + if let searchBarTextField = searchBar.vc_searchTextField { + searchBarTextField.textColor = searchBar.tintColor + } } func applyStyle(onTextField texField: UITextField) { diff --git a/Riot/Managers/Theme/Themes/DefaultTheme.swift b/Riot/Managers/Theme/Themes/DefaultTheme.swift index 7f154f232..5d1fec222 100644 --- a/Riot/Managers/Theme/Themes/DefaultTheme.swift +++ b/Riot/Managers/Theme/Themes/DefaultTheme.swift @@ -94,6 +94,10 @@ class DefaultTheme: NSObject, Theme { searchBar.barStyle = .default searchBar.tintColor = self.searchPlaceholderColor searchBar.barTintColor = self.headerBackgroundColor + + if let searchBarTextField = searchBar.vc_searchTextField { + searchBarTextField.textColor = searchBar.tintColor + } } func applyStyle(onTextField texField: UITextField) { From 8dca707d8233f3f9fe7eb81c55974fefefc97403 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 25 Jul 2019 16:27:28 +0200 Subject: [PATCH 16/75] Create SerializationService that enables to deserialize or serialize an object conforming to Codable. --- .../Serialization/SerializationService.swift | 35 +++++++++++++++++++ .../SerializationServiceType.swift | 22 ++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 Riot/Managers/Serialization/SerializationService.swift create mode 100644 Riot/Managers/Serialization/SerializationServiceType.swift diff --git a/Riot/Managers/Serialization/SerializationService.swift b/Riot/Managers/Serialization/SerializationService.swift new file mode 100644 index 000000000..ca0dae0dd --- /dev/null +++ b/Riot/Managers/Serialization/SerializationService.swift @@ -0,0 +1,35 @@ +/* + 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 + +final class SerializationService: SerializationServiceType { + + // MARK: - Properties + + private let decoder = JSONDecoder() + private let encoder = JSONEncoder() + + // MARK: - Public + + func deserialize(_ data: Data) throws -> T { + return try decoder.decode(T.self, from: data) + } + + func serialize(_ object: T) throws -> Data { + return try encoder.encode(object) + } +} diff --git a/Riot/Managers/Serialization/SerializationServiceType.swift b/Riot/Managers/Serialization/SerializationServiceType.swift new file mode 100644 index 000000000..2a0d77e04 --- /dev/null +++ b/Riot/Managers/Serialization/SerializationServiceType.swift @@ -0,0 +1,22 @@ +/* + 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 SerializationServiceType { + func deserialize(_ data: Data) throws -> T + func serialize(_ object: T) throws -> Data +} From 01b9b483c6bdc91ce10b87172057bd2d519e6f2a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 25 Jul 2019 16:32:24 +0200 Subject: [PATCH 17/75] Emoji picker: Handle Emoji parsing with EmojiService. --- Riot/Assets/apple_emojis_data.json | 1 + Riot/Assets/en.lproj/Vector.strings | 12 +++ Riot/Generated/Strings.swift | 36 +++++++++ .../Room/EmojiPicker/Data/EmojiService.swift | 70 ++++++++++++++++++ .../EmojiPicker/Data/EmojiServiceType.swift | 21 ++++++ .../Data/JSON/EmojiItem+Decodable.swift | 74 +++++++++++++++++++ .../Data/JSON/EmojiJSONCategory.swift | 33 +++++++++ .../Data/JSON/EmojiJSONStore.swift | 65 ++++++++++++++++ .../Data/Store/EmojiCategory.swift | 27 +++++++ .../EmojiPicker/Data/Store/EmojiItem.swift | 41 ++++++++++ 10 files changed, 380 insertions(+) create mode 100644 Riot/Assets/apple_emojis_data.json create mode 100644 Riot/Modules/Room/EmojiPicker/Data/EmojiService.swift create mode 100644 Riot/Modules/Room/EmojiPicker/Data/EmojiServiceType.swift create mode 100644 Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiItem+Decodable.swift create mode 100644 Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONCategory.swift create mode 100644 Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONStore.swift create mode 100644 Riot/Modules/Room/EmojiPicker/Data/Store/EmojiCategory.swift create mode 100644 Riot/Modules/Room/EmojiPicker/Data/Store/EmojiItem.swift diff --git a/Riot/Assets/apple_emojis_data.json b/Riot/Assets/apple_emojis_data.json new file mode 100644 index 000000000..5af4e34d0 --- /dev/null +++ b/Riot/Assets/apple_emojis_data.json @@ -0,0 +1 @@ +{"compressed":true,"categories":[{"id":"people","name":"Smileys & People","emojis":["grinning","grin","joy","rolling_on_the_floor_laughing","smiley","smile","sweat_smile","laughing","wink","blush","yum","sunglasses","heart_eyes","kissing_heart","kissing","kissing_smiling_eyes","kissing_closed_eyes","relaxed","slightly_smiling_face","hugging_face","star-struck","thinking_face","face_with_raised_eyebrow","neutral_face","expressionless","no_mouth","face_with_rolling_eyes","smirk","persevere","disappointed_relieved","open_mouth","zipper_mouth_face","hushed","sleepy","tired_face","sleeping","relieved","stuck_out_tongue","stuck_out_tongue_winking_eye","stuck_out_tongue_closed_eyes","drooling_face","unamused","sweat","pensive","confused","upside_down_face","money_mouth_face","astonished","white_frowning_face","slightly_frowning_face","confounded","disappointed","worried","triumph","cry","sob","frowning","anguished","fearful","weary","exploding_head","grimacing","cold_sweat","scream","flushed","zany_face","dizzy_face","rage","angry","face_with_symbols_on_mouth","mask","face_with_thermometer","face_with_head_bandage","nauseated_face","face_vomiting","sneezing_face","innocent","face_with_cowboy_hat","clown_face","lying_face","shushing_face","face_with_hand_over_mouth","face_with_monocle","nerd_face","smiling_imp","imp","japanese_ogre","japanese_goblin","skull","skull_and_crossbones","ghost","alien","space_invader","robot_face","hankey","smiley_cat","smile_cat","joy_cat","heart_eyes_cat","smirk_cat","kissing_cat","scream_cat","crying_cat_face","pouting_cat","see_no_evil","hear_no_evil","speak_no_evil","baby","child","boy","girl","adult","man","woman","older_adult","older_man","older_woman","male-doctor","female-doctor","male-student","female-student","male-teacher","female-teacher","male-judge","female-judge","male-farmer","female-farmer","male-cook","female-cook","male-mechanic","female-mechanic","male-factory-worker","female-factory-worker","male-office-worker","female-office-worker","male-scientist","female-scientist","male-technologist","female-technologist","male-singer","female-singer","male-artist","female-artist","male-pilot","female-pilot","male-astronaut","female-astronaut","male-firefighter","female-firefighter","cop","male-police-officer","female-police-officer","sleuth_or_spy","male-detective","female-detective","guardsman","male-guard","female-guard","construction_worker","male-construction-worker","female-construction-worker","prince","princess","man_with_turban","man-wearing-turban","woman-wearing-turban","man_with_gua_pi_mao","person_with_headscarf","bearded_person","person_with_blond_hair","blond-haired-man","blond-haired-woman","man_in_tuxedo","bride_with_veil","pregnant_woman","breast-feeding","angel","santa","mrs_claus","mage","female_mage","male_mage","fairy","female_fairy","male_fairy","vampire","female_vampire","male_vampire","merperson","mermaid","merman","elf","female_elf","male_elf","genie","female_genie","male_genie","zombie","female_zombie","male_zombie","person_frowning","man-frowning","woman-frowning","person_with_pouting_face","man-pouting","woman-pouting","no_good","man-gesturing-no","woman-gesturing-no","ok_woman","man-gesturing-ok","woman-gesturing-ok","information_desk_person","man-tipping-hand","woman-tipping-hand","raising_hand","man-raising-hand","woman-raising-hand","bow","man-bowing","woman-bowing","face_palm","man-facepalming","woman-facepalming","shrug","man-shrugging","woman-shrugging","massage","man-getting-massage","woman-getting-massage","haircut","man-getting-haircut","woman-getting-haircut","walking","man-walking","woman-walking","runner","man-running","woman-running","dancer","man_dancing","dancers","man-with-bunny-ears-partying","woman-with-bunny-ears-partying","person_in_steamy_room","woman_in_steamy_room","man_in_steamy_room","person_climbing","woman_climbing","man_climbing","person_in_lotus_position","woman_in_lotus_position","man_in_lotus_position","bath","sleeping_accommodation","man_in_business_suit_levitating","speaking_head_in_silhouette","bust_in_silhouette","busts_in_silhouette","fencer","horse_racing","skier","snowboarder","golfer","man-golfing","woman-golfing","surfer","man-surfing","woman-surfing","rowboat","man-rowing-boat","woman-rowing-boat","swimmer","man-swimming","woman-swimming","person_with_ball","man-bouncing-ball","woman-bouncing-ball","weight_lifter","man-lifting-weights","woman-lifting-weights","bicyclist","man-biking","woman-biking","mountain_bicyclist","man-mountain-biking","woman-mountain-biking","racing_car","racing_motorcycle","person_doing_cartwheel","man-cartwheeling","woman-cartwheeling","wrestlers","man-wrestling","woman-wrestling","water_polo","man-playing-water-polo","woman-playing-water-polo","handball","man-playing-handball","woman-playing-handball","juggling","man-juggling","woman-juggling","couple","two_men_holding_hands","two_women_holding_hands","couplekiss","woman-kiss-man","man-kiss-man","woman-kiss-woman","couple_with_heart","woman-heart-man","man-heart-man","woman-heart-woman","family","man-woman-boy","man-woman-girl","man-woman-girl-boy","man-woman-boy-boy","man-woman-girl-girl","man-man-boy","man-man-girl","man-man-girl-boy","man-man-boy-boy","man-man-girl-girl","woman-woman-boy","woman-woman-girl","woman-woman-girl-boy","woman-woman-boy-boy","woman-woman-girl-girl","man-boy","man-boy-boy","man-girl","man-girl-boy","man-girl-girl","woman-boy","woman-boy-boy","woman-girl","woman-girl-boy","woman-girl-girl","selfie","muscle","point_left","point_right","point_up","point_up_2","middle_finger","point_down","v","crossed_fingers","spock-hand","the_horns","call_me_hand","raised_hand_with_fingers_splayed","hand","ok_hand","+1","-1","fist","facepunch","left-facing_fist","right-facing_fist","raised_back_of_hand","wave","i_love_you_hand_sign","writing_hand","clap","open_hands","raised_hands","palms_up_together","pray","handshake","nail_care","ear","nose","footprints","eyes","eye","eye-in-speech-bubble","brain","tongue","lips","kiss","cupid","heart","heartbeat","broken_heart","two_hearts","sparkling_heart","heartpulse","blue_heart","green_heart","yellow_heart","orange_heart","purple_heart","black_heart","gift_heart","revolving_hearts","heart_decoration","heavy_heart_exclamation_mark_ornament","love_letter","zzz","anger","bomb","boom","sweat_drops","dash","dizzy","speech_balloon","left_speech_bubble","right_anger_bubble","thought_balloon","hole","eyeglasses","dark_sunglasses","necktie","shirt","jeans","scarf","gloves","coat","socks","dress","kimono","bikini","womans_clothes","purse","handbag","pouch","shopping_bags","school_satchel","mans_shoe","athletic_shoe","high_heel","sandal","boot","crown","womans_hat","tophat","mortar_board","billed_cap","helmet_with_white_cross","prayer_beads","lipstick","ring","gem"]},{"id":"nature","name":"Animals & Nature","emojis":["monkey_face","monkey","gorilla","dog","dog2","poodle","wolf","fox_face","cat","cat2","lion_face","tiger","tiger2","leopard","horse","racehorse","unicorn_face","zebra_face","deer","cow","ox","water_buffalo","cow2","pig","pig2","boar","pig_nose","ram","sheep","goat","dromedary_camel","camel","giraffe_face","elephant","rhinoceros","mouse","mouse2","rat","hamster","rabbit","rabbit2","chipmunk","hedgehog","bat","bear","koala","panda_face","feet","turkey","chicken","rooster","hatching_chick","baby_chick","hatched_chick","bird","penguin","dove_of_peace","eagle","duck","owl","frog","crocodile","turtle","lizard","snake","dragon_face","dragon","sauropod","t-rex","whale","whale2","dolphin","fish","tropical_fish","blowfish","shark","octopus","shell","crab","shrimp","squid","snail","butterfly","bug","ant","bee","beetle","cricket","spider","spider_web","scorpion","bouquet","cherry_blossom","white_flower","rosette","rose","wilted_flower","hibiscus","sunflower","blossom","tulip","seedling","evergreen_tree","deciduous_tree","palm_tree","cactus","ear_of_rice","herb","shamrock","four_leaf_clover","maple_leaf","fallen_leaf","leaves"]},{"id":"foods","name":"Food & Drink","emojis":["grapes","melon","watermelon","tangerine","lemon","banana","pineapple","apple","green_apple","pear","peach","cherries","strawberry","kiwifruit","tomato","coconut","avocado","eggplant","potato","carrot","corn","hot_pepper","cucumber","broccoli","mushroom","peanuts","chestnut","bread","croissant","baguette_bread","pretzel","pancakes","cheese_wedge","meat_on_bone","poultry_leg","cut_of_meat","bacon","hamburger","fries","pizza","hotdog","sandwich","taco","burrito","stuffed_flatbread","egg","fried_egg","shallow_pan_of_food","stew","bowl_with_spoon","green_salad","popcorn","canned_food","bento","rice_cracker","rice_ball","rice","curry","ramen","spaghetti","sweet_potato","oden","sushi","fried_shrimp","fish_cake","dango","dumpling","fortune_cookie","takeout_box","icecream","shaved_ice","ice_cream","doughnut","cookie","birthday","cake","pie","chocolate_bar","candy","lollipop","custard","honey_pot","baby_bottle","glass_of_milk","coffee","tea","sake","champagne","wine_glass","cocktail","tropical_drink","beer","beers","clinking_glasses","tumbler_glass","cup_with_straw","chopsticks","knife_fork_plate","fork_and_knife","spoon","hocho","amphora"]},{"id":"activity","name":"Activities","emojis":["jack_o_lantern","christmas_tree","fireworks","sparkler","sparkles","balloon","tada","confetti_ball","tanabata_tree","bamboo","dolls","flags","wind_chime","rice_scene","ribbon","gift","reminder_ribbon","admission_tickets","ticket","medal","trophy","sports_medal","first_place_medal","second_place_medal","third_place_medal","soccer","baseball","basketball","volleyball","football","rugby_football","tennis","8ball","bowling","cricket_bat_and_ball","field_hockey_stick_and_ball","ice_hockey_stick_and_puck","table_tennis_paddle_and_ball","badminton_racquet_and_shuttlecock","boxing_glove","martial_arts_uniform","goal_net","dart","golf","ice_skate","fishing_pole_and_fish","running_shirt_with_sash","ski","sled","curling_stone","video_game","joystick","game_die","spades","hearts","diamonds","clubs","black_joker","mahjong","flower_playing_cards"]},{"id":"places","name":"Travel & Places","emojis":["earth_africa","earth_americas","earth_asia","globe_with_meridians","world_map","japan","snow_capped_mountain","mountain","volcano","mount_fuji","camping","beach_with_umbrella","desert","desert_island","national_park","stadium","classical_building","building_construction","house_buildings","cityscape","derelict_house_building","house","house_with_garden","office","post_office","european_post_office","hospital","bank","hotel","love_hotel","convenience_store","school","department_store","factory","japanese_castle","european_castle","wedding","tokyo_tower","statue_of_liberty","church","mosque","synagogue","shinto_shrine","kaaba","fountain","tent","foggy","night_with_stars","sunrise_over_mountains","sunrise","city_sunset","city_sunrise","bridge_at_night","hotsprings","milky_way","carousel_horse","ferris_wheel","roller_coaster","barber","circus_tent","performing_arts","frame_with_picture","art","slot_machine","steam_locomotive","railway_car","bullettrain_side","bullettrain_front","train2","metro","light_rail","station","tram","monorail","mountain_railway","train","bus","oncoming_bus","trolleybus","minibus","ambulance","fire_engine","police_car","oncoming_police_car","taxi","oncoming_taxi","car","oncoming_automobile","blue_car","truck","articulated_lorry","tractor","bike","scooter","motor_scooter","busstop","motorway","railway_track","fuelpump","rotating_light","traffic_light","vertical_traffic_light","construction","octagonal_sign","anchor","boat","canoe","speedboat","passenger_ship","ferry","motor_boat","ship","airplane","small_airplane","airplane_departure","airplane_arriving","seat","helicopter","suspension_railway","mountain_cableway","aerial_tramway","satellite","rocket","flying_saucer","bellhop_bell","door","bed","couch_and_lamp","toilet","shower","bathtub","hourglass","hourglass_flowing_sand","watch","alarm_clock","stopwatch","timer_clock","mantelpiece_clock","clock12","clock1230","clock1","clock130","clock2","clock230","clock3","clock330","clock4","clock430","clock5","clock530","clock6","clock630","clock7","clock730","clock8","clock830","clock9","clock930","clock10","clock1030","clock11","clock1130","new_moon","waxing_crescent_moon","first_quarter_moon","moon","full_moon","waning_gibbous_moon","last_quarter_moon","waning_crescent_moon","crescent_moon","new_moon_with_face","first_quarter_moon_with_face","last_quarter_moon_with_face","thermometer","sunny","full_moon_with_face","sun_with_face","star","star2","stars","cloud","partly_sunny","thunder_cloud_and_rain","mostly_sunny","barely_sunny","partly_sunny_rain","rain_cloud","snow_cloud","lightning","tornado","fog","wind_blowing_face","cyclone","rainbow","closed_umbrella","umbrella","umbrella_with_rain_drops","umbrella_on_ground","zap","snowflake","snowman","snowman_without_snow","comet","fire","droplet","ocean"]},{"id":"objects","name":"Objects","emojis":["mute","speaker","sound","loud_sound","loudspeaker","mega","postal_horn","bell","no_bell","musical_score","musical_note","notes","studio_microphone","level_slider","control_knobs","microphone","headphones","radio","saxophone","guitar","musical_keyboard","trumpet","violin","drum_with_drumsticks","iphone","calling","phone","telephone_receiver","pager","fax","battery","electric_plug","computer","desktop_computer","printer","keyboard","three_button_mouse","trackball","minidisc","floppy_disk","cd","dvd","movie_camera","film_frames","film_projector","clapper","tv","camera","camera_with_flash","video_camera","vhs","mag","mag_right","microscope","telescope","satellite_antenna","candle","bulb","flashlight","izakaya_lantern","notebook_with_decorative_cover","closed_book","book","green_book","blue_book","orange_book","books","notebook","ledger","page_with_curl","scroll","page_facing_up","newspaper","rolled_up_newspaper","bookmark_tabs","bookmark","label","moneybag","yen","dollar","euro","pound","money_with_wings","credit_card","chart","currency_exchange","heavy_dollar_sign","email","e-mail","incoming_envelope","envelope_with_arrow","outbox_tray","inbox_tray","package","mailbox","mailbox_closed","mailbox_with_mail","mailbox_with_no_mail","postbox","ballot_box_with_ballot","pencil2","black_nib","lower_left_fountain_pen","lower_left_ballpoint_pen","lower_left_paintbrush","lower_left_crayon","memo","briefcase","file_folder","open_file_folder","card_index_dividers","date","calendar","spiral_note_pad","spiral_calendar_pad","card_index","chart_with_upwards_trend","chart_with_downwards_trend","bar_chart","clipboard","pushpin","round_pushpin","paperclip","linked_paperclips","straight_ruler","triangular_ruler","scissors","card_file_box","file_cabinet","wastebasket","lock","unlock","lock_with_ink_pen","closed_lock_with_key","key","old_key","hammer","pick","hammer_and_pick","hammer_and_wrench","dagger_knife","crossed_swords","gun","bow_and_arrow","shield","wrench","nut_and_bolt","gear","compression","alembic","scales","link","chains","syringe","pill","smoking","coffin","funeral_urn","moyai","oil_drum","crystal_ball","shopping_trolley"]},{"id":"symbols","name":"Symbols","emojis":["atm","put_litter_in_its_place","potable_water","wheelchair","mens","womens","restroom","baby_symbol","wc","passport_control","customs","baggage_claim","left_luggage","warning","children_crossing","no_entry","no_entry_sign","no_bicycles","no_smoking","do_not_litter","non-potable_water","no_pedestrians","no_mobile_phones","underage","radioactive_sign","biohazard_sign","arrow_up","arrow_upper_right","arrow_right","arrow_lower_right","arrow_down","arrow_lower_left","arrow_left","arrow_upper_left","arrow_up_down","left_right_arrow","leftwards_arrow_with_hook","arrow_right_hook","arrow_heading_up","arrow_heading_down","arrows_clockwise","arrows_counterclockwise","back","end","on","soon","top","place_of_worship","atom_symbol","om_symbol","star_of_david","wheel_of_dharma","yin_yang","latin_cross","orthodox_cross","star_and_crescent","peace_symbol","menorah_with_nine_branches","six_pointed_star","aries","taurus","gemini","cancer","leo","virgo","libra","scorpius","sagittarius","capricorn","aquarius","pisces","ophiuchus","twisted_rightwards_arrows","repeat","repeat_one","arrow_forward","fast_forward","black_right_pointing_double_triangle_with_vertical_bar","black_right_pointing_triangle_with_double_vertical_bar","arrow_backward","rewind","black_left_pointing_double_triangle_with_vertical_bar","arrow_up_small","arrow_double_up","arrow_down_small","arrow_double_down","double_vertical_bar","black_square_for_stop","black_circle_for_record","eject","cinema","low_brightness","high_brightness","signal_strength","vibration_mode","mobile_phone_off","recycle","fleur_de_lis","trident","name_badge","beginner","o","white_check_mark","ballot_box_with_check","heavy_check_mark","heavy_multiplication_x","x","negative_squared_cross_mark","heavy_plus_sign","heavy_minus_sign","heavy_division_sign","curly_loop","loop","part_alternation_mark","eight_spoked_asterisk","eight_pointed_black_star","sparkle","bangbang","interrobang","question","grey_question","grey_exclamation","exclamation","wavy_dash","copyright","registered","tm","hash","keycap_star","zero","one","two","three","four","five","six","seven","eight","nine","keycap_ten","100","capital_abcd","abcd","1234","symbols","abc","a","ab","b","cl","cool","free","information_source","id","m","new","ng","o2","ok","parking","sos","up","vs","koko","sa","u6708","u6709","u6307","ideograph_advantage","u5272","u7121","u7981","accept","u7533","u5408","u7a7a","congratulations","secret","u55b6","u6e80","black_small_square","white_small_square","white_medium_square","black_medium_square","white_medium_small_square","black_medium_small_square","black_large_square","white_large_square","large_orange_diamond","large_blue_diamond","small_orange_diamond","small_blue_diamond","small_red_triangle","small_red_triangle_down","diamond_shape_with_a_dot_inside","radio_button","black_square_button","white_square_button","white_circle","black_circle","red_circle","large_blue_circle"]},{"id":"flags","name":"Flags","emojis":["checkered_flag","cn","crossed_flags","de","es","flag-ac","flag-ad","flag-ae","flag-af","flag-ag","flag-ai","flag-al","flag-am","flag-ao","flag-aq","flag-ar","flag-as","flag-at","flag-au","flag-aw","flag-ax","flag-az","flag-ba","flag-bb","flag-bd","flag-be","flag-bf","flag-bg","flag-bh","flag-bi","flag-bj","flag-bl","flag-bm","flag-bn","flag-bo","flag-bq","flag-br","flag-bs","flag-bt","flag-bv","flag-bw","flag-by","flag-bz","flag-ca","flag-cc","flag-cd","flag-cf","flag-cg","flag-ch","flag-ci","flag-ck","flag-cl","flag-cm","flag-co","flag-cp","flag-cr","flag-cu","flag-cv","flag-cw","flag-cx","flag-cy","flag-cz","flag-dg","flag-dj","flag-dk","flag-dm","flag-do","flag-dz","flag-ea","flag-ec","flag-ee","flag-eg","flag-eh","flag-england","flag-er","flag-et","flag-eu","flag-fi","flag-fj","flag-fk","flag-fm","flag-fo","flag-ga","flag-gd","flag-ge","flag-gf","flag-gg","flag-gh","flag-gi","flag-gl","flag-gm","flag-gn","flag-gp","flag-gq","flag-gr","flag-gs","flag-gt","flag-gu","flag-gw","flag-gy","flag-hk","flag-hm","flag-hn","flag-hr","flag-ht","flag-hu","flag-ic","flag-id","flag-ie","flag-il","flag-im","flag-in","flag-io","flag-iq","flag-ir","flag-is","flag-je","flag-jm","flag-jo","flag-ke","flag-kg","flag-kh","flag-ki","flag-km","flag-kn","flag-kp","flag-kw","flag-ky","flag-kz","flag-la","flag-lb","flag-lc","flag-li","flag-lk","flag-lr","flag-ls","flag-lt","flag-lu","flag-lv","flag-ly","flag-ma","flag-mc","flag-md","flag-me","flag-mf","flag-mg","flag-mh","flag-mk","flag-ml","flag-mm","flag-mn","flag-mo","flag-mp","flag-mq","flag-mr","flag-ms","flag-mt","flag-mu","flag-mv","flag-mw","flag-mx","flag-my","flag-mz","flag-na","flag-nc","flag-ne","flag-nf","flag-ng","flag-ni","flag-nl","flag-no","flag-np","flag-nr","flag-nu","flag-nz","flag-om","flag-pa","flag-pe","flag-pf","flag-pg","flag-ph","flag-pk","flag-pl","flag-pm","flag-pn","flag-pr","flag-ps","flag-pt","flag-pw","flag-py","flag-qa","flag-re","flag-ro","flag-rs","flag-rw","flag-sa","flag-sb","flag-sc","flag-scotland","flag-sd","flag-se","flag-sg","flag-sh","flag-si","flag-sj","flag-sk","flag-sl","flag-sm","flag-sn","flag-so","flag-sr","flag-ss","flag-st","flag-sv","flag-sx","flag-sy","flag-sz","flag-ta","flag-tc","flag-td","flag-tf","flag-tg","flag-th","flag-tj","flag-tk","flag-tl","flag-tm","flag-tn","flag-to","flag-tr","flag-tt","flag-tv","flag-tw","flag-tz","flag-ua","flag-ug","flag-um","flag-uy","flag-uz","flag-va","flag-vc","flag-ve","flag-vg","flag-vi","flag-vn","flag-vu","flag-wales","flag-wf","flag-ws","flag-xk","flag-ye","flag-yt","flag-za","flag-zm","flag-zw","fr","gb","it","jp","kr","rainbow-flag","ru","triangular_flag_on_post","us","waving_black_flag","waving_white_flag"]}],"emojis":{"100":{"a":"Hundred Points Symbol","b":"1F4AF","j":["score","perfect","numbers","century","exam","quiz","test","pass","hundred"],"k":[25,26]},"1234":{"a":"Input Symbol for Numbers","b":"1F522","j":["numbers","blue-square"],"k":[27,36]},"monkey_face":{"a":"Monkey Face","b":"1F435","j":["animal","nature","circus"],"k":[13,31],"l":[":o)"]},"grinning":{"a":"Grinning Face","b":"1F600","j":["face","smile","happy","joy",":D","grin"],"k":[30,24],"m":":D"},"earth_africa":{"a":"Earth Globe Europe-Africa","b":"1F30D","j":["globe","world","international"],"k":[6,5]},"checkered_flag":{"a":"Chequered Flag","b":"1F3C1","j":["contest","finishline","race","gokart"],"k":[9,27]},"mute":{"a":"Speaker with Cancellation Stroke","b":"1F507","j":["sound","volume","silence","quiet"],"k":[27,9]},"jack_o_lantern":{"a":"Jack-O-Lantern","b":"1F383","j":["halloween","light","pumpkin","creepy","fall"],"k":[8,17]},"atm":{"a":"Automated Teller Machine","b":"1F3E7","j":["money","sales","cash","blue-square","payment","bank"],"k":[12,4]},"grapes":{"a":"Grapes","b":"1F347","j":["fruit","food","wine"],"k":[7,9]},"earth_americas":{"a":"Earth Globe Americas","b":"1F30E","j":["globe","world","USA","international"],"k":[6,6]},"grin":{"a":"Grinning Face with Smiling Eyes","b":"1F601","j":["face","happy","smile","joy","kawaii"],"k":[30,25]},"melon":{"a":"Melon","b":"1F348","j":["fruit","nature","food"],"k":[7,10]},"triangular_flag_on_post":{"a":"Triangular Flag on Post","b":"1F6A9","j":["mark","milestone","place"],"k":[35,14]},"monkey":{"a":"Monkey","b":"1F412","j":["animal","nature","banana","circus"],"k":[12,48]},"christmas_tree":{"a":"Christmas Tree","b":"1F384","j":["festival","vacation","december","xmas","celebration"],"k":[8,18]},"put_litter_in_its_place":{"a":"Put Litter in Its Place Symbol","b":"1F6AE","j":["blue-square","sign","human","info"],"k":[35,19]},"speaker":{"a":"Speaker","b":"1F508","j":["sound","volume","silence","broadcast"],"k":[27,10]},"earth_asia":{"a":"Earth Globe Asia-Australia","b":"1F30F","j":["globe","world","east","international"],"k":[6,7]},"crossed_flags":{"a":"Crossed Flags","b":"1F38C","j":["japanese","nation","country","border"],"k":[8,31]},"joy":{"a":"Face with Tears of Joy","b":"1F602","j":["face","cry","tears","weep","happy","happytears","haha"],"k":[30,26]},"sound":{"a":"Speaker with One Sound Wave","b":"1F509","j":["volume","speaker","broadcast"],"k":[27,11]},"watermelon":{"a":"Watermelon","b":"1F349","j":["fruit","food","picnic","summer"],"k":[7,11]},"gorilla":{"a":"Gorilla","b":"1F98D","j":["animal","nature","circus"],"k":[42,37],"o":9},"fireworks":{"a":"Fireworks","b":"1F386","j":["photo","festival","carnival","congratulations"],"k":[8,25]},"potable_water":{"a":"Potable Water Symbol","b":"1F6B0","j":["blue-square","liquid","restroom","cleaning","faucet"],"k":[35,21]},"wheelchair":{"a":"Wheelchair Symbol","b":"267F","j":["blue-square","disabled","a11y","accessibility"],"k":[48,10],"o":4},"rolling_on_the_floor_laughing":{"a":"Rolling on the Floor Laughing","b":"1F923","k":[38,26],"o":9},"loud_sound":{"a":"Speaker with Three Sound Waves","b":"1F50A","j":["volume","noise","noisy","speaker","broadcast"],"k":[27,12]},"waving_black_flag":{"a":"Waving Black Flag","b":"1F3F4","k":[12,19],"o":7},"tangerine":{"a":"Tangerine","b":"1F34A","j":["food","fruit","nature","orange"],"k":[7,12]},"dog":{"a":"Dog Face","b":"1F436","j":["animal","friend","nature","woof","puppy","pet","faithful"],"k":[13,32]},"sparkler":{"a":"Firework Sparkler","b":"1F387","j":["stars","night","shine"],"k":[8,26]},"globe_with_meridians":{"a":"Globe with Meridians","b":"1F310","j":["earth","international","world","internet","interweb","i18n"],"k":[6,8]},"smiley":{"a":"Smiling Face with Open Mouth","b":"1F603","j":["face","happy","joy","haha",":D",":)","smile","funny"],"k":[30,27],"l":["=)","=-)"],"m":":)"},"loudspeaker":{"a":"Public Address Loudspeaker","b":"1F4E2","j":["volume","sound"],"k":[26,25]},"sparkles":{"a":"Sparkles","b":"2728","j":["stars","shine","shiny","cool","awesome","good","magic"],"k":[49,48]},"dog2":{"a":"Dog","b":"1F415","j":["animal","nature","friend","doge","pet","faithful"],"k":[12,51]},"waving_white_flag":{"a":"Waving White Flag","b":"1F3F3-FE0F","c":"1F3F3","k":[12,15],"o":7},"world_map":{"a":"World Map","b":"1F5FA-FE0F","c":"1F5FA","j":["location","direction"],"k":[30,18],"o":7},"lemon":{"a":"Lemon","b":"1F34B","j":["fruit","nature"],"k":[7,13]},"mens":{"a":"Mens Symbol","b":"1F6B9","j":["toilet","restroom","wc","blue-square","gender","male"],"k":[36,29]},"womens":{"a":"Womens Symbol","b":"1F6BA","j":["purple-square","woman","female","toilet","loo","restroom","gender"],"k":[36,30]},"rainbow-flag":{"a":"Rainbow Flag","b":"1F3F3-FE0F-200D-1F308","c":"1F3F3-200D-1F308","k":[12,14],"o":7},"smile":{"a":"Smiling Face with Open Mouth and Smiling Eyes","b":"1F604","j":["face","happy","joy","funny","haha","laugh","like",":D",":)"],"k":[30,28],"l":["C:","c:",":D",":-D"],"m":":)"},"banana":{"a":"Banana","b":"1F34C","j":["fruit","food","monkey"],"k":[7,14]},"mega":{"a":"Cheering Megaphone","b":"1F4E3","j":["sound","speaker","volume"],"k":[26,26]},"japan":{"a":"Silhouette of Japan","b":"1F5FE","j":["nation","country","japanese","asia"],"k":[30,22]},"poodle":{"a":"Poodle","b":"1F429","j":["dog","animal","101","nature","pet"],"k":[13,19]},"balloon":{"a":"Balloon","b":"1F388","j":["party","celebration","birthday","circus"],"k":[8,27]},"flag-ac":{"a":"Ascension Island Flag","b":"1F1E6-1F1E8","k":[0,31]},"sweat_smile":{"a":"Smiling Face with Open Mouth and Cold Sweat","b":"1F605","j":["face","hot","happy","laugh","sweat","smile","relief"],"k":[30,29]},"pineapple":{"a":"Pineapple","b":"1F34D","j":["fruit","nature","food"],"k":[7,15]},"restroom":{"a":"Restroom","b":"1F6BB","j":["blue-square","toilet","refresh","wc","gender"],"k":[36,31]},"postal_horn":{"a":"Postal Horn","b":"1F4EF","j":["instrument","music"],"k":[26,38]},"wolf":{"a":"Wolf Face","b":"1F43A","j":["animal","nature","wild"],"k":[13,36]},"tada":{"a":"Party Popper","b":"1F389","j":["party","congratulations","birthday","magic","circus","celebration"],"k":[8,28]},"snow_capped_mountain":{"a":"Snow Capped Mountain","b":"1F3D4-FE0F","c":"1F3D4","k":[11,37],"o":7},"laughing":{"a":"Smiling Face with Open Mouth and Tightly-Closed Eyes","b":"1F606","j":["happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],"k":[30,30],"l":[":>",":->"],"n":["satisfied"]},"apple":{"a":"Red Apple","b":"1F34E","j":["fruit","mac","school"],"k":[7,16]},"flag-ad":{"a":"Andorra Flag","b":"1F1E6-1F1E9","k":[0,32]},"fox_face":{"a":"Fox Face","b":"1F98A","j":["animal","nature","face"],"k":[42,34],"o":9},"confetti_ball":{"a":"Confetti Ball","b":"1F38A","j":["festival","party","birthday","circus"],"k":[8,29]},"bell":{"a":"Bell","b":"1F514","j":["sound","notification","christmas","xmas","chime"],"k":[27,22]},"mountain":{"a":"Mountain","b":"26F0-FE0F","c":"26F0","j":["photo","nature","environment"],"k":[48,38],"o":5},"baby_symbol":{"a":"Baby Symbol","b":"1F6BC","j":["orange-square","child"],"k":[36,32]},"wc":{"a":"Water Closet","b":"1F6BE","j":["toilet","restroom","blue-square"],"k":[36,34]},"wink":{"a":"Winking Face","b":"1F609","j":["face","happy","mischievous","secret",";)","smile","eye"],"k":[30,33],"l":[";)",";-)"],"m":";)"},"no_bell":{"a":"Bell with Cancellation Stroke","b":"1F515","j":["sound","volume","mute","quiet","silent"],"k":[27,23]},"green_apple":{"a":"Green Apple","b":"1F34F","j":["fruit","nature"],"k":[7,17]},"tanabata_tree":{"a":"Tanabata Tree","b":"1F38B","j":["plant","nature","branch","summer"],"k":[8,30]},"flag-ae":{"a":"United Arab Emirates Flag","b":"1F1E6-1F1EA","k":[0,33]},"volcano":{"a":"Volcano","b":"1F30B","j":["photo","nature","disaster"],"k":[6,3]},"cat":{"a":"Cat Face","b":"1F431","j":["animal","meow","nature","pet","kitten"],"k":[13,27]},"flag-af":{"a":"Afghanistan Flag","b":"1F1E6-1F1EB","k":[0,34]},"musical_score":{"a":"Musical Score","b":"1F3BC","j":["treble","clef","compose"],"k":[9,22]},"blush":{"a":"Smiling Face with Smiling Eyes","b":"1F60A","j":["face","smile","happy","flushed","crush","embarrassed","shy","joy"],"k":[30,34],"m":":)"},"pear":{"a":"Pear","b":"1F350","j":["fruit","nature","food"],"k":[7,18]},"bamboo":{"a":"Pine Decoration","b":"1F38D","j":["plant","nature","vegetable","panda","pine_decoration"],"k":[8,32]},"passport_control":{"a":"Passport Control","b":"1F6C2","j":["custom","blue-square"],"k":[36,43]},"mount_fuji":{"a":"Mount Fuji","b":"1F5FB","j":["photo","mountain","nature","japanese"],"k":[30,19]},"cat2":{"a":"Cat","b":"1F408","j":["animal","meow","pet","cats"],"k":[12,38]},"musical_note":{"a":"Musical Note","b":"1F3B5","j":["score","tone","sound"],"k":[9,15]},"dolls":{"a":"Japanese Dolls","b":"1F38E","j":["japanese","toy","kimono"],"k":[8,33]},"lion_face":{"a":"Lion Face","b":"1F981","k":[42,25],"o":8},"camping":{"a":"Camping","b":"1F3D5-FE0F","c":"1F3D5","j":["photo","outdoors","tent"],"k":[11,38],"o":7},"flag-ag":{"a":"Antigua & Barbuda Flag","b":"1F1E6-1F1EC","k":[0,35]},"customs":{"a":"Customs","b":"1F6C3","j":["passport","border","blue-square"],"k":[36,44]},"yum":{"a":"Face Savouring Delicious Food","b":"1F60B","j":["happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"],"k":[30,35]},"peach":{"a":"Peach","b":"1F351","j":["fruit","nature","food"],"k":[7,19]},"tiger":{"a":"Tiger Face","b":"1F42F","j":["animal","cat","danger","wild","nature","roar"],"k":[13,25]},"notes":{"a":"Multiple Musical Notes","b":"1F3B6","j":["music","score"],"k":[9,16]},"flags":{"a":"Carp Streamer","b":"1F38F","j":["fish","japanese","koinobori","carp","banner"],"k":[8,34]},"beach_with_umbrella":{"a":"Beach with Umbrella","b":"1F3D6-FE0F","c":"1F3D6","k":[11,39],"o":7},"cherries":{"a":"Cherries","b":"1F352","j":["food","fruit"],"k":[7,20]},"flag-ai":{"a":"Anguilla Flag","b":"1F1E6-1F1EE","k":[0,36]},"baggage_claim":{"a":"Baggage Claim","b":"1F6C4","j":["blue-square","airport","transport"],"k":[36,45]},"sunglasses":{"a":"Smiling Face with Sunglasses","b":"1F60E","j":["face","cool","smile","summer","beach","sunglass"],"k":[30,38],"l":["8)"]},"left_luggage":{"a":"Left Luggage","b":"1F6C5","j":["blue-square","travel"],"k":[36,46]},"wind_chime":{"a":"Wind Chime","b":"1F390","j":["nature","ding","spring","bell"],"k":[8,35]},"strawberry":{"a":"Strawberry","b":"1F353","j":["fruit","food","nature"],"k":[7,21]},"desert":{"a":"Desert","b":"1F3DC-FE0F","c":"1F3DC","j":["photo","warm","saharah"],"k":[11,45],"o":7},"studio_microphone":{"a":"Studio Microphone","b":"1F399-FE0F","c":"1F399","j":["sing","recording","artist","talkshow"],"k":[8,41],"o":7},"flag-al":{"a":"Albania Flag","b":"1F1E6-1F1F1","k":[0,37]},"tiger2":{"a":"Tiger","b":"1F405","j":["animal","nature","roar"],"k":[12,35]},"heart_eyes":{"a":"Smiling Face with Heart-Shaped Eyes","b":"1F60D","j":["face","love","like","affection","valentines","infatuation","crush","heart"],"k":[30,37]},"desert_island":{"a":"Desert Island","b":"1F3DD-FE0F","c":"1F3DD","j":["photo","tropical","mojito"],"k":[11,46],"o":7},"kiwifruit":{"a":"Kiwifruit","b":"1F95D","k":[42,9],"o":9},"rice_scene":{"a":"Moon Viewing Ceremony","b":"1F391","j":["photo","japan","asia","tsukimi"],"k":[8,36]},"kissing_heart":{"a":"Face Throwing a Kiss","b":"1F618","j":["face","love","like","affection","valentines","infatuation","kiss"],"k":[30,48],"l":[":*",":-*"]},"warning":{"a":"Warning Sign","b":"26A0-FE0F","c":"26A0","j":["exclamation","wip","alert","error","problem","issue"],"k":[48,20],"o":4},"flag-am":{"a":"Armenia Flag","b":"1F1E6-1F1F2","k":[0,38]},"leopard":{"a":"Leopard","b":"1F406","j":["animal","nature"],"k":[12,36]},"level_slider":{"a":"Level Slider","b":"1F39A-FE0F","c":"1F39A","j":["scale"],"k":[8,42],"o":7},"horse":{"a":"Horse Face","b":"1F434","j":["animal","brown","nature"],"k":[13,30]},"children_crossing":{"a":"Children Crossing","b":"1F6B8","j":["school","warning","danger","sign","driving","yellow-diamond"],"k":[36,28]},"ribbon":{"a":"Ribbon","b":"1F380","j":["decoration","pink","girl","bowtie"],"k":[8,14]},"national_park":{"a":"National Park","b":"1F3DE-FE0F","c":"1F3DE","j":["photo","environment","nature"],"k":[11,47],"o":7},"control_knobs":{"a":"Control Knobs","b":"1F39B-FE0F","c":"1F39B","j":["dial"],"k":[8,43],"o":7},"kissing":{"a":"Kissing Face","b":"1F617","j":["love","like","face","3","valentines","infatuation","kiss"],"k":[30,47]},"tomato":{"a":"Tomato","b":"1F345","j":["fruit","vegetable","nature","food"],"k":[7,7]},"flag-ao":{"a":"Angola Flag","b":"1F1E6-1F1F4","k":[0,39]},"stadium":{"a":"Stadium","b":"1F3DF-FE0F","c":"1F3DF","j":["photo","place","sports","concert","venue"],"k":[11,48],"o":7},"flag-aq":{"a":"Antarctica Flag","b":"1F1E6-1F1F6","k":[0,40]},"gift":{"a":"Wrapped Present","b":"1F381","j":["present","birthday","christmas","xmas"],"k":[8,15]},"no_entry":{"a":"No Entry","b":"26D4","j":["limit","security","privacy","bad","denied","stop","circle"],"k":[48,35],"o":5},"kissing_smiling_eyes":{"a":"Kissing Face with Smiling Eyes","b":"1F619","j":["face","affection","valentines","infatuation","kiss"],"k":[30,49]},"coconut":{"a":"Coconut","b":"1F965","k":[42,17],"o":10},"racehorse":{"a":"Horse","b":"1F40E","j":["animal","gamble","luck"],"k":[12,44]},"microphone":{"a":"Microphone","b":"1F3A4","j":["sound","music","PA","sing","talkshow"],"k":[8,50]},"classical_building":{"a":"Classical Building","b":"1F3DB-FE0F","c":"1F3DB","j":["art","culture","history"],"k":[11,44],"o":7},"no_entry_sign":{"a":"No Entry Sign","b":"1F6AB","j":["forbid","stop","limit","denied","disallow","circle"],"k":[35,16]},"reminder_ribbon":{"a":"Reminder Ribbon","b":"1F397-FE0F","c":"1F397","j":["sports","cause","support","awareness"],"k":[8,40],"o":7},"kissing_closed_eyes":{"a":"Kissing Face with Closed Eyes","b":"1F61A","j":["face","love","like","affection","valentines","infatuation","kiss"],"k":[30,50]},"unicorn_face":{"a":"Unicorn Face","b":"1F984","k":[42,28],"o":8},"flag-ar":{"a":"Argentina Flag","b":"1F1E6-1F1F7","k":[0,41]},"headphones":{"a":"Headphone","b":"1F3A7","j":["music","score","gadgets"],"k":[9,1]},"avocado":{"a":"Avocado","b":"1F951","j":["fruit","food"],"k":[41,49],"o":9},"relaxed":{"a":"White Smiling Face","b":"263A-FE0F","c":"263A","j":["face","blush","massage","happiness"],"k":[47,41],"o":1},"zebra_face":{"a":"Zebra Face","b":"1F993","k":[42,43],"o":10},"eggplant":{"a":"Aubergine","b":"1F346","j":["vegetable","nature","food","aubergine"],"k":[7,8]},"radio":{"a":"Radio","b":"1F4FB","j":["communication","music","podcast","program"],"k":[26,50]},"building_construction":{"a":"Building Construction","b":"1F3D7-FE0F","c":"1F3D7","j":["wip","working","progress"],"k":[11,40],"o":7},"flag-as":{"a":"American Samoa Flag","b":"1F1E6-1F1F8","k":[0,42]},"admission_tickets":{"a":"Admission Tickets","b":"1F39F-FE0F","c":"1F39F","k":[8,45],"o":7},"no_bicycles":{"a":"No Bicycles","b":"1F6B3","j":["cyclist","prohibited","circle"],"k":[35,24]},"no_smoking":{"a":"No Smoking Symbol","b":"1F6AD","j":["cigarette","blue-square","smell","smoke"],"k":[35,18]},"slightly_smiling_face":{"a":"Slightly Smiling Face","b":"1F642","j":["face","smile"],"k":[31,38],"l":[":)","(:",":-)"],"o":7},"flag-at":{"a":"Austria Flag","b":"1F1E6-1F1F9","k":[0,43]},"ticket":{"a":"Ticket","b":"1F3AB","j":["event","concert","pass"],"k":[9,5]},"saxophone":{"a":"Saxophone","b":"1F3B7","j":["music","instrument","jazz","blues"],"k":[9,17]},"deer":{"a":"Deer","b":"1F98C","j":["animal","nature","horns","venison"],"k":[42,36],"o":9},"house_buildings":{"a":"House Buildings","b":"1F3D8-FE0F","c":"1F3D8","k":[11,41],"o":7},"potato":{"a":"Potato","b":"1F954","j":["food","tuber","vegatable","starch"],"k":[42,0],"o":9},"guitar":{"a":"Guitar","b":"1F3B8","j":["music","instrument"],"k":[9,18]},"carrot":{"a":"Carrot","b":"1F955","j":["vegetable","food","orange"],"k":[42,1],"o":9},"cityscape":{"a":"Cityscape","b":"1F3D9-FE0F","c":"1F3D9","j":["photo","night life","urban"],"k":[11,42],"o":7},"flag-au":{"a":"Australia Flag","b":"1F1E6-1F1FA","k":[0,44]},"do_not_litter":{"a":"Do Not Litter Symbol","b":"1F6AF","j":["trash","bin","garbage","circle"],"k":[35,20]},"hugging_face":{"a":"Hugging Face","b":"1F917","k":[37,31],"o":8},"cow":{"a":"Cow Face","b":"1F42E","j":["beef","ox","animal","nature","moo","milk"],"k":[13,24]},"medal":{"a":"Medal","b":"1F396-FE0F","c":"1F396","k":[8,39],"o":7},"musical_keyboard":{"a":"Musical Keyboard","b":"1F3B9","j":["piano","instrument","compose"],"k":[9,19]},"corn":{"a":"Ear of Maize","b":"1F33D","j":["food","vegetable","plant"],"k":[6,51]},"derelict_house_building":{"a":"Derelict House Building","b":"1F3DA-FE0F","c":"1F3DA","k":[11,43],"o":7},"non-potable_water":{"a":"Non-Potable Water Symbol","b":"1F6B1","j":["drink","faucet","tap","circle"],"k":[35,22]},"trophy":{"a":"Trophy","b":"1F3C6","j":["win","award","contest","place","ftw","ceremony"],"k":[10,19]},"flag-aw":{"a":"Aruba Flag","b":"1F1E6-1F1FC","k":[0,45]},"star-struck":{"a":"Grinning Face with Star Eyes","b":"1F929","k":[38,49],"n":["grinning_face_with_star_eyes"],"o":10},"ox":{"a":"Ox","b":"1F402","j":["animal","cow","beef"],"k":[12,32]},"trumpet":{"a":"Trumpet","b":"1F3BA","j":["music","brass"],"k":[9,20]},"hot_pepper":{"a":"Hot Pepper","b":"1F336-FE0F","c":"1F336","j":["food","spicy","chilli","chili"],"k":[6,44],"o":7},"sports_medal":{"a":"Sports Medal","b":"1F3C5","k":[10,18],"o":7},"flag-ax":{"a":"Åland Islands Flag","b":"1F1E6-1F1FD","k":[0,46]},"water_buffalo":{"a":"Water Buffalo","b":"1F403","j":["animal","nature","ox","cow"],"k":[12,33]},"no_pedestrians":{"a":"No Pedestrians","b":"1F6B7","j":["rules","crossing","walking","circle"],"k":[36,27]},"thinking_face":{"a":"Thinking Face","b":"1F914","k":[37,28],"o":8},"house":{"a":"House Building","b":"1F3E0","j":["building","home"],"k":[11,49]},"no_mobile_phones":{"a":"No Mobile Phones","b":"1F4F5","j":["iphone","mute","circle"],"k":[26,44]},"flag-az":{"a":"Azerbaijan Flag","b":"1F1E6-1F1FF","k":[0,47]},"first_place_medal":{"a":"First Place Medal","b":"1F947","k":[41,42],"o":9},"house_with_garden":{"a":"House with Garden","b":"1F3E1","j":["home","plant","nature"],"k":[11,50]},"violin":{"a":"Violin","b":"1F3BB","j":["music","instrument","orchestra","symphony"],"k":[9,21]},"face_with_raised_eyebrow":{"a":"Face with One Eyebrow Raised","b":"1F928","k":[38,48],"n":["face_with_one_eyebrow_raised"],"o":10},"cucumber":{"a":"Cucumber","b":"1F952","j":["fruit","food","pickle"],"k":[41,50],"o":9},"cow2":{"a":"Cow","b":"1F404","j":["beef","ox","animal","nature","moo","milk"],"k":[12,34]},"flag-ba":{"a":"Bosnia & Herzegovina Flag","b":"1F1E7-1F1E6","k":[0,48]},"pig":{"a":"Pig Face","b":"1F437","j":["animal","oink","nature"],"k":[13,33]},"drum_with_drumsticks":{"a":"Drum with Drumsticks","b":"1F941","k":[41,37],"o":9},"underage":{"a":"No One Under Eighteen Symbol","b":"1F51E","j":["18","drink","pub","night","minor","circle"],"k":[27,32]},"broccoli":{"a":"Broccoli","b":"1F966","k":[42,18],"o":10},"office":{"a":"Office Building","b":"1F3E2","j":["building","bureau","work"],"k":[11,51]},"second_place_medal":{"a":"Second Place Medal","b":"1F948","k":[41,43],"o":9},"neutral_face":{"a":"Neutral Face","b":"1F610","j":["indifference","meh",":|","neutral"],"k":[30,40],"l":[":|",":-|"]},"third_place_medal":{"a":"Third Place Medal","b":"1F949","k":[41,44],"o":9},"mushroom":{"a":"Mushroom","b":"1F344","j":["plant","vegetable"],"k":[7,6]},"flag-bb":{"a":"Barbados Flag","b":"1F1E7-1F1E7","k":[0,49]},"radioactive_sign":{"a":"Radioactive Sign","b":"2622-FE0F","c":"2622","k":[47,33],"o":1},"pig2":{"a":"Pig","b":"1F416","j":["animal","nature"],"k":[13,0]},"expressionless":{"a":"Expressionless Face","b":"1F611","j":["face","indifferent","-_-","meh","deadpan"],"k":[30,41]},"iphone":{"a":"Mobile Phone","b":"1F4F1","j":["technology","apple","gadgets","dial"],"k":[26,40]},"post_office":{"a":"Japanese Post Office","b":"1F3E3","j":["building","envelope","communication"],"k":[12,0]},"european_post_office":{"a":"European Post Office","b":"1F3E4","j":["building","email"],"k":[12,1]},"soccer":{"a":"Soccer Ball","b":"26BD","j":["sports","football"],"k":[48,26],"o":5},"boar":{"a":"Boar","b":"1F417","j":["animal","nature"],"k":[13,1]},"peanuts":{"a":"Peanuts","b":"1F95C","j":["food","nut"],"k":[42,8],"o":9},"calling":{"a":"Mobile Phone with Rightwards Arrow at Left","b":"1F4F2","j":["iphone","incoming"],"k":[26,41]},"biohazard_sign":{"a":"Biohazard Sign","b":"2623-FE0F","c":"2623","k":[47,34],"o":1},"flag-bd":{"a":"Bangladesh Flag","b":"1F1E7-1F1E9","k":[0,50]},"no_mouth":{"a":"Face Without Mouth","b":"1F636","j":["face","hellokitty"],"k":[31,26]},"face_with_rolling_eyes":{"a":"Face with Rolling Eyes","b":"1F644","k":[31,40],"o":8},"phone":{"a":"Black Telephone","b":"260E-FE0F","c":"260E","j":["technology","communication","dial","telephone"],"k":[47,21],"n":["telephone"],"o":1},"pig_nose":{"a":"Pig Nose","b":"1F43D","j":["animal","oink"],"k":[13,39]},"chestnut":{"a":"Chestnut","b":"1F330","j":["food","squirrel"],"k":[6,38]},"arrow_up":{"a":"Upwards Black Arrow","b":"2B06-FE0F","c":"2B06","j":["blue-square","continue","top","direction"],"k":[50,18],"o":4},"hospital":{"a":"Hospital","b":"1F3E5","j":["building","health","surgery","doctor"],"k":[12,2]},"flag-be":{"a":"Belgium Flag","b":"1F1E7-1F1EA","k":[0,51]},"baseball":{"a":"Baseball","b":"26BE","j":["sports","balls"],"k":[48,27],"o":5},"smirk":{"a":"Smirking Face","b":"1F60F","j":["face","smile","mean","prank","smug","sarcasm"],"k":[30,39]},"arrow_upper_right":{"a":"North East Arrow","b":"2197-FE0F","c":"2197","j":["blue-square","point","direction","diagonal","northeast"],"k":[46,36],"o":1},"flag-bf":{"a":"Burkina Faso Flag","b":"1F1E7-1F1EB","k":[1,0]},"basketball":{"a":"Basketball and Hoop","b":"1F3C0","j":["sports","balls","NBA"],"k":[9,26]},"ram":{"a":"Ram","b":"1F40F","j":["animal","sheep","nature"],"k":[12,45]},"bank":{"a":"Bank","b":"1F3E6","j":["building","money","sales","cash","business","enterprise"],"k":[12,3]},"bread":{"a":"Bread","b":"1F35E","j":["food","wheat","breakfast","toast"],"k":[7,32]},"telephone_receiver":{"a":"Telephone Receiver","b":"1F4DE","j":["technology","communication","dial"],"k":[26,21]},"croissant":{"a":"Croissant","b":"1F950","j":["food","bread","french"],"k":[41,48],"o":9},"pager":{"a":"Pager","b":"1F4DF","j":["bbcall","oldschool","90s"],"k":[26,22]},"sheep":{"a":"Sheep","b":"1F411","j":["animal","nature","wool","shipit"],"k":[12,47]},"arrow_right":{"a":"Black Rightwards Arrow","b":"27A1-FE0F","c":"27A1","j":["blue-square","next"],"k":[50,12],"o":1},"persevere":{"a":"Persevering Face","b":"1F623","j":["face","sick","no","upset","oops"],"k":[31,7]},"flag-bg":{"a":"Bulgaria Flag","b":"1F1E7-1F1EC","k":[1,1]},"volleyball":{"a":"Volleyball","b":"1F3D0","j":["sports","balls"],"k":[11,33],"o":8},"hotel":{"a":"Hotel","b":"1F3E8","j":["building","accomodation","checkin"],"k":[12,5]},"arrow_lower_right":{"a":"South East Arrow","b":"2198-FE0F","c":"2198","j":["blue-square","direction","diagonal","southeast"],"k":[46,37],"o":1},"goat":{"a":"Goat","b":"1F410","j":["animal","nature"],"k":[12,46]},"flag-bh":{"a":"Bahrain Flag","b":"1F1E7-1F1ED","k":[1,2]},"love_hotel":{"a":"Love Hotel","b":"1F3E9","j":["like","affection","dating"],"k":[12,6]},"disappointed_relieved":{"a":"Disappointed but Relieved Face","b":"1F625","j":["face","phew","sweat","nervous"],"k":[31,9]},"baguette_bread":{"a":"Baguette Bread","b":"1F956","j":["food","bread","french"],"k":[42,2],"o":9},"football":{"a":"American Football","b":"1F3C8","j":["sports","balls","NFL"],"k":[10,26]},"fax":{"a":"Fax Machine","b":"1F4E0","j":["communication","technology"],"k":[26,23]},"convenience_store":{"a":"Convenience Store","b":"1F3EA","j":["building","shopping","groceries"],"k":[12,7]},"dromedary_camel":{"a":"Dromedary Camel","b":"1F42A","j":["animal","hot","desert","hump"],"k":[13,20]},"arrow_down":{"a":"Downwards Black Arrow","b":"2B07-FE0F","c":"2B07","j":["blue-square","direction","bottom"],"k":[50,19],"o":4},"battery":{"a":"Battery","b":"1F50B","j":["power","energy","sustain"],"k":[27,13]},"rugby_football":{"a":"Rugby Football","b":"1F3C9","j":["sports","team"],"k":[10,27]},"pretzel":{"a":"Pretzel","b":"1F968","k":[42,20],"o":10},"open_mouth":{"a":"Face with Open Mouth","b":"1F62E","j":["face","surprise","impressed","wow","whoa",":O"],"k":[31,18],"l":[":o",":-o",":O",":-O"]},"flag-bi":{"a":"Burundi Flag","b":"1F1E7-1F1EE","k":[1,3]},"flag-bj":{"a":"Benin Flag","b":"1F1E7-1F1EF","k":[1,4]},"pancakes":{"a":"Pancakes","b":"1F95E","j":["food","breakfast","flapjacks","hotcakes"],"k":[42,10],"o":9},"school":{"a":"School","b":"1F3EB","j":["building","student","education","learn","teach"],"k":[12,8]},"tennis":{"a":"Tennis Racquet and Ball","b":"1F3BE","j":["sports","balls","green"],"k":[9,24]},"zipper_mouth_face":{"a":"Zipper-Mouth Face","b":"1F910","j":["face","sealed","zipper","secret"],"k":[37,24],"o":8},"camel":{"a":"Bactrian Camel","b":"1F42B","j":["animal","nature","hot","desert","hump"],"k":[13,21]},"arrow_lower_left":{"a":"South West Arrow","b":"2199-FE0F","c":"2199","j":["blue-square","direction","diagonal","southwest"],"k":[46,38],"o":1},"electric_plug":{"a":"Electric Plug","b":"1F50C","j":["charger","power"],"k":[27,14]},"cheese_wedge":{"a":"Cheese Wedge","b":"1F9C0","k":[42,48],"o":8},"hushed":{"a":"Hushed Face","b":"1F62F","j":["face","woo","shh"],"k":[31,19]},"computer":{"a":"Personal Computer","b":"1F4BB","j":["technology","laptop","screen","display","monitor"],"k":[25,38]},"giraffe_face":{"a":"Giraffe Face","b":"1F992","k":[42,42],"o":10},"8ball":{"a":"Billiards","b":"1F3B1","j":["pool","hobby","game","luck","magic"],"k":[9,11]},"flag-bl":{"a":"St. Barthélemy Flag","b":"1F1E7-1F1F1","k":[1,5]},"arrow_left":{"a":"Leftwards Black Arrow","b":"2B05-FE0F","c":"2B05","j":["blue-square","previous","back"],"k":[50,17],"o":4},"department_store":{"a":"Department Store","b":"1F3EC","j":["building","shopping","mall"],"k":[12,9]},"meat_on_bone":{"a":"Meat on Bone","b":"1F356","j":["good","food","drumstick"],"k":[7,24]},"arrow_upper_left":{"a":"North West Arrow","b":"2196-FE0F","c":"2196","j":["blue-square","point","direction","diagonal","northwest"],"k":[46,35],"o":1},"flag-bm":{"a":"Bermuda Flag","b":"1F1E7-1F1F2","k":[1,6]},"sleepy":{"a":"Sleepy Face","b":"1F62A","j":["face","tired","rest","nap"],"k":[31,14]},"bowling":{"a":"Bowling","b":"1F3B3","j":["sports","fun","play"],"k":[9,13]},"factory":{"a":"Factory","b":"1F3ED","j":["building","industry","pollution","smoke"],"k":[12,10]},"desktop_computer":{"a":"Desktop Computer","b":"1F5A5-FE0F","c":"1F5A5","j":["technology","computing","screen"],"k":[29,51],"o":7},"elephant":{"a":"Elephant","b":"1F418","j":["animal","nature","nose","th","circus"],"k":[13,2]},"rhinoceros":{"a":"Rhinoceros","b":"1F98F","j":["animal","nature","horn"],"k":[42,39],"o":9},"arrow_up_down":{"a":"Up Down Arrow","b":"2195-FE0F","c":"2195","j":["blue-square","direction","way","vertical"],"k":[46,34],"o":1},"cricket_bat_and_ball":{"a":"Cricket Bat and Ball","b":"1F3CF","k":[11,32],"o":8},"printer":{"a":"Printer","b":"1F5A8-FE0F","c":"1F5A8","j":["paper","ink"],"k":[30,0],"o":7},"poultry_leg":{"a":"Poultry Leg","b":"1F357","j":["food","meat","drumstick","bird","chicken","turkey"],"k":[7,25]},"tired_face":{"a":"Tired Face","b":"1F62B","j":["sick","whine","upset","frustrated"],"k":[31,15]},"japanese_castle":{"a":"Japanese Castle","b":"1F3EF","j":["photo","building"],"k":[12,12]},"flag-bn":{"a":"Brunei Flag","b":"1F1E7-1F1F3","k":[1,7]},"field_hockey_stick_and_ball":{"a":"Field Hockey Stick and Ball","b":"1F3D1","k":[11,34],"o":8},"sleeping":{"a":"Sleeping Face","b":"1F634","j":["face","tired","sleepy","night","zzz"],"k":[31,24]},"left_right_arrow":{"a":"Left Right Arrow","b":"2194-FE0F","c":"2194","j":["shape","direction","horizontal","sideways"],"k":[46,33],"o":1},"keyboard":{"a":"Keyboard","b":"2328-FE0F","c":"2328","j":["technology","computer","type","input","text"],"k":[46,43],"o":1},"european_castle":{"a":"European Castle","b":"1F3F0","j":["building","royalty","history"],"k":[12,13]},"mouse":{"a":"Mouse Face","b":"1F42D","j":["animal","nature","cheese_wedge","rodent"],"k":[13,23]},"flag-bo":{"a":"Bolivia Flag","b":"1F1E7-1F1F4","k":[1,8]},"cut_of_meat":{"a":"Cut of Meat","b":"1F969","k":[42,21],"o":10},"ice_hockey_stick_and_puck":{"a":"Ice Hockey Stick and Puck","b":"1F3D2","k":[11,35],"o":8},"mouse2":{"a":"Mouse","b":"1F401","j":["animal","nature","rodent"],"k":[12,31]},"three_button_mouse":{"a":"Three Button Mouse","b":"1F5B1-FE0F","c":"1F5B1","k":[30,1],"o":7},"leftwards_arrow_with_hook":{"a":"Leftwards Arrow with Hook","b":"21A9-FE0F","c":"21A9","j":["back","return","blue-square","undo","enter"],"k":[46,39],"o":1},"bacon":{"a":"Bacon","b":"1F953","j":["food","breakfast","pork","pig","meat"],"k":[41,51],"o":9},"relieved":{"a":"Relieved Face","b":"1F60C","j":["face","relaxed","phew","massage","happiness"],"k":[30,36]},"flag-bq":{"a":"Caribbean Netherlands Flag","b":"1F1E7-1F1F6","k":[1,9]},"wedding":{"a":"Wedding","b":"1F492","j":["love","like","affection","couple","marriage","bride","groom"],"k":[24,44]},"tokyo_tower":{"a":"Tokyo Tower","b":"1F5FC","j":["photo","japanese"],"k":[30,20]},"arrow_right_hook":{"a":"Rightwards Arrow with Hook","b":"21AA-FE0F","c":"21AA","j":["blue-square","return","rotate","direction"],"k":[46,40],"o":1},"hamburger":{"a":"Hamburger","b":"1F354","j":["meat","fast food","beef","cheeseburger","mcdonalds","burger king"],"k":[7,22]},"stuck_out_tongue":{"a":"Face with Stuck-out Tongue","b":"1F61B","j":["face","prank","childish","playful","mischievous","smile","tongue"],"k":[30,51],"l":[":p",":-p",":P",":-P",":b",":-b"],"m":":p"},"trackball":{"a":"Trackball","b":"1F5B2-FE0F","c":"1F5B2","j":["technology","trackpad"],"k":[30,2],"o":7},"flag-br":{"a":"Brazil Flag","b":"1F1E7-1F1F7","k":[1,10]},"rat":{"a":"Rat","b":"1F400","j":["animal","mouse","rodent"],"k":[12,30]},"table_tennis_paddle_and_ball":{"a":"Table Tennis Paddle and Ball","b":"1F3D3","k":[11,36],"o":8},"minidisc":{"a":"Minidisc","b":"1F4BD","j":["technology","record","data","disk","90s"],"k":[25,40]},"stuck_out_tongue_winking_eye":{"a":"Face with Stuck-out Tongue and Winking Eye","b":"1F61C","j":["face","prank","childish","playful","mischievous","smile","wink","tongue"],"k":[31,0],"l":[";p",";-p",";b",";-b",";P",";-P"],"m":";p"},"fries":{"a":"French Fries","b":"1F35F","j":["chips","snack","fast food"],"k":[7,33]},"badminton_racquet_and_shuttlecock":{"a":"Badminton Racquet and Shuttlecock","b":"1F3F8","k":[12,22],"o":8},"statue_of_liberty":{"a":"Statue of Liberty","b":"1F5FD","j":["american","newyork"],"k":[30,21]},"flag-bs":{"a":"Bahamas Flag","b":"1F1E7-1F1F8","k":[1,11]},"arrow_heading_up":{"a":"Arrow Pointing Rightwards Then Curving Upwards","b":"2934-FE0F","c":"2934","j":["blue-square","direction","top"],"k":[50,15],"o":3},"hamster":{"a":"Hamster Face","b":"1F439","j":["animal","nature"],"k":[13,35]},"stuck_out_tongue_closed_eyes":{"a":"Face with Stuck-out Tongue and Tightly-Closed Eyes","b":"1F61D","j":["face","prank","playful","mischievous","smile","tongue"],"k":[31,1]},"pizza":{"a":"Slice of Pizza","b":"1F355","j":["food","party"],"k":[7,23]},"boxing_glove":{"a":"Boxing Glove","b":"1F94A","j":["sports","fighting"],"k":[41,45],"o":9},"floppy_disk":{"a":"Floppy Disk","b":"1F4BE","j":["oldschool","technology","save","90s","80s"],"k":[25,41]},"arrow_heading_down":{"a":"Arrow Pointing Rightwards Then Curving Downwards","b":"2935-FE0F","c":"2935","j":["blue-square","direction","bottom"],"k":[50,16],"o":3},"flag-bt":{"a":"Bhutan Flag","b":"1F1E7-1F1F9","k":[1,12]},"rabbit":{"a":"Rabbit Face","b":"1F430","j":["animal","nature","pet","spring","magic","bunny"],"k":[13,26]},"church":{"a":"Church","b":"26EA","j":["building","religion","christ"],"k":[48,37],"o":5},"drooling_face":{"a":"Drooling Face","b":"1F924","j":["face"],"k":[38,27],"o":9},"flag-bv":{"a":"Bouvet Island Flag","b":"1F1E7-1F1FB","k":[1,13]},"mosque":{"a":"Mosque","b":"1F54C","j":["islam","worship","minaret"],"k":[28,15],"o":8},"rabbit2":{"a":"Rabbit","b":"1F407","j":["animal","nature","pet","magic","spring"],"k":[12,37]},"hotdog":{"a":"Hot Dog","b":"1F32D","j":["food","frankfurter"],"k":[6,35],"o":8},"martial_arts_uniform":{"a":"Martial Arts Uniform","b":"1F94B","j":["judo","karate","taekwondo"],"k":[41,46],"o":9},"arrows_clockwise":{"a":"Clockwise Downwards and Upwards Open Circle Arrows","b":"1F503","j":["sync","cycle","round","repeat"],"k":[27,5]},"cd":{"a":"Optical Disc","b":"1F4BF","j":["technology","dvd","disk","disc","90s"],"k":[25,42]},"arrows_counterclockwise":{"a":"Anticlockwise Downwards and Upwards Open Circle Arrows","b":"1F504","j":["blue-square","sync","cycle"],"k":[27,6]},"sandwich":{"a":"Sandwich","b":"1F96A","k":[42,22],"o":10},"chipmunk":{"a":"Chipmunk","b":"1F43F-FE0F","c":"1F43F","j":["animal","nature","rodent","squirrel"],"k":[13,41],"o":7},"synagogue":{"a":"Synagogue","b":"1F54D","j":["judaism","worship","temple","jewish"],"k":[28,16],"o":8},"unamused":{"a":"Unamused Face","b":"1F612","j":["indifference","bored","straight face","serious","sarcasm"],"k":[30,42],"m":":("},"goal_net":{"a":"Goal Net","b":"1F945","j":["sports"],"k":[41,41],"o":9},"flag-bw":{"a":"Botswana Flag","b":"1F1E7-1F1FC","k":[1,14]},"dvd":{"a":"Dvd","b":"1F4C0","j":["cd","disk","disc"],"k":[25,43]},"hedgehog":{"a":"Hedgehog","b":"1F994","k":[42,44],"o":10},"dart":{"a":"Direct Hit","b":"1F3AF","j":["game","play","bar"],"k":[9,9]},"taco":{"a":"Taco","b":"1F32E","j":["food","mexican"],"k":[6,36],"o":8},"back":{"a":"Back with Leftwards Arrow Above","b":"1F519","j":["arrow","words","return"],"k":[27,27]},"flag-by":{"a":"Belarus Flag","b":"1F1E7-1F1FE","k":[1,15]},"shinto_shrine":{"a":"Shinto Shrine","b":"26E9-FE0F","c":"26E9","j":["temple","japan","kyoto"],"k":[48,36],"o":5},"movie_camera":{"a":"Movie Camera","b":"1F3A5","j":["film","record"],"k":[8,51]},"sweat":{"a":"Face with Cold Sweat","b":"1F613","j":["face","hot","sad","tired","exercise"],"k":[30,43]},"burrito":{"a":"Burrito","b":"1F32F","j":["food","mexican"],"k":[6,37],"o":8},"flag-bz":{"a":"Belize Flag","b":"1F1E7-1F1FF","k":[1,16]},"pensive":{"a":"Pensive Face","b":"1F614","j":["face","sad","depressed","upset"],"k":[30,44]},"kaaba":{"a":"Kaaba","b":"1F54B","j":["mecca","mosque","islam"],"k":[28,14],"o":8},"film_frames":{"a":"Film Frames","b":"1F39E-FE0F","c":"1F39E","k":[8,44],"o":7},"bat":{"a":"Bat","b":"1F987","j":["animal","nature","blind","vampire"],"k":[42,31],"o":9},"golf":{"a":"Flag in Hole","b":"26F3","j":["sports","business","flag","hole","summer"],"k":[48,41],"o":5},"end":{"a":"End with Leftwards Arrow Above","b":"1F51A","j":["words","arrow"],"k":[27,28]},"film_projector":{"a":"Film Projector","b":"1F4FD-FE0F","c":"1F4FD","j":["video","tape","record","movie"],"k":[27,0],"o":7},"bear":{"a":"Bear Face","b":"1F43B","j":["animal","nature","wild"],"k":[13,37]},"ice_skate":{"a":"Ice Skate","b":"26F8-FE0F","c":"26F8","j":["sports"],"k":[48,45],"o":5},"fountain":{"a":"Fountain","b":"26F2","j":["photo","summer","water","fresh"],"k":[48,40],"o":5},"confused":{"a":"Confused Face","b":"1F615","j":["face","indifference","huh","weird","hmmm",":/"],"k":[30,45],"l":[":\\",":-\\",":/",":-/"]},"flag-ca":{"a":"Canada Flag","b":"1F1E8-1F1E6","k":[1,17]},"on":{"a":"On with Exclamation Mark with Left Right Arrow Above","b":"1F51B","j":["arrow","words"],"k":[27,29]},"stuffed_flatbread":{"a":"Stuffed Flatbread","b":"1F959","j":["food","flatbread","stuffed","gyro"],"k":[42,5],"o":9},"soon":{"a":"Soon with Rightwards Arrow Above","b":"1F51C","j":["arrow","words"],"k":[27,30]},"upside_down_face":{"a":"Upside-Down Face","b":"1F643","j":["face","flipped","silly","smile"],"k":[31,39],"o":8},"fishing_pole_and_fish":{"a":"Fishing Pole and Fish","b":"1F3A3","j":["food","hobby","summer"],"k":[8,49]},"tent":{"a":"Tent","b":"26FA","j":["photo","camping","outdoors"],"k":[49,12],"o":5},"clapper":{"a":"Clapper Board","b":"1F3AC","j":["movie","film","record"],"k":[9,6]},"egg":{"a":"Egg","b":"1F95A","j":["food","chicken","breakfast"],"k":[42,6],"o":9},"flag-cc":{"a":"Cocos (keeling) Islands Flag","b":"1F1E8-1F1E8","k":[1,18]},"koala":{"a":"Koala","b":"1F428","j":["animal","nature"],"k":[13,18]},"foggy":{"a":"Foggy","b":"1F301","j":["photo","mountain"],"k":[5,45]},"tv":{"a":"Television","b":"1F4FA","j":["technology","program","oldschool","show","television"],"k":[26,49]},"panda_face":{"a":"Panda Face","b":"1F43C","j":["animal","nature","panda"],"k":[13,38]},"fried_egg":{"a":"Cooking","b":"1F373","j":["food","breakfast","kitchen","egg"],"k":[8,1],"n":["cooking"]},"top":{"a":"Top with Upwards Arrow Above","b":"1F51D","j":["words","blue-square"],"k":[27,31]},"flag-cd":{"a":"Congo - Kinshasa Flag","b":"1F1E8-1F1E9","k":[1,19]},"money_mouth_face":{"a":"Money-Mouth Face","b":"1F911","j":["face","rich","dollar","money"],"k":[37,25],"o":8},"running_shirt_with_sash":{"a":"Running Shirt with Sash","b":"1F3BD","j":["play","pageant"],"k":[9,23]},"astonished":{"a":"Astonished Face","b":"1F632","j":["face","xox","surprised","poisoned"],"k":[31,22]},"feet":{"a":"Paw Prints","b":"1F43E","k":[13,40],"n":["paw_prints"]},"camera":{"a":"Camera","b":"1F4F7","j":["gadgets","photography"],"k":[26,46]},"flag-cf":{"a":"Central African Republic Flag","b":"1F1E8-1F1EB","k":[1,20]},"place_of_worship":{"a":"Place of Worship","b":"1F6D0","j":["religion","church","temple","prayer"],"k":[37,5],"o":8},"night_with_stars":{"a":"Night with Stars","b":"1F303","j":["evening","city","downtown"],"k":[5,47]},"ski":{"a":"Ski and Ski Boot","b":"1F3BF","j":["sports","winter","cold","snow"],"k":[9,25]},"shallow_pan_of_food":{"a":"Shallow Pan of Food","b":"1F958","j":["food","cooking","casserole","paella"],"k":[42,4],"o":9},"camera_with_flash":{"a":"Camera with Flash","b":"1F4F8","k":[26,47],"o":7},"sunrise_over_mountains":{"a":"Sunrise over Mountains","b":"1F304","j":["view","vacation","photo"],"k":[5,48]},"turkey":{"a":"Turkey","b":"1F983","j":["animal","bird"],"k":[42,27],"o":8},"white_frowning_face":{"a":"White Frowning Face","b":"2639-FE0F","c":"2639","k":[47,40],"o":1},"flag-cg":{"a":"Congo - Brazzaville Flag","b":"1F1E8-1F1EC","k":[1,21]},"stew":{"a":"Pot of Food","b":"1F372","j":["food","meat","soup"],"k":[8,0]},"sled":{"a":"Sled","b":"1F6F7","k":[37,22],"o":10},"atom_symbol":{"a":"Atom Symbol","b":"269B-FE0F","c":"269B","j":["science","physics","chemistry"],"k":[48,18],"o":4},"curling_stone":{"a":"Curling Stone","b":"1F94C","k":[41,47],"o":10},"slightly_frowning_face":{"a":"Slightly Frowning Face","b":"1F641","j":["face","frowning","disappointed","sad","upset"],"k":[31,37],"o":7},"sunrise":{"a":"Sunrise","b":"1F305","j":["morning","view","vacation","photo"],"k":[5,49]},"om_symbol":{"a":"Om Symbol","b":"1F549-FE0F","c":"1F549","k":[28,12],"o":7},"chicken":{"a":"Chicken","b":"1F414","j":["animal","cluck","nature","bird"],"k":[12,50]},"bowl_with_spoon":{"a":"Bowl with Spoon","b":"1F963","k":[42,15],"o":10},"flag-ch":{"a":"Switzerland Flag","b":"1F1E8-1F1ED","k":[1,22]},"video_camera":{"a":"Video Camera","b":"1F4F9","j":["film","record"],"k":[26,48]},"video_game":{"a":"Video Game","b":"1F3AE","j":["play","console","PS4","controller"],"k":[9,8]},"rooster":{"a":"Rooster","b":"1F413","j":["animal","nature","chicken"],"k":[12,49]},"vhs":{"a":"Videocassette","b":"1F4FC","j":["record","video","oldschool","90s","80s"],"k":[26,51]},"city_sunset":{"a":"Cityscape at Dusk","b":"1F306","j":["photo","evening","sky","buildings"],"k":[5,50]},"confounded":{"a":"Confounded Face","b":"1F616","j":["face","confused","sick","unwell","oops",":S"],"k":[30,46]},"green_salad":{"a":"Green Salad","b":"1F957","j":["food","healthy","lettuce"],"k":[42,3],"o":9},"star_of_david":{"a":"Star of David","b":"2721-FE0F","c":"2721","j":["judaism"],"k":[49,47],"o":1},"flag-ci":{"a":"Côte D’ivoire Flag","b":"1F1E8-1F1EE","k":[1,23]},"popcorn":{"a":"Popcorn","b":"1F37F","j":["food","movie theater","films","snack"],"k":[8,13],"o":8},"city_sunrise":{"a":"Sunset over Buildings","b":"1F307","j":["photo","good morning","dawn"],"k":[5,51]},"disappointed":{"a":"Disappointed Face","b":"1F61E","j":["face","sad","upset","depressed",":("],"k":[31,2],"l":["):",":(",":-("],"m":":("},"mag":{"a":"Left-Pointing Magnifying Glass","b":"1F50D","j":["search","zoom","find","detective"],"k":[27,15]},"hatching_chick":{"a":"Hatching Chick","b":"1F423","j":["animal","chicken","egg","born","baby","bird"],"k":[13,13]},"joystick":{"a":"Joystick","b":"1F579-FE0F","c":"1F579","j":["game","play"],"k":[29,20],"o":7},"wheel_of_dharma":{"a":"Wheel of Dharma","b":"2638-FE0F","c":"2638","j":["hinduism","buddhism","sikhism","jainism"],"k":[47,39],"o":1},"flag-ck":{"a":"Cook Islands Flag","b":"1F1E8-1F1F0","k":[1,24]},"canned_food":{"a":"Canned Food","b":"1F96B","k":[42,23],"o":10},"worried":{"a":"Worried Face","b":"1F61F","j":["face","concern","nervous",":("],"k":[31,3]},"baby_chick":{"a":"Baby Chick","b":"1F424","j":["animal","chicken","bird"],"k":[13,14]},"flag-cl":{"a":"Chile Flag","b":"1F1E8-1F1F1","k":[1,25]},"game_die":{"a":"Game Die","b":"1F3B2","j":["dice","random","tabletop","play","luck"],"k":[9,12]},"mag_right":{"a":"Right-Pointing Magnifying Glass","b":"1F50E","j":["search","zoom","find","detective"],"k":[27,16]},"yin_yang":{"a":"Yin Yang","b":"262F-FE0F","c":"262F","j":["balance"],"k":[47,38],"o":1},"bridge_at_night":{"a":"Bridge at Night","b":"1F309","j":["photo","sanfrancisco"],"k":[6,1]},"spades":{"a":"Black Spade Suit","b":"2660-FE0F","c":"2660","j":["poker","cards","suits","magic"],"k":[48,4],"o":1},"hatched_chick":{"a":"Front-Facing Baby Chick","b":"1F425","j":["animal","chicken","baby","bird"],"k":[13,15]},"flag-cm":{"a":"Cameroon Flag","b":"1F1E8-1F1F2","k":[1,26]},"latin_cross":{"a":"Latin Cross","b":"271D-FE0F","c":"271D","j":["christianity"],"k":[49,46],"o":1},"triumph":{"a":"Face with Look of Triumph","b":"1F624","j":["face","gas","phew","proud","pride"],"k":[31,8]},"hotsprings":{"a":"Hot Springs","b":"2668-FE0F","c":"2668","j":["bath","warm","relax"],"k":[48,8],"o":1},"bento":{"a":"Bento Box","b":"1F371","j":["food","japanese","box"],"k":[7,51]},"microscope":{"a":"Microscope","b":"1F52C","j":["laboratory","experiment","zoomin","science","study"],"k":[27,46]},"cry":{"a":"Crying Face","b":"1F622","j":["face","tears","sad","depressed","upset",":'("],"k":[31,6],"l":[":'("],"m":":'("},"bird":{"a":"Bird","b":"1F426","j":["animal","nature","fly","tweet","spring"],"k":[13,16]},"cn":{"a":"China Flag","b":"1F1E8-1F1F3","j":["china","chinese","prc","flag","country","nation","banner"],"k":[1,27],"n":["flag-cn"]},"telescope":{"a":"Telescope","b":"1F52D","j":["stars","space","zoom","science","astronomy"],"k":[27,47]},"rice_cracker":{"a":"Rice Cracker","b":"1F358","j":["food","japanese"],"k":[7,26]},"hearts":{"a":"Black Heart Suit","b":"2665-FE0F","c":"2665","j":["poker","cards","magic","suits"],"k":[48,6],"o":1},"orthodox_cross":{"a":"Orthodox Cross","b":"2626-FE0F","c":"2626","j":["suppedaneum","religion"],"k":[47,35],"o":1},"milky_way":{"a":"Milky Way","b":"1F30C","j":["photo","space","stars"],"k":[6,4]},"rice_ball":{"a":"Rice Ball","b":"1F359","j":["food","japanese"],"k":[7,27]},"satellite_antenna":{"a":"Satellite Antenna","b":"1F4E1","k":[26,24]},"flag-co":{"a":"Colombia Flag","b":"1F1E8-1F1F4","k":[1,28]},"carousel_horse":{"a":"Carousel Horse","b":"1F3A0","j":["photo","carnival"],"k":[8,46]},"sob":{"a":"Loudly Crying Face","b":"1F62D","j":["face","cry","tears","sad","upset","depressed"],"k":[31,17],"m":":'("},"diamonds":{"a":"Black Diamond Suit","b":"2666-FE0F","c":"2666","j":["poker","cards","magic","suits"],"k":[48,7],"o":1},"star_and_crescent":{"a":"Star and Crescent","b":"262A-FE0F","c":"262A","j":["islam"],"k":[47,36],"o":1},"penguin":{"a":"Penguin","b":"1F427","j":["animal","nature"],"k":[13,17]},"dove_of_peace":{"a":"Dove of Peace","b":"1F54A-FE0F","c":"1F54A","k":[28,13],"o":7},"flag-cp":{"a":"Clipperton Island Flag","b":"1F1E8-1F1F5","k":[1,29]},"ferris_wheel":{"a":"Ferris Wheel","b":"1F3A1","j":["photo","carnival","londoneye"],"k":[8,47]},"clubs":{"a":"Black Club Suit","b":"2663-FE0F","c":"2663","j":["poker","cards","magic","suits"],"k":[48,5],"o":1},"peace_symbol":{"a":"Peace Symbol","b":"262E-FE0F","c":"262E","j":["hippie"],"k":[47,37],"o":1},"candle":{"a":"Candle","b":"1F56F-FE0F","c":"1F56F","j":["fire","wax"],"k":[28,42],"o":7},"frowning":{"a":"Frowning Face with Open Mouth","b":"1F626","j":["face","aw","what"],"k":[31,10]},"rice":{"a":"Cooked Rice","b":"1F35A","j":["food","china","asian"],"k":[7,28]},"flag-cr":{"a":"Costa Rica Flag","b":"1F1E8-1F1F7","k":[1,30]},"roller_coaster":{"a":"Roller Coaster","b":"1F3A2","j":["carnival","playground","photo","fun"],"k":[8,48]},"menorah_with_nine_branches":{"a":"Menorah with Nine Branches","b":"1F54E","k":[28,17],"o":8},"black_joker":{"a":"Playing Card Black Joker","b":"1F0CF","j":["poker","cards","game","play","magic"],"k":[0,15]},"eagle":{"a":"Eagle","b":"1F985","j":["animal","nature","bird"],"k":[42,29],"o":9},"curry":{"a":"Curry and Rice","b":"1F35B","j":["food","spicy","hot","indian"],"k":[7,29]},"bulb":{"a":"Electric Light Bulb","b":"1F4A1","j":["light","electricity","idea"],"k":[25,7]},"anguished":{"a":"Anguished Face","b":"1F627","j":["face","stunned","nervous"],"k":[31,11],"l":["D:"]},"flag-cu":{"a":"Cuba Flag","b":"1F1E8-1F1FA","k":[1,31]},"barber":{"a":"Barber Pole","b":"1F488","j":["hair","salon","style"],"k":[24,34]},"duck":{"a":"Duck","b":"1F986","j":["animal","nature","bird","mallard"],"k":[42,30],"o":9},"six_pointed_star":{"a":"Six Pointed Star with Middle Dot","b":"1F52F","j":["purple-square","religion","jewish","hexagram"],"k":[27,49]},"ramen":{"a":"Steaming Bowl","b":"1F35C","j":["food","japanese","noodle","chopsticks"],"k":[7,30]},"flashlight":{"a":"Electric Torch","b":"1F526","j":["dark","camping","sight","night"],"k":[27,40]},"mahjong":{"a":"Mahjong Tile Red Dragon","b":"1F004","j":["game","play","chinese","kanji"],"k":[0,14],"o":5},"fearful":{"a":"Fearful Face","b":"1F628","j":["face","scared","terrified","nervous","oops","huh"],"k":[31,12]},"aries":{"a":"Aries","b":"2648","j":["sign","purple-square","zodiac","astrology"],"k":[47,44],"o":1},"spaghetti":{"a":"Spaghetti","b":"1F35D","j":["food","italian","noodle"],"k":[7,31]},"circus_tent":{"a":"Circus Tent","b":"1F3AA","j":["festival","carnival","party"],"k":[9,4]},"izakaya_lantern":{"a":"Izakaya Lantern","b":"1F3EE","j":["light","paper","halloween","spooky"],"k":[12,11],"n":["lantern"]},"flag-cv":{"a":"Cape Verde Flag","b":"1F1E8-1F1FB","k":[1,32]},"weary":{"a":"Weary Face","b":"1F629","j":["face","tired","sleepy","sad","frustrated","upset"],"k":[31,13]},"flower_playing_cards":{"a":"Flower Playing Cards","b":"1F3B4","j":["game","sunset","red"],"k":[9,14]},"owl":{"a":"Owl","b":"1F989","j":["animal","nature","bird","hoot"],"k":[42,33],"o":9},"performing_arts":{"a":"Performing Arts","b":"1F3AD","j":["acting","theater","drama"],"k":[9,7]},"frog":{"a":"Frog Face","b":"1F438","j":["animal","nature","croak","toad"],"k":[13,34]},"flag-cw":{"a":"Curaçao Flag","b":"1F1E8-1F1FC","k":[1,33]},"notebook_with_decorative_cover":{"a":"Notebook with Decorative Cover","b":"1F4D4","j":["classroom","notes","record","paper","study"],"k":[26,11]},"exploding_head":{"a":"Shocked Face with Exploding Head","b":"1F92F","k":[39,3],"n":["shocked_face_with_exploding_head"],"o":10},"taurus":{"a":"Taurus","b":"2649","j":["purple-square","sign","zodiac","astrology"],"k":[47,45],"o":1},"sweet_potato":{"a":"Roasted Sweet Potato","b":"1F360","j":["food","nature"],"k":[7,34]},"closed_book":{"a":"Closed Book","b":"1F4D5","j":["read","library","knowledge","textbook","learn"],"k":[26,12]},"gemini":{"a":"Gemini","b":"264A","j":["sign","zodiac","purple-square","astrology"],"k":[47,46],"o":1},"frame_with_picture":{"a":"Frame with Picture","b":"1F5BC-FE0F","c":"1F5BC","k":[30,3],"o":7},"flag-cx":{"a":"Christmas Island Flag","b":"1F1E8-1F1FD","k":[1,34]},"grimacing":{"a":"Grimacing Face","b":"1F62C","j":["face","grimace","teeth"],"k":[31,16]},"crocodile":{"a":"Crocodile","b":"1F40A","j":["animal","nature","reptile","lizard","alligator"],"k":[12,40]},"oden":{"a":"Oden","b":"1F362","j":["food","japanese"],"k":[7,36]},"flag-cy":{"a":"Cyprus Flag","b":"1F1E8-1F1FE","k":[1,35]},"book":{"a":"Open Book","b":"1F4D6","k":[26,13],"n":["open_book"]},"turtle":{"a":"Turtle","b":"1F422","j":["animal","slow","nature","tortoise"],"k":[13,12]},"art":{"a":"Artist Palette","b":"1F3A8","j":["design","paint","draw","colors"],"k":[9,2]},"sushi":{"a":"Sushi","b":"1F363","j":["food","fish","japanese","rice"],"k":[7,37]},"cold_sweat":{"a":"Face with Open Mouth and Cold Sweat","b":"1F630","j":["face","nervous","sweat"],"k":[31,20]},"cancer":{"a":"Cancer","b":"264B","j":["sign","zodiac","purple-square","astrology"],"k":[47,47],"o":1},"fried_shrimp":{"a":"Fried Shrimp","b":"1F364","j":["food","animal","appetizer","summer"],"k":[7,38]},"slot_machine":{"a":"Slot Machine","b":"1F3B0","j":["bet","gamble","vegas","fruit machine","luck","casino"],"k":[9,10]},"scream":{"a":"Face Screaming in Fear","b":"1F631","j":["face","munch","scared","omg"],"k":[31,21]},"green_book":{"a":"Green Book","b":"1F4D7","j":["read","library","knowledge","study"],"k":[26,14]},"leo":{"a":"Leo","b":"264C","j":["sign","purple-square","zodiac","astrology"],"k":[47,48],"o":1},"flag-cz":{"a":"Czechia Flag","b":"1F1E8-1F1FF","k":[1,36]},"lizard":{"a":"Lizard","b":"1F98E","j":["animal","nature","reptile"],"k":[42,38],"o":9},"virgo":{"a":"Virgo","b":"264D","j":["sign","zodiac","purple-square","astrology"],"k":[47,49],"o":1},"steam_locomotive":{"a":"Steam Locomotive","b":"1F682","j":["transportation","vehicle","train"],"k":[34,10]},"de":{"a":"Germany Flag","b":"1F1E9-1F1EA","j":["german","nation","flag","country","banner"],"k":[1,37],"n":["flag-de"]},"flushed":{"a":"Flushed Face","b":"1F633","j":["face","blush","shy","flattered"],"k":[31,23]},"blue_book":{"a":"Blue Book","b":"1F4D8","j":["read","library","knowledge","learn","study"],"k":[26,15]},"snake":{"a":"Snake","b":"1F40D","j":["animal","evil","nature","hiss","python"],"k":[12,43]},"fish_cake":{"a":"Fish Cake with Swirl Design","b":"1F365","j":["food","japan","sea","beach","narutomaki","pink","swirl","kamaboko","surimi","ramen"],"k":[7,39]},"railway_car":{"a":"Railway Car","b":"1F683","j":["transportation","vehicle"],"k":[34,11]},"dango":{"a":"Dango","b":"1F361","j":["food","dessert","sweet","japanese","barbecue","meat"],"k":[7,35]},"orange_book":{"a":"Orange Book","b":"1F4D9","j":["read","library","knowledge","textbook","study"],"k":[26,16]},"libra":{"a":"Libra","b":"264E","j":["sign","purple-square","zodiac","astrology"],"k":[47,50],"o":1},"dragon_face":{"a":"Dragon Face","b":"1F432","j":["animal","myth","nature","chinese","green"],"k":[13,28]},"flag-dg":{"a":"Diego Garcia Flag","b":"1F1E9-1F1EC","k":[1,38]},"zany_face":{"a":"Grinning Face with One Large and One Small Eye","b":"1F92A","k":[38,50],"n":["grinning_face_with_one_large_and_one_small_eye"],"o":10},"books":{"a":"Books","b":"1F4DA","j":["literature","library","study"],"k":[26,17]},"dragon":{"a":"Dragon","b":"1F409","j":["animal","myth","nature","chinese","green"],"k":[12,39]},"flag-dj":{"a":"Djibouti Flag","b":"1F1E9-1F1EF","k":[1,39]},"dumpling":{"a":"Dumpling","b":"1F95F","k":[42,11],"o":10},"dizzy_face":{"a":"Dizzy Face","b":"1F635","j":["spent","unconscious","xox","dizzy"],"k":[31,25]},"scorpius":{"a":"Scorpius","b":"264F","j":["sign","zodiac","purple-square","astrology","scorpio"],"k":[47,51],"o":1},"bullettrain_side":{"a":"High-Speed Train","b":"1F684","j":["transportation","vehicle"],"k":[34,12]},"bullettrain_front":{"a":"High-Speed Train with Bullet Nose","b":"1F685","j":["transportation","vehicle","speed","fast","public","travel"],"k":[34,13]},"notebook":{"a":"Notebook","b":"1F4D3","j":["stationery","record","notes","paper","study"],"k":[26,10]},"fortune_cookie":{"a":"Fortune Cookie","b":"1F960","k":[42,12],"o":10},"sagittarius":{"a":"Sagittarius","b":"2650","j":["sign","zodiac","purple-square","astrology"],"k":[48,0],"o":1},"sauropod":{"a":"Sauropod","b":"1F995","k":[42,45],"o":10},"flag-dk":{"a":"Denmark Flag","b":"1F1E9-1F1F0","k":[1,40]},"rage":{"a":"Pouting Face","b":"1F621","j":["angry","mad","hate","despise"],"k":[31,5]},"ledger":{"a":"Ledger","b":"1F4D2","j":["notes","paper"],"k":[26,9]},"angry":{"a":"Angry Face","b":"1F620","j":["mad","face","annoyed","frustrated"],"k":[31,4],"l":[">:(",">:-("]},"t-rex":{"a":"T-Rex","b":"1F996","k":[42,46],"o":10},"capricorn":{"a":"Capricorn","b":"2651","j":["sign","zodiac","purple-square","astrology"],"k":[48,1],"o":1},"takeout_box":{"a":"Takeout Box","b":"1F961","k":[42,13],"o":10},"flag-dm":{"a":"Dominica Flag","b":"1F1E9-1F1F2","k":[1,41]},"train2":{"a":"Train","b":"1F686","j":["transportation","vehicle"],"k":[34,14]},"page_with_curl":{"a":"Page with Curl","b":"1F4C3","j":["documents","office","paper"],"k":[25,46]},"whale":{"a":"Spouting Whale","b":"1F433","j":["animal","nature","sea","ocean"],"k":[13,29]},"face_with_symbols_on_mouth":{"a":"Serious Face with Symbols Covering Mouth","b":"1F92C","k":[39,0],"n":["serious_face_with_symbols_covering_mouth"],"o":10},"flag-do":{"a":"Dominican Republic Flag","b":"1F1E9-1F1F4","k":[1,42]},"metro":{"a":"Metro","b":"1F687","j":["transportation","blue-square","mrt","underground","tube"],"k":[34,15]},"icecream":{"a":"Soft Ice Cream","b":"1F366","j":["food","hot","dessert","summer"],"k":[7,40]},"aquarius":{"a":"Aquarius","b":"2652","j":["sign","purple-square","zodiac","astrology"],"k":[48,2],"o":1},"flag-dz":{"a":"Algeria Flag","b":"1F1E9-1F1FF","k":[1,43]},"whale2":{"a":"Whale","b":"1F40B","j":["animal","nature","sea","ocean"],"k":[12,41]},"mask":{"a":"Face with Medical Mask","b":"1F637","j":["face","sick","ill","disease"],"k":[31,27]},"scroll":{"a":"Scroll","b":"1F4DC","j":["documents","ancient","history","paper"],"k":[26,19]},"shaved_ice":{"a":"Shaved Ice","b":"1F367","j":["hot","dessert","summer"],"k":[7,41]},"pisces":{"a":"Pisces","b":"2653","j":["purple-square","sign","zodiac","astrology"],"k":[48,3],"o":1},"light_rail":{"a":"Light Rail","b":"1F688","j":["transportation","vehicle"],"k":[34,16]},"dolphin":{"a":"Dolphin","b":"1F42C","j":["animal","nature","fish","sea","ocean","flipper","fins","beach"],"k":[13,22],"n":["flipper"]},"face_with_thermometer":{"a":"Face with Thermometer","b":"1F912","j":["sick","temperature","thermometer","cold","fever"],"k":[37,26],"o":8},"flag-ea":{"a":"Ceuta & Melilla Flag","b":"1F1EA-1F1E6","k":[1,44]},"ophiuchus":{"a":"Ophiuchus","b":"26CE","j":["sign","purple-square","constellation","astrology"],"k":[48,31]},"station":{"a":"Station","b":"1F689","j":["transportation","vehicle","public"],"k":[34,17]},"ice_cream":{"a":"Ice Cream","b":"1F368","j":["food","hot","dessert"],"k":[7,42]},"page_facing_up":{"a":"Page Facing Up","b":"1F4C4","j":["documents","office","paper","information"],"k":[25,47]},"doughnut":{"a":"Doughnut","b":"1F369","j":["food","dessert","snack","sweet","donut"],"k":[7,43]},"face_with_head_bandage":{"a":"Face with Head-Bandage","b":"1F915","j":["injured","clumsy","bandage","hurt"],"k":[37,29],"o":8},"fish":{"a":"Fish","b":"1F41F","j":["animal","food","nature"],"k":[13,9]},"newspaper":{"a":"Newspaper","b":"1F4F0","j":["press","headline"],"k":[26,39]},"tram":{"a":"Tram","b":"1F68A","j":["transportation","vehicle"],"k":[34,18]},"flag-ec":{"a":"Ecuador Flag","b":"1F1EA-1F1E8","k":[1,45]},"twisted_rightwards_arrows":{"a":"Twisted Rightwards Arrows","b":"1F500","j":["blue-square","shuffle","music","random"],"k":[27,2]},"flag-ee":{"a":"Estonia Flag","b":"1F1EA-1F1EA","k":[1,46]},"cookie":{"a":"Cookie","b":"1F36A","j":["food","snack","oreo","chocolate","sweet","dessert"],"k":[7,44]},"monorail":{"a":"Monorail","b":"1F69D","j":["transportation","vehicle"],"k":[34,37]},"tropical_fish":{"a":"Tropical Fish","b":"1F420","j":["animal","swim","ocean","beach","nemo"],"k":[13,10]},"rolled_up_newspaper":{"a":"Rolled Up Newspaper","b":"1F5DE-FE0F","c":"1F5DE","k":[30,12],"o":7},"nauseated_face":{"a":"Nauseated Face","b":"1F922","j":["face","vomit","gross","green","sick","throw up","ill"],"k":[38,25],"o":9},"repeat":{"a":"Clockwise Rightwards and Leftwards Open Circle Arrows","b":"1F501","j":["loop","record"],"k":[27,3]},"bookmark_tabs":{"a":"Bookmark Tabs","b":"1F4D1","j":["favorite","save","order","tidy"],"k":[26,8]},"repeat_one":{"a":"Clockwise Rightwards and Leftwards Open Circle Arrows with Circled One Overlay","b":"1F502","j":["blue-square","loop"],"k":[27,4]},"flag-eg":{"a":"Egypt Flag","b":"1F1EA-1F1EC","k":[1,47]},"mountain_railway":{"a":"Mountain Railway","b":"1F69E","j":["transportation","vehicle"],"k":[34,38]},"birthday":{"a":"Birthday Cake","b":"1F382","j":["food","dessert","cake"],"k":[8,16]},"blowfish":{"a":"Blowfish","b":"1F421","j":["animal","nature","food","sea","ocean"],"k":[13,11]},"face_vomiting":{"a":"Face with Open Mouth Vomiting","b":"1F92E","k":[39,2],"n":["face_with_open_mouth_vomiting"],"o":10},"arrow_forward":{"a":"Black Right-Pointing Triangle","b":"25B6-FE0F","c":"25B6","j":["blue-square","right","direction","play"],"k":[47,10],"o":1},"bookmark":{"a":"Bookmark","b":"1F516","j":["favorite","label","save"],"k":[27,24]},"flag-eh":{"a":"Western Sahara Flag","b":"1F1EA-1F1ED","k":[1,48]},"shark":{"a":"Shark","b":"1F988","j":["animal","nature","fish","sea","ocean","jaws","fins","beach"],"k":[42,32],"o":9},"train":{"a":"Tram Car","b":"1F68B","j":["transportation","vehicle","carriage","public","travel"],"k":[34,19]},"sneezing_face":{"a":"Sneezing Face","b":"1F927","j":["face","gesundheit","sneeze","sick","allergy"],"k":[38,47],"o":9},"cake":{"a":"Shortcake","b":"1F370","j":["food","dessert"],"k":[7,50]},"bus":{"a":"Bus","b":"1F68C","j":["car","vehicle","transportation"],"k":[34,20]},"pie":{"a":"Pie","b":"1F967","k":[42,19],"o":10},"innocent":{"a":"Smiling Face with Halo","b":"1F607","j":["face","angel","heaven","halo"],"k":[30,31]},"fast_forward":{"a":"Black Right-Pointing Double Triangle","b":"23E9","j":["blue-square","play","speed","continue"],"k":[46,45]},"label":{"a":"Label","b":"1F3F7-FE0F","c":"1F3F7","j":["sale","tag"],"k":[12,21],"o":7},"octopus":{"a":"Octopus","b":"1F419","j":["animal","creature","ocean","sea","nature","beach"],"k":[13,3]},"flag-er":{"a":"Eritrea Flag","b":"1F1EA-1F1F7","k":[1,49]},"black_right_pointing_double_triangle_with_vertical_bar":{"a":"Black Right Pointing Double Triangle with Vertical Bar","b":"23ED-FE0F","c":"23ED","k":[46,49]},"chocolate_bar":{"a":"Chocolate Bar","b":"1F36B","j":["food","snack","dessert","sweet"],"k":[7,45]},"oncoming_bus":{"a":"Oncoming Bus","b":"1F68D","j":["vehicle","transportation"],"k":[34,21]},"shell":{"a":"Spiral Shell","b":"1F41A","j":["nature","sea","beach"],"k":[13,4]},"face_with_cowboy_hat":{"a":"Face with Cowboy Hat","b":"1F920","k":[38,23],"o":9},"moneybag":{"a":"Money Bag","b":"1F4B0","j":["dollar","payment","coins","sale"],"k":[25,27]},"es":{"a":"Spain Flag","b":"1F1EA-1F1F8","j":["spain","flag","nation","country","banner"],"k":[1,50],"n":["flag-es"]},"crab":{"a":"Crab","b":"1F980","j":["animal","crustacean"],"k":[42,24],"o":8},"yen":{"a":"Banknote with Yen Sign","b":"1F4B4","j":["money","sales","japanese","dollar","currency"],"k":[25,31]},"flag-et":{"a":"Ethiopia Flag","b":"1F1EA-1F1F9","k":[1,51]},"clown_face":{"a":"Clown Face","b":"1F921","j":["face"],"k":[38,24],"o":9},"black_right_pointing_triangle_with_double_vertical_bar":{"a":"Black Right Pointing Triangle with Double Vertical Bar","b":"23EF-FE0F","c":"23EF","k":[46,51]},"trolleybus":{"a":"Trolleybus","b":"1F68E","j":["bart","transportation","vehicle"],"k":[34,22]},"candy":{"a":"Candy","b":"1F36C","j":["snack","dessert","sweet","lolly"],"k":[7,46]},"lying_face":{"a":"Lying Face","b":"1F925","j":["face","lie","pinocchio"],"k":[38,28],"o":9},"arrow_backward":{"a":"Black Left-Pointing Triangle","b":"25C0-FE0F","c":"25C0","j":["blue-square","left","direction"],"k":[47,11],"o":1},"dollar":{"a":"Banknote with Dollar Sign","b":"1F4B5","j":["money","sales","bill","currency"],"k":[25,32]},"shrimp":{"a":"Shrimp","b":"1F990","j":["animal","ocean","nature","seafood"],"k":[42,40],"o":9},"minibus":{"a":"Minibus","b":"1F690","j":["vehicle","car","transportation"],"k":[34,24]},"flag-eu":{"a":"European Union Flag","b":"1F1EA-1F1FA","k":[2,0]},"lollipop":{"a":"Lollipop","b":"1F36D","j":["food","snack","candy","sweet"],"k":[7,47]},"squid":{"a":"Squid","b":"1F991","j":["animal","nature","ocean","sea"],"k":[42,41],"o":9},"euro":{"a":"Banknote with Euro Sign","b":"1F4B6","j":["money","sales","dollar","currency"],"k":[25,33]},"flag-fi":{"a":"Finland Flag","b":"1F1EB-1F1EE","k":[2,1]},"ambulance":{"a":"Ambulance","b":"1F691","j":["health","911","hospital"],"k":[34,25]},"custard":{"a":"Custard","b":"1F36E","j":["dessert","food"],"k":[7,48]},"shushing_face":{"a":"Face with Finger Covering Closed Lips","b":"1F92B","k":[38,51],"n":["face_with_finger_covering_closed_lips"],"o":10},"rewind":{"a":"Black Left-Pointing Double Triangle","b":"23EA","j":["play","blue-square"],"k":[46,46]},"black_left_pointing_double_triangle_with_vertical_bar":{"a":"Black Left Pointing Double Triangle with Vertical Bar","b":"23EE-FE0F","c":"23EE","k":[46,50]},"face_with_hand_over_mouth":{"a":"Smiling Face with Smiling Eyes and Hand Covering Mouth","b":"1F92D","k":[39,1],"n":["smiling_face_with_smiling_eyes_and_hand_covering_mouth"],"o":10},"flag-fj":{"a":"Fiji Flag","b":"1F1EB-1F1EF","k":[2,2]},"honey_pot":{"a":"Honey Pot","b":"1F36F","j":["bees","sweet","kitchen"],"k":[7,49]},"snail":{"a":"Snail","b":"1F40C","j":["slow","animal","shell"],"k":[12,42]},"pound":{"a":"Banknote with Pound Sign","b":"1F4B7","j":["british","sterling","money","sales","bills","uk","england","currency"],"k":[25,34]},"fire_engine":{"a":"Fire Engine","b":"1F692","j":["transportation","cars","vehicle"],"k":[34,26]},"baby_bottle":{"a":"Baby Bottle","b":"1F37C","j":["food","container","milk"],"k":[8,10]},"flag-fk":{"a":"Falkland Islands Flag","b":"1F1EB-1F1F0","k":[2,3]},"butterfly":{"a":"Butterfly","b":"1F98B","j":["animal","insect","nature","caterpillar"],"k":[42,35],"o":9},"money_with_wings":{"a":"Money with Wings","b":"1F4B8","j":["dollar","bills","payment","sale"],"k":[25,35]},"face_with_monocle":{"a":"Face with Monocle","b":"1F9D0","k":[42,49],"o":10},"police_car":{"a":"Police Car","b":"1F693","j":["vehicle","cars","transportation","law","legal","enforcement"],"k":[34,27]},"arrow_up_small":{"a":"Up-Pointing Small Red Triangle","b":"1F53C","j":["blue-square","triangle","direction","point","forward","top"],"k":[28,10]},"flag-fm":{"a":"Micronesia Flag","b":"1F1EB-1F1F2","k":[2,4]},"glass_of_milk":{"a":"Glass of Milk","b":"1F95B","k":[42,7],"o":9},"credit_card":{"a":"Credit Card","b":"1F4B3","j":["money","sales","dollar","bill","payment","shopping"],"k":[25,30]},"oncoming_police_car":{"a":"Oncoming Police Car","b":"1F694","j":["vehicle","law","legal","enforcement","911"],"k":[34,28]},"bug":{"a":"Bug","b":"1F41B","j":["animal","insect","nature","worm"],"k":[13,5]},"nerd_face":{"a":"Nerd Face","b":"1F913","j":["face","nerdy","geek","dork"],"k":[37,27],"o":8},"arrow_double_up":{"a":"Black Up-Pointing Double Triangle","b":"23EB","j":["blue-square","direction","top"],"k":[46,47]},"chart":{"a":"Chart with Upwards Trend and Yen Sign","b":"1F4B9","j":["green-square","graph","presentation","stats"],"k":[25,36]},"flag-fo":{"a":"Faroe Islands Flag","b":"1F1EB-1F1F4","k":[2,5]},"ant":{"a":"Ant","b":"1F41C","j":["animal","insect","nature","bug"],"k":[13,6]},"arrow_down_small":{"a":"Down-Pointing Small Red Triangle","b":"1F53D","j":["blue-square","direction","bottom"],"k":[28,11]},"smiling_imp":{"a":"Smiling Face with Horns","b":"1F608","j":["devil","horns"],"k":[30,32]},"taxi":{"a":"Taxi","b":"1F695","j":["uber","vehicle","cars","transportation"],"k":[34,29]},"coffee":{"a":"Hot Beverage","b":"2615","j":["beverage","caffeine","latte","espresso"],"k":[47,24],"o":4},"fr":{"a":"France Flag","b":"1F1EB-1F1F7","j":["banner","flag","nation","france","french","country"],"k":[2,6],"n":["flag-fr"]},"oncoming_taxi":{"a":"Oncoming Taxi","b":"1F696","j":["vehicle","cars","uber"],"k":[34,30]},"arrow_double_down":{"a":"Black Down-Pointing Double Triangle","b":"23EC","j":["blue-square","direction","bottom"],"k":[46,48]},"imp":{"a":"Imp","b":"1F47F","j":["devil","angry","horns"],"k":[22,51]},"currency_exchange":{"a":"Currency Exchange","b":"1F4B1","j":["money","sales","dollar","travel"],"k":[25,28]},"tea":{"a":"Teacup Without Handle","b":"1F375","j":["drink","bowl","breakfast","green","british"],"k":[8,3]},"bee":{"a":"Honeybee","b":"1F41D","k":[13,7],"n":["honeybee"]},"heavy_dollar_sign":{"a":"Heavy Dollar Sign","b":"1F4B2","j":["money","sales","payment","currency","buck"],"k":[25,29]},"car":{"a":"Automobile","b":"1F697","k":[34,31],"n":["red_car"]},"sake":{"a":"Sake Bottle and Cup","b":"1F376","j":["wine","drink","drunk","beverage","japanese","alcohol","booze"],"k":[8,4]},"flag-ga":{"a":"Gabon Flag","b":"1F1EC-1F1E6","k":[2,7]},"beetle":{"a":"Lady Beetle","b":"1F41E","j":["animal","insect","nature","ladybug"],"k":[13,8]},"japanese_ogre":{"a":"Japanese Ogre","b":"1F479","j":["monster","red","mask","halloween","scary","creepy","devil","demon","japanese","ogre"],"k":[22,40]},"double_vertical_bar":{"a":"Double Vertical Bar","b":"23F8-FE0F","c":"23F8","k":[47,4],"o":7},"champagne":{"a":"Bottle with Popping Cork","b":"1F37E","j":["drink","wine","bottle","celebration"],"k":[8,12],"o":8},"japanese_goblin":{"a":"Japanese Goblin","b":"1F47A","j":["red","evil","mask","monster","scary","creepy","japanese","goblin"],"k":[22,41]},"black_square_for_stop":{"a":"Black Square for Stop","b":"23F9-FE0F","c":"23F9","k":[47,5],"o":7},"oncoming_automobile":{"a":"Oncoming Automobile","b":"1F698","j":["car","vehicle","transportation"],"k":[34,32]},"email":{"a":"Envelope","b":"2709-FE0F","c":"2709","j":["letter","postal","inbox","communication"],"k":[49,17],"n":["envelope"],"o":1},"cricket":{"a":"Cricket","b":"1F997","j":["sports"],"k":[42,47],"o":10},"gb":{"a":"United Kingdom Flag","b":"1F1EC-1F1E7","k":[2,8],"n":["uk","flag-gb"]},"black_circle_for_record":{"a":"Black Circle for Record","b":"23FA-FE0F","c":"23FA","k":[47,6],"o":7},"flag-gd":{"a":"Grenada Flag","b":"1F1EC-1F1E9","k":[2,9]},"spider":{"a":"Spider","b":"1F577-FE0F","c":"1F577","j":["animal","arachnid"],"k":[29,18],"o":7},"blue_car":{"a":"Recreational Vehicle","b":"1F699","j":["transportation","vehicle"],"k":[34,33]},"skull":{"a":"Skull","b":"1F480","j":["dead","skeleton","creepy","death"],"k":[23,0]},"e-mail":{"a":"E-Mail Symbol","b":"1F4E7","j":["communication","inbox"],"k":[26,30]},"wine_glass":{"a":"Wine Glass","b":"1F377","j":["drink","beverage","drunk","alcohol","booze"],"k":[8,5]},"spider_web":{"a":"Spider Web","b":"1F578-FE0F","c":"1F578","j":["animal","insect","arachnid","silk"],"k":[29,19],"o":7},"cocktail":{"a":"Cocktail Glass","b":"1F378","j":["drink","drunk","alcohol","beverage","booze","mojito"],"k":[8,6]},"skull_and_crossbones":{"a":"Skull and Crossbones","b":"2620-FE0F","c":"2620","j":["poison","danger","deadly","scary","death","pirate","evil"],"k":[47,32],"o":1},"flag-ge":{"a":"Georgia Flag","b":"1F1EC-1F1EA","k":[2,10]},"eject":{"a":"Eject","b":"23CF-FE0F","c":"23CF","k":[46,44],"o":4},"truck":{"a":"Delivery Truck","b":"1F69A","j":["cars","transportation"],"k":[34,34]},"incoming_envelope":{"a":"Incoming Envelope","b":"1F4E8","j":["email","inbox"],"k":[26,31]},"tropical_drink":{"a":"Tropical Drink","b":"1F379","j":["beverage","cocktail","summer","beach","alcohol","booze","mojito"],"k":[8,7]},"scorpion":{"a":"Scorpion","b":"1F982","j":["animal","arachnid"],"k":[42,26],"o":8},"cinema":{"a":"Cinema","b":"1F3A6","j":["blue-square","record","film","movie","curtain","stage","theater"],"k":[9,0]},"articulated_lorry":{"a":"Articulated Lorry","b":"1F69B","j":["vehicle","cars","transportation","express"],"k":[34,35]},"envelope_with_arrow":{"a":"Envelope with Downwards Arrow Above","b":"1F4E9","j":["email","communication"],"k":[26,32]},"ghost":{"a":"Ghost","b":"1F47B","j":["halloween","spooky","scary"],"k":[22,42]},"flag-gf":{"a":"French Guiana Flag","b":"1F1EC-1F1EB","k":[2,11]},"bouquet":{"a":"Bouquet","b":"1F490","j":["flowers","nature","spring"],"k":[24,42]},"tractor":{"a":"Tractor","b":"1F69C","j":["vehicle","car","farming","agriculture"],"k":[34,36]},"beer":{"a":"Beer Mug","b":"1F37A","j":["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],"k":[8,8]},"outbox_tray":{"a":"Outbox Tray","b":"1F4E4","j":["inbox","email"],"k":[26,27]},"low_brightness":{"a":"Low Brightness Symbol","b":"1F505","j":["sun","afternoon","warm","summer"],"k":[27,7]},"alien":{"a":"Extraterrestrial Alien","b":"1F47D","j":["UFO","paul","weird","outer_space"],"k":[22,49]},"flag-gg":{"a":"Guernsey Flag","b":"1F1EC-1F1EC","k":[2,12]},"cherry_blossom":{"a":"Cherry Blossom","b":"1F338","j":["nature","plant","spring","flower"],"k":[6,46]},"inbox_tray":{"a":"Inbox Tray","b":"1F4E5","j":["email","documents"],"k":[26,28]},"flag-gh":{"a":"Ghana Flag","b":"1F1EC-1F1ED","k":[2,13]},"bike":{"a":"Bicycle","b":"1F6B2","j":["sports","bicycle","exercise","hipster"],"k":[35,23]},"space_invader":{"a":"Alien Monster","b":"1F47E","j":["game","arcade","play"],"k":[22,50]},"beers":{"a":"Clinking Beer Mugs","b":"1F37B","j":["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],"k":[8,9]},"high_brightness":{"a":"High Brightness Symbol","b":"1F506","j":["sun","light"],"k":[27,8]},"package":{"a":"Package","b":"1F4E6","j":["mail","gift","cardboard","box","moving"],"k":[26,29]},"scooter":{"a":"Scooter","b":"1F6F4","k":[37,19],"o":9},"white_flower":{"a":"White Flower","b":"1F4AE","j":["japanese","spring"],"k":[25,25]},"clinking_glasses":{"a":"Clinking Glasses","b":"1F942","j":["beverage","drink","party","alcohol","celebrate","cheers"],"k":[41,38],"o":9},"robot_face":{"a":"Robot Face","b":"1F916","k":[37,30],"o":8},"signal_strength":{"a":"Antenna with Bars","b":"1F4F6","j":["blue-square","reception","phone","internet","connection","wifi","bluetooth","bars"],"k":[26,45]},"flag-gi":{"a":"Gibraltar Flag","b":"1F1EC-1F1EE","k":[2,14]},"flag-gl":{"a":"Greenland Flag","b":"1F1EC-1F1F1","k":[2,15]},"motor_scooter":{"a":"Motor Scooter","b":"1F6F5","j":["vehicle","vespa","sasha"],"k":[37,20],"o":9},"mailbox":{"a":"Closed Mailbox with Raised Flag","b":"1F4EB","j":["email","inbox","communication"],"k":[26,34]},"vibration_mode":{"a":"Vibration Mode","b":"1F4F3","j":["orange-square","phone"],"k":[26,42]},"hankey":{"a":"Pile of Poo","b":"1F4A9","k":[25,15],"n":["poop","shit"]},"rosette":{"a":"Rosette","b":"1F3F5-FE0F","c":"1F3F5","j":["flower","decoration","military"],"k":[12,20],"o":7},"tumbler_glass":{"a":"Tumbler Glass","b":"1F943","j":["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","glass","shot"],"k":[41,39],"o":9},"cup_with_straw":{"a":"Cup with Straw","b":"1F964","k":[42,16],"o":10},"flag-gm":{"a":"Gambia Flag","b":"1F1EC-1F1F2","k":[2,16]},"mailbox_closed":{"a":"Closed Mailbox with Lowered Flag","b":"1F4EA","j":["email","communication","inbox"],"k":[26,33]},"mobile_phone_off":{"a":"Mobile Phone off","b":"1F4F4","j":["mute","orange-square","silence","quiet"],"k":[26,43]},"busstop":{"a":"Bus Stop","b":"1F68F","j":["transportation","wait"],"k":[34,23]},"smiley_cat":{"a":"Smiling Cat Face with Open Mouth","b":"1F63A","j":["animal","cats","happy","smile"],"k":[31,30]},"rose":{"a":"Rose","b":"1F339","j":["flowers","valentines","love","spring"],"k":[6,47]},"motorway":{"a":"Motorway","b":"1F6E3-FE0F","c":"1F6E3","j":["road","cupertino","interstate","highway"],"k":[37,11],"o":7},"smile_cat":{"a":"Grinning Cat Face with Smiling Eyes","b":"1F638","j":["animal","cats","smile"],"k":[31,28]},"flag-gn":{"a":"Guinea Flag","b":"1F1EC-1F1F3","k":[2,17]},"wilted_flower":{"a":"Wilted Flower","b":"1F940","j":["plant","nature","flower"],"k":[41,36],"o":9},"mailbox_with_mail":{"a":"Open Mailbox with Raised Flag","b":"1F4EC","j":["email","inbox","communication"],"k":[26,35]},"chopsticks":{"a":"Chopsticks","b":"1F962","k":[42,14],"o":10},"mailbox_with_no_mail":{"a":"Open Mailbox with Lowered Flag","b":"1F4ED","j":["email","inbox"],"k":[26,36]},"knife_fork_plate":{"a":"Knife Fork Plate","b":"1F37D-FE0F","c":"1F37D","k":[8,11],"o":7},"hibiscus":{"a":"Hibiscus","b":"1F33A","j":["plant","vegetable","flowers","beach"],"k":[6,48]},"flag-gp":{"a":"Guadeloupe Flag","b":"1F1EC-1F1F5","k":[2,18]},"railway_track":{"a":"Railway Track","b":"1F6E4-FE0F","c":"1F6E4","j":["train","transportation"],"k":[37,12],"o":7},"joy_cat":{"a":"Cat Face with Tears of Joy","b":"1F639","j":["animal","cats","haha","happy","tears"],"k":[31,29]},"fuelpump":{"a":"Fuel Pump","b":"26FD","j":["gas station","petroleum"],"k":[49,13],"o":5},"sunflower":{"a":"Sunflower","b":"1F33B","j":["nature","plant","fall"],"k":[6,49]},"postbox":{"a":"Postbox","b":"1F4EE","j":["email","letter","envelope"],"k":[26,37]},"flag-gq":{"a":"Equatorial Guinea Flag","b":"1F1EC-1F1F6","k":[2,19]},"heart_eyes_cat":{"a":"Smiling Cat Face with Heart-Shaped Eyes","b":"1F63B","j":["animal","love","like","affection","cats","valentines","heart"],"k":[31,31]},"fork_and_knife":{"a":"Fork and Knife","b":"1F374","j":["cutlery","kitchen"],"k":[8,2]},"recycle":{"a":"Black Universal Recycling Symbol","b":"267B-FE0F","c":"267B","j":["arrow","environment","garbage","trash"],"k":[48,9],"o":3},"spoon":{"a":"Spoon","b":"1F944","j":["cutlery","kitchen","tableware"],"k":[41,40],"o":9},"blossom":{"a":"Blossom","b":"1F33C","j":["nature","flowers","yellow"],"k":[6,50]},"rotating_light":{"a":"Police Cars Revolving Light","b":"1F6A8","j":["police","ambulance","911","emergency","alert","error","pinged","law","legal"],"k":[35,13]},"smirk_cat":{"a":"Cat Face with Wry Smile","b":"1F63C","j":["animal","cats","smirk"],"k":[31,32]},"ballot_box_with_ballot":{"a":"Ballot Box with Ballot","b":"1F5F3-FE0F","c":"1F5F3","k":[30,17],"o":7},"flag-gr":{"a":"Greece Flag","b":"1F1EC-1F1F7","k":[2,20]},"kissing_cat":{"a":"Kissing Cat Face with Closed Eyes","b":"1F63D","j":["animal","cats","kiss"],"k":[31,33]},"pencil2":{"a":"Pencil","b":"270F-FE0F","c":"270F","j":["stationery","write","paper","writing","school","study"],"k":[49,42],"o":1},"traffic_light":{"a":"Horizontal Traffic Light","b":"1F6A5","j":["transportation","signal"],"k":[35,10]},"fleur_de_lis":{"a":"Fleur De Lis","b":"269C-FE0F","c":"269C","j":["decorative","scout"],"k":[48,19],"o":4},"tulip":{"a":"Tulip","b":"1F337","j":["flowers","plant","nature","summer","spring"],"k":[6,45]},"hocho":{"a":"Hocho","b":"1F52A","j":["knife","blade","cutlery","kitchen","weapon"],"k":[27,44],"n":["knife"]},"flag-gs":{"a":"South Georgia & South Sandwich Islands Flag","b":"1F1EC-1F1F8","k":[2,21]},"seedling":{"a":"Seedling","b":"1F331","j":["plant","nature","grass","lawn","spring"],"k":[6,39]},"amphora":{"a":"Amphora","b":"1F3FA","j":["vase","jar"],"k":[12,24],"o":8},"scream_cat":{"a":"Weary Cat Face","b":"1F640","j":["animal","cats","munch","scared","scream"],"k":[31,36]},"vertical_traffic_light":{"a":"Vertical Traffic Light","b":"1F6A6","j":["transportation","driving"],"k":[35,11]},"black_nib":{"a":"Black Nib","b":"2712-FE0F","c":"2712","j":["pen","stationery","writing","write"],"k":[49,43],"o":1},"flag-gt":{"a":"Guatemala Flag","b":"1F1EC-1F1F9","k":[2,22]},"trident":{"a":"Trident Emblem","b":"1F531","j":["weapon","spear"],"k":[27,51]},"flag-gu":{"a":"Guam Flag","b":"1F1EC-1F1FA","k":[2,23]},"name_badge":{"a":"Name Badge","b":"1F4DB","j":["fire","forbid"],"k":[26,18]},"construction":{"a":"Construction Sign","b":"1F6A7","j":["wip","progress","caution","warning"],"k":[35,12]},"lower_left_fountain_pen":{"a":"Lower Left Fountain Pen","b":"1F58B-FE0F","c":"1F58B","k":[29,29],"o":7},"evergreen_tree":{"a":"Evergreen Tree","b":"1F332","j":["plant","nature"],"k":[6,40]},"crying_cat_face":{"a":"Crying Cat Face","b":"1F63F","j":["animal","tears","weep","sad","cats","upset","cry"],"k":[31,35]},"flag-gw":{"a":"Guinea-Bissau Flag","b":"1F1EC-1F1FC","k":[2,24]},"lower_left_ballpoint_pen":{"a":"Lower Left Ballpoint Pen","b":"1F58A-FE0F","c":"1F58A","k":[29,28],"o":7},"pouting_cat":{"a":"Pouting Cat Face","b":"1F63E","j":["animal","cats"],"k":[31,34]},"deciduous_tree":{"a":"Deciduous Tree","b":"1F333","j":["plant","nature"],"k":[6,41]},"octagonal_sign":{"a":"Octagonal Sign","b":"1F6D1","k":[37,6],"o":9},"beginner":{"a":"Japanese Symbol for Beginner","b":"1F530","j":["badge","shield"],"k":[27,50]},"flag-gy":{"a":"Guyana Flag","b":"1F1EC-1F1FE","k":[2,25]},"lower_left_paintbrush":{"a":"Lower Left Paintbrush","b":"1F58C-FE0F","c":"1F58C","k":[29,30],"o":7},"o":{"a":"Heavy Large Circle","b":"2B55","j":["circle","round"],"k":[50,23],"o":5},"palm_tree":{"a":"Palm Tree","b":"1F334","j":["plant","vegetable","nature","summer","beach","mojito","tropical"],"k":[6,42]},"anchor":{"a":"Anchor","b":"2693","j":["ship","ferry","sea","boat"],"k":[48,12],"o":4},"see_no_evil":{"a":"See-No-Evil Monkey","b":"1F648","j":["monkey","animal","nature","haha"],"k":[32,43]},"boat":{"a":"Sailboat","b":"26F5","k":[48,43],"n":["sailboat"],"o":5},"white_check_mark":{"a":"White Heavy Check Mark","b":"2705","j":["green-square","ok","agree","vote","election","answer","tick"],"k":[49,15]},"flag-hk":{"a":"Hong Kong Sar China Flag","b":"1F1ED-1F1F0","k":[2,26]},"lower_left_crayon":{"a":"Lower Left Crayon","b":"1F58D-FE0F","c":"1F58D","k":[29,31],"o":7},"hear_no_evil":{"a":"Hear-No-Evil Monkey","b":"1F649","j":["animal","monkey","nature"],"k":[32,44]},"cactus":{"a":"Cactus","b":"1F335","j":["vegetable","plant","nature"],"k":[6,43]},"ear_of_rice":{"a":"Ear of Rice","b":"1F33E","j":["nature","plant"],"k":[7,0]},"speak_no_evil":{"a":"Speak-No-Evil Monkey","b":"1F64A","j":["monkey","animal","nature","omg"],"k":[32,45]},"flag-hm":{"a":"Heard & Mcdonald Islands Flag","b":"1F1ED-1F1F2","k":[2,27]},"ballot_box_with_check":{"a":"Ballot Box with Check","b":"2611-FE0F","c":"2611","j":["ok","agree","confirm","black-square","vote","election","yes","tick"],"k":[47,22],"o":1},"canoe":{"a":"Canoe","b":"1F6F6","j":["boat","paddle","water","ship"],"k":[37,21],"o":9},"memo":{"a":"Memo","b":"1F4DD","j":["write","documents","stationery","pencil","paper","writing","legal","exam","quiz","test","study","compose"],"k":[26,20],"n":["pencil"]},"herb":{"a":"Herb","b":"1F33F","j":["vegetable","plant","medicine","weed","grass","lawn"],"k":[7,1]},"flag-hn":{"a":"Honduras Flag","b":"1F1ED-1F1F3","k":[2,28]},"heavy_check_mark":{"a":"Heavy Check Mark","b":"2714-FE0F","c":"2714","j":["ok","nike","answer","yes","tick"],"k":[49,44],"o":1},"briefcase":{"a":"Briefcase","b":"1F4BC","j":["business","documents","work","law","legal","job","career"],"k":[25,39]},"speedboat":{"a":"Speedboat","b":"1F6A4","j":["ship","transportation","vehicle","summer"],"k":[35,9]},"baby":{"skin_variations":{"1F3FB":{"unified":"1F476-1F3FB","non_qualified":null,"image":"1f476-1f3fb.png","sheet_x":22,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F476-1F3FC","non_qualified":null,"image":"1f476-1f3fc.png","sheet_x":22,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F476-1F3FD","non_qualified":null,"image":"1f476-1f3fd.png","sheet_x":22,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F476-1F3FE","non_qualified":null,"image":"1f476-1f3fe.png","sheet_x":22,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F476-1F3FF","non_qualified":null,"image":"1f476-1f3ff.png","sheet_x":22,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Baby","b":"1F476","j":["child","boy","girl","toddler"],"k":[22,10]},"heavy_multiplication_x":{"a":"Heavy Multiplication X","b":"2716-FE0F","c":"2716","j":["math","calculation"],"k":[49,45],"o":1},"child":{"skin_variations":{"1F3FB":{"unified":"1F9D2-1F3FB","non_qualified":null,"image":"1f9d2-1f3fb.png","sheet_x":43,"sheet_y":5,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D2-1F3FC","non_qualified":null,"image":"1f9d2-1f3fc.png","sheet_x":43,"sheet_y":6,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D2-1F3FD","non_qualified":null,"image":"1f9d2-1f3fd.png","sheet_x":43,"sheet_y":7,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D2-1F3FE","non_qualified":null,"image":"1f9d2-1f3fe.png","sheet_x":43,"sheet_y":8,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D2-1F3FF","non_qualified":null,"image":"1f9d2-1f3ff.png","sheet_x":43,"sheet_y":9,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Child","b":"1F9D2","k":[43,4],"o":10},"shamrock":{"a":"Shamrock","b":"2618-FE0F","c":"2618","j":["vegetable","plant","nature","irish","clover"],"k":[47,25],"o":4},"passenger_ship":{"a":"Passenger Ship","b":"1F6F3-FE0F","c":"1F6F3","j":["yacht","cruise","ferry"],"k":[37,18],"o":7},"flag-hr":{"a":"Croatia Flag","b":"1F1ED-1F1F7","k":[2,29]},"file_folder":{"a":"File Folder","b":"1F4C1","j":["documents","business","office"],"k":[25,44]},"x":{"a":"Cross Mark","b":"274C","j":["no","delete","remove","cancel"],"k":[50,1]},"four_leaf_clover":{"a":"Four Leaf Clover","b":"1F340","j":["vegetable","plant","nature","lucky","irish"],"k":[7,2]},"open_file_folder":{"a":"Open File Folder","b":"1F4C2","j":["documents","load"],"k":[25,45]},"boy":{"skin_variations":{"1F3FB":{"unified":"1F466-1F3FB","non_qualified":null,"image":"1f466-1f3fb.png","sheet_x":15,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F466-1F3FC","non_qualified":null,"image":"1f466-1f3fc.png","sheet_x":15,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F466-1F3FD","non_qualified":null,"image":"1f466-1f3fd.png","sheet_x":15,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F466-1F3FE","non_qualified":null,"image":"1f466-1f3fe.png","sheet_x":15,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F466-1F3FF","non_qualified":null,"image":"1f466-1f3ff.png","sheet_x":15,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Boy","b":"1F466","j":["man","male","guy","teenager"],"k":[15,42]},"ferry":{"a":"Ferry","b":"26F4-FE0F","c":"26F4","j":["boat","ship","yacht"],"k":[48,42],"o":5},"flag-ht":{"a":"Haiti Flag","b":"1F1ED-1F1F9","k":[2,30]},"girl":{"skin_variations":{"1F3FB":{"unified":"1F467-1F3FB","non_qualified":null,"image":"1f467-1f3fb.png","sheet_x":15,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F467-1F3FC","non_qualified":null,"image":"1f467-1f3fc.png","sheet_x":15,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F467-1F3FD","non_qualified":null,"image":"1f467-1f3fd.png","sheet_x":15,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F467-1F3FE","non_qualified":null,"image":"1f467-1f3fe.png","sheet_x":16,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F467-1F3FF","non_qualified":null,"image":"1f467-1f3ff.png","sheet_x":16,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Girl","b":"1F467","j":["female","woman","teenager"],"k":[15,48]},"negative_squared_cross_mark":{"a":"Negative Squared Cross Mark","b":"274E","j":["x","green-square","no","deny"],"k":[50,2]},"flag-hu":{"a":"Hungary Flag","b":"1F1ED-1F1FA","k":[2,31]},"card_index_dividers":{"a":"Card Index Dividers","b":"1F5C2-FE0F","c":"1F5C2","j":["organizing","business","stationery"],"k":[30,4],"o":7},"maple_leaf":{"a":"Maple Leaf","b":"1F341","j":["nature","plant","vegetable","ca","fall"],"k":[7,3]},"motor_boat":{"a":"Motor Boat","b":"1F6E5-FE0F","c":"1F6E5","j":["ship"],"k":[37,13],"o":7},"flag-ic":{"a":"Canary Islands Flag","b":"1F1EE-1F1E8","k":[2,32]},"fallen_leaf":{"a":"Fallen Leaf","b":"1F342","j":["nature","plant","vegetable","leaves"],"k":[7,4]},"adult":{"skin_variations":{"1F3FB":{"unified":"1F9D1-1F3FB","non_qualified":null,"image":"1f9d1-1f3fb.png","sheet_x":42,"sheet_y":51,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D1-1F3FC","non_qualified":null,"image":"1f9d1-1f3fc.png","sheet_x":43,"sheet_y":0,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D1-1F3FD","non_qualified":null,"image":"1f9d1-1f3fd.png","sheet_x":43,"sheet_y":1,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D1-1F3FE","non_qualified":null,"image":"1f9d1-1f3fe.png","sheet_x":43,"sheet_y":2,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D1-1F3FF","non_qualified":null,"image":"1f9d1-1f3ff.png","sheet_x":43,"sheet_y":3,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Adult","b":"1F9D1","k":[42,50],"o":10},"ship":{"a":"Ship","b":"1F6A2","j":["transportation","titanic","deploy"],"k":[34,42]},"heavy_plus_sign":{"a":"Heavy Plus Sign","b":"2795","j":["math","calculation","addition","more","increase"],"k":[50,9]},"date":{"a":"Calendar","b":"1F4C5","j":["calendar","schedule"],"k":[25,48]},"man":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB","non_qualified":null,"image":"1f468-1f3fb.png","sheet_x":18,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F468-1F3FC","non_qualified":null,"image":"1f468-1f3fc.png","sheet_x":18,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F468-1F3FD","non_qualified":null,"image":"1f468-1f3fd.png","sheet_x":18,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F468-1F3FE","non_qualified":null,"image":"1f468-1f3fe.png","sheet_x":18,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F468-1F3FF","non_qualified":null,"image":"1f468-1f3ff.png","sheet_x":18,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Man","b":"1F468","j":["mustache","father","dad","guy","classy","sir","moustache"],"k":[18,11]},"flag-id":{"a":"Indonesia Flag","b":"1F1EE-1F1E9","k":[2,33]},"leaves":{"a":"Leaf Fluttering in Wind","b":"1F343","j":["nature","plant","tree","vegetable","grass","lawn","spring"],"k":[7,5]},"heavy_minus_sign":{"a":"Heavy Minus Sign","b":"2796","j":["math","calculation","subtract","less"],"k":[50,10]},"calendar":{"a":"Tear-off Calendar","b":"1F4C6","j":["schedule","date","planning"],"k":[25,49]},"airplane":{"a":"Airplane","b":"2708-FE0F","c":"2708","j":["vehicle","transportation","flight","fly"],"k":[49,16],"o":1},"spiral_note_pad":{"a":"Spiral Note Pad","b":"1F5D2-FE0F","c":"1F5D2","k":[30,8],"o":7},"heavy_division_sign":{"a":"Heavy Division Sign","b":"2797","j":["divide","math","calculation"],"k":[50,11]},"small_airplane":{"a":"Small Airplane","b":"1F6E9-FE0F","c":"1F6E9","j":["flight","transportation","fly","vehicle"],"k":[37,14],"o":7},"woman":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB","non_qualified":null,"image":"1f469-1f3fb.png","sheet_x":20,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F469-1F3FC","non_qualified":null,"image":"1f469-1f3fc.png","sheet_x":20,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F469-1F3FD","non_qualified":null,"image":"1f469-1f3fd.png","sheet_x":20,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F469-1F3FE","non_qualified":null,"image":"1f469-1f3fe.png","sheet_x":20,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F469-1F3FF","non_qualified":null,"image":"1f469-1f3ff.png","sheet_x":20,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Woman","b":"1F469","j":["female","girls","lady"],"k":[20,23]},"flag-ie":{"a":"Ireland Flag","b":"1F1EE-1F1EA","k":[2,34]},"curly_loop":{"a":"Curly Loop","b":"27B0","j":["scribble","draw","shape","squiggle"],"k":[50,13]},"flag-il":{"a":"Israel Flag","b":"1F1EE-1F1F1","k":[2,35]},"airplane_departure":{"a":"Airplane Departure","b":"1F6EB","k":[37,15],"o":7},"spiral_calendar_pad":{"a":"Spiral Calendar Pad","b":"1F5D3-FE0F","c":"1F5D3","k":[30,9],"o":7},"older_adult":{"skin_variations":{"1F3FB":{"unified":"1F9D3-1F3FB","non_qualified":null,"image":"1f9d3-1f3fb.png","sheet_x":43,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D3-1F3FC","non_qualified":null,"image":"1f9d3-1f3fc.png","sheet_x":43,"sheet_y":12,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D3-1F3FD","non_qualified":null,"image":"1f9d3-1f3fd.png","sheet_x":43,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D3-1F3FE","non_qualified":null,"image":"1f9d3-1f3fe.png","sheet_x":43,"sheet_y":14,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D3-1F3FF","non_qualified":null,"image":"1f9d3-1f3ff.png","sheet_x":43,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Older Adult","b":"1F9D3","k":[43,10],"o":10},"airplane_arriving":{"a":"Airplane Arriving","b":"1F6EC","k":[37,16],"o":7},"card_index":{"a":"Card Index","b":"1F4C7","j":["business","stationery"],"k":[25,50]},"loop":{"a":"Double Curly Loop","b":"27BF","j":["tape","cassette"],"k":[50,14]},"older_man":{"skin_variations":{"1F3FB":{"unified":"1F474-1F3FB","non_qualified":null,"image":"1f474-1f3fb.png","sheet_x":21,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F474-1F3FC","non_qualified":null,"image":"1f474-1f3fc.png","sheet_x":22,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F474-1F3FD","non_qualified":null,"image":"1f474-1f3fd.png","sheet_x":22,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F474-1F3FE","non_qualified":null,"image":"1f474-1f3fe.png","sheet_x":22,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F474-1F3FF","non_qualified":null,"image":"1f474-1f3ff.png","sheet_x":22,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Older Man","b":"1F474","j":["human","male","men","old","elder","senior"],"k":[21,50]},"flag-im":{"a":"Isle of Man Flag","b":"1F1EE-1F1F2","k":[2,36]},"flag-in":{"a":"India Flag","b":"1F1EE-1F1F3","k":[2,37]},"chart_with_upwards_trend":{"a":"Chart with Upwards Trend","b":"1F4C8","j":["graph","presentation","stats","recovery","business","economics","money","sales","good","success"],"k":[25,51]},"part_alternation_mark":{"a":"Part Alternation Mark","b":"303D-FE0F","c":"303D","j":["graph","presentation","stats","business","economics","bad"],"k":[50,25],"o":3},"seat":{"a":"Seat","b":"1F4BA","j":["sit","airplane","transport","bus","flight","fly"],"k":[25,37]},"older_woman":{"skin_variations":{"1F3FB":{"unified":"1F475-1F3FB","non_qualified":null,"image":"1f475-1f3fb.png","sheet_x":22,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F475-1F3FC","non_qualified":null,"image":"1f475-1f3fc.png","sheet_x":22,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F475-1F3FD","non_qualified":null,"image":"1f475-1f3fd.png","sheet_x":22,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F475-1F3FE","non_qualified":null,"image":"1f475-1f3fe.png","sheet_x":22,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F475-1F3FF","non_qualified":null,"image":"1f475-1f3ff.png","sheet_x":22,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Older Woman","b":"1F475","j":["human","female","women","lady","old","elder","senior"],"k":[22,4]},"eight_spoked_asterisk":{"a":"Eight Spoked Asterisk","b":"2733-FE0F","c":"2733","j":["star","sparkle","green-square"],"k":[49,49],"o":1},"chart_with_downwards_trend":{"a":"Chart with Downwards Trend","b":"1F4C9","j":["graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],"k":[26,0]},"flag-io":{"a":"British Indian Ocean Territory Flag","b":"1F1EE-1F1F4","k":[2,38]},"male-doctor":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2695-FE0F","non_qualified":"1F468-1F3FB-200D-2695","image":"1f468-1f3fb-200d-2695-fe0f.png","sheet_x":17,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-2695-FE0F","non_qualified":"1F468-1F3FC-200D-2695","image":"1f468-1f3fc-200d-2695-fe0f.png","sheet_x":17,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-2695-FE0F","non_qualified":"1F468-1F3FD-200D-2695","image":"1f468-1f3fd-200d-2695-fe0f.png","sheet_x":17,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-2695-FE0F","non_qualified":"1F468-1F3FE-200D-2695","image":"1f468-1f3fe-200d-2695-fe0f.png","sheet_x":17,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-2695-FE0F","non_qualified":"1F468-1F3FF-200D-2695","image":"1f468-1f3ff-200d-2695-fe0f.png","sheet_x":17,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Male Doctor","b":"1F468-200D-2695-FE0F","c":"1F468-200D-2695","k":[17,43]},"helicopter":{"a":"Helicopter","b":"1F681","j":["transportation","vehicle","fly"],"k":[34,9]},"female-doctor":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2695-FE0F","non_qualified":"1F469-1F3FB-200D-2695","image":"1f469-1f3fb-200d-2695-fe0f.png","sheet_x":20,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-2695-FE0F","non_qualified":"1F469-1F3FC-200D-2695","image":"1f469-1f3fc-200d-2695-fe0f.png","sheet_x":20,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-2695-FE0F","non_qualified":"1F469-1F3FD-200D-2695","image":"1f469-1f3fd-200d-2695-fe0f.png","sheet_x":20,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-2695-FE0F","non_qualified":"1F469-1F3FE-200D-2695","image":"1f469-1f3fe-200d-2695-fe0f.png","sheet_x":20,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-2695-FE0F","non_qualified":"1F469-1F3FF-200D-2695","image":"1f469-1f3ff-200d-2695-fe0f.png","sheet_x":20,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Doctor","b":"1F469-200D-2695-FE0F","c":"1F469-200D-2695","k":[20,1]},"suspension_railway":{"a":"Suspension Railway","b":"1F69F","j":["vehicle","transportation"],"k":[34,39]},"bar_chart":{"a":"Bar Chart","b":"1F4CA","j":["graph","presentation","stats"],"k":[26,1]},"flag-iq":{"a":"Iraq Flag","b":"1F1EE-1F1F6","k":[2,39]},"eight_pointed_black_star":{"a":"Eight Pointed Black Star","b":"2734-FE0F","c":"2734","j":["orange-square","shape","polygon"],"k":[49,50],"o":1},"mountain_cableway":{"a":"Mountain Cableway","b":"1F6A0","j":["transportation","vehicle","ski"],"k":[34,40]},"male-student":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F393","non_qualified":null,"image":"1f468-1f3fb-200d-1f393.png","sheet_x":16,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F393","non_qualified":null,"image":"1f468-1f3fc-200d-1f393.png","sheet_x":16,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F393","non_qualified":null,"image":"1f468-1f3fd-200d-1f393.png","sheet_x":16,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F393","non_qualified":null,"image":"1f468-1f3fe-200d-1f393.png","sheet_x":16,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F393","non_qualified":null,"image":"1f468-1f3ff-200d-1f393.png","sheet_x":16,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Student","b":"1F468-200D-1F393","k":[16,14]},"clipboard":{"a":"Clipboard","b":"1F4CB","j":["stationery","documents"],"k":[26,2]},"flag-ir":{"a":"Iran Flag","b":"1F1EE-1F1F7","k":[2,40]},"sparkle":{"a":"Sparkle","b":"2747-FE0F","c":"2747","j":["stars","green-square","awesome","good","fireworks"],"k":[50,0],"o":1},"female-student":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F393","non_qualified":null,"image":"1f469-1f3fb-200d-1f393.png","sheet_x":18,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F393","non_qualified":null,"image":"1f469-1f3fc-200d-1f393.png","sheet_x":18,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F393","non_qualified":null,"image":"1f469-1f3fd-200d-1f393.png","sheet_x":18,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F393","non_qualified":null,"image":"1f469-1f3fe-200d-1f393.png","sheet_x":18,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F393","non_qualified":null,"image":"1f469-1f3ff-200d-1f393.png","sheet_x":18,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Student","b":"1F469-200D-1F393","k":[18,29]},"pushpin":{"a":"Pushpin","b":"1F4CC","j":["stationery","mark","here"],"k":[26,3]},"aerial_tramway":{"a":"Aerial Tramway","b":"1F6A1","j":["transportation","vehicle","ski"],"k":[34,41]},"flag-is":{"a":"Iceland Flag","b":"1F1EE-1F1F8","k":[2,41]},"bangbang":{"a":"Double Exclamation Mark","b":"203C-FE0F","c":"203C","j":["exclamation","surprise"],"k":[46,29],"o":1},"interrobang":{"a":"Exclamation Question Mark","b":"2049-FE0F","c":"2049","j":["wat","punctuation","surprise"],"k":[46,30],"o":3},"satellite":{"a":"Satellite","b":"1F6F0-FE0F","c":"1F6F0","j":["communication","future","radio","space"],"k":[37,17],"o":7},"it":{"a":"Italy Flag","b":"1F1EE-1F1F9","j":["italy","flag","nation","country","banner"],"k":[2,42],"n":["flag-it"]},"male-teacher":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fb-200d-1f3eb.png","sheet_x":16,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fc-200d-1f3eb.png","sheet_x":16,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fd-200d-1f3eb.png","sheet_x":16,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3EB","non_qualified":null,"image":"1f468-1f3fe-200d-1f3eb.png","sheet_x":16,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3EB","non_qualified":null,"image":"1f468-1f3ff-200d-1f3eb.png","sheet_x":16,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Teacher","b":"1F468-200D-1F3EB","k":[16,32]},"round_pushpin":{"a":"Round Pushpin","b":"1F4CD","j":["stationery","location","map","here"],"k":[26,4]},"flag-je":{"a":"Jersey Flag","b":"1F1EF-1F1EA","k":[2,43]},"question":{"a":"Black Question Mark Ornament","b":"2753","j":["doubt","confused"],"k":[50,3]},"rocket":{"a":"Rocket","b":"1F680","j":["launch","ship","staffmode","NASA","outer space","outer_space","fly"],"k":[34,8]},"female-teacher":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fb-200d-1f3eb.png","sheet_x":18,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fc-200d-1f3eb.png","sheet_x":18,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fd-200d-1f3eb.png","sheet_x":18,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3EB","non_qualified":null,"image":"1f469-1f3fe-200d-1f3eb.png","sheet_x":18,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3EB","non_qualified":null,"image":"1f469-1f3ff-200d-1f3eb.png","sheet_x":19,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Teacher","b":"1F469-200D-1F3EB","k":[18,47]},"paperclip":{"a":"Paperclip","b":"1F4CE","j":["documents","stationery"],"k":[26,5]},"linked_paperclips":{"a":"Linked Paperclips","b":"1F587-FE0F","c":"1F587","k":[29,27],"o":7},"flying_saucer":{"a":"Flying Saucer","b":"1F6F8","k":[37,23],"o":10},"male-judge":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2696-FE0F","non_qualified":"1F468-1F3FB-200D-2696","image":"1f468-1f3fb-200d-2696-fe0f.png","sheet_x":17,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-2696-FE0F","non_qualified":"1F468-1F3FC-200D-2696","image":"1f468-1f3fc-200d-2696-fe0f.png","sheet_x":17,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-2696-FE0F","non_qualified":"1F468-1F3FD-200D-2696","image":"1f468-1f3fd-200d-2696-fe0f.png","sheet_x":18,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-2696-FE0F","non_qualified":"1F468-1F3FE-200D-2696","image":"1f468-1f3fe-200d-2696-fe0f.png","sheet_x":18,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-2696-FE0F","non_qualified":"1F468-1F3FF-200D-2696","image":"1f468-1f3ff-200d-2696-fe0f.png","sheet_x":18,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Male Judge","b":"1F468-200D-2696-FE0F","c":"1F468-200D-2696","k":[17,49]},"grey_question":{"a":"White Question Mark Ornament","b":"2754","j":["doubts","gray","huh","confused"],"k":[50,4]},"flag-jm":{"a":"Jamaica Flag","b":"1F1EF-1F1F2","k":[2,44]},"bellhop_bell":{"a":"Bellhop Bell","b":"1F6CE-FE0F","c":"1F6CE","j":["service"],"k":[37,3],"o":7},"straight_ruler":{"a":"Straight Ruler","b":"1F4CF","j":["stationery","calculate","length","math","school","drawing","architect","sketch"],"k":[26,6]},"flag-jo":{"a":"Jordan Flag","b":"1F1EF-1F1F4","k":[2,45]},"female-judge":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2696-FE0F","non_qualified":"1F469-1F3FB-200D-2696","image":"1f469-1f3fb-200d-2696-fe0f.png","sheet_x":20,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-2696-FE0F","non_qualified":"1F469-1F3FC-200D-2696","image":"1f469-1f3fc-200d-2696-fe0f.png","sheet_x":20,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-2696-FE0F","non_qualified":"1F469-1F3FD-200D-2696","image":"1f469-1f3fd-200d-2696-fe0f.png","sheet_x":20,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-2696-FE0F","non_qualified":"1F469-1F3FE-200D-2696","image":"1f469-1f3fe-200d-2696-fe0f.png","sheet_x":20,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-2696-FE0F","non_qualified":"1F469-1F3FF-200D-2696","image":"1f469-1f3ff-200d-2696-fe0f.png","sheet_x":20,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Judge","b":"1F469-200D-2696-FE0F","c":"1F469-200D-2696","k":[20,7]},"grey_exclamation":{"a":"White Exclamation Mark Ornament","b":"2755","j":["surprise","punctuation","gray","wow","warning"],"k":[50,5]},"door":{"a":"Door","b":"1F6AA","j":["house","entry","exit"],"k":[35,15]},"male-farmer":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F33E","non_qualified":null,"image":"1f468-1f3fb-200d-1f33e.png","sheet_x":16,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F33E","non_qualified":null,"image":"1f468-1f3fc-200d-1f33e.png","sheet_x":16,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F33E","non_qualified":null,"image":"1f468-1f3fd-200d-1f33e.png","sheet_x":16,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F33E","non_qualified":null,"image":"1f468-1f3fe-200d-1f33e.png","sheet_x":16,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F33E","non_qualified":null,"image":"1f468-1f3ff-200d-1f33e.png","sheet_x":16,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Farmer","b":"1F468-200D-1F33E","k":[16,2]},"jp":{"a":"Japan Flag","b":"1F1EF-1F1F5","j":["japanese","nation","flag","country","banner"],"k":[2,46],"n":["flag-jp"]},"triangular_ruler":{"a":"Triangular Ruler","b":"1F4D0","j":["stationery","math","architect","sketch"],"k":[26,7]},"exclamation":{"a":"Heavy Exclamation Mark Symbol","b":"2757","j":["heavy_exclamation_mark","danger","surprise","punctuation","wow","warning"],"k":[50,6],"n":["heavy_exclamation_mark"],"o":5},"bed":{"a":"Bed","b":"1F6CF-FE0F","c":"1F6CF","j":["sleep","rest"],"k":[37,4],"o":7},"female-farmer":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F33E","non_qualified":null,"image":"1f469-1f3fb-200d-1f33e.png","sheet_x":18,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F33E","non_qualified":null,"image":"1f469-1f3fc-200d-1f33e.png","sheet_x":18,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F33E","non_qualified":null,"image":"1f469-1f3fd-200d-1f33e.png","sheet_x":18,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F33E","non_qualified":null,"image":"1f469-1f3fe-200d-1f33e.png","sheet_x":18,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F33E","non_qualified":null,"image":"1f469-1f3ff-200d-1f33e.png","sheet_x":18,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Farmer","b":"1F469-200D-1F33E","k":[18,17]},"scissors":{"a":"Black Scissors","b":"2702-FE0F","c":"2702","j":["stationery","cut"],"k":[49,14],"o":1},"wavy_dash":{"a":"Wavy Dash","b":"3030-FE0F","c":"3030","j":["draw","line","moustache","mustache","squiggle","scribble"],"k":[50,24],"o":1},"flag-ke":{"a":"Kenya Flag","b":"1F1F0-1F1EA","k":[2,47]},"flag-kg":{"a":"Kyrgyzstan Flag","b":"1F1F0-1F1EC","k":[2,48]},"couch_and_lamp":{"a":"Couch and Lamp","b":"1F6CB-FE0F","c":"1F6CB","j":["read","chill"],"k":[36,47],"o":7},"male-cook":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F373","non_qualified":null,"image":"1f468-1f3fb-200d-1f373.png","sheet_x":16,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F373","non_qualified":null,"image":"1f468-1f3fc-200d-1f373.png","sheet_x":16,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F373","non_qualified":null,"image":"1f468-1f3fd-200d-1f373.png","sheet_x":16,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F373","non_qualified":null,"image":"1f468-1f3fe-200d-1f373.png","sheet_x":16,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F373","non_qualified":null,"image":"1f468-1f3ff-200d-1f373.png","sheet_x":16,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Cook","b":"1F468-200D-1F373","k":[16,8]},"card_file_box":{"a":"Card File Box","b":"1F5C3-FE0F","c":"1F5C3","j":["business","stationery"],"k":[30,5],"o":7},"copyright":{"a":"Copyright Sign","b":"00A9-FE0F","c":"00A9","j":["ip","license","circle","law","legal"],"k":[0,12],"o":1},"file_cabinet":{"a":"File Cabinet","b":"1F5C4-FE0F","c":"1F5C4","j":["filing","organizing"],"k":[30,6],"o":7},"registered":{"a":"Registered Sign","b":"00AE-FE0F","c":"00AE","j":["alphabet","circle"],"k":[0,13],"o":1},"flag-kh":{"a":"Cambodia Flag","b":"1F1F0-1F1ED","k":[2,49]},"female-cook":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F373","non_qualified":null,"image":"1f469-1f3fb-200d-1f373.png","sheet_x":18,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F373","non_qualified":null,"image":"1f469-1f3fc-200d-1f373.png","sheet_x":18,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F373","non_qualified":null,"image":"1f469-1f3fd-200d-1f373.png","sheet_x":18,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F373","non_qualified":null,"image":"1f469-1f3fe-200d-1f373.png","sheet_x":18,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F373","non_qualified":null,"image":"1f469-1f3ff-200d-1f373.png","sheet_x":18,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Cook","b":"1F469-200D-1F373","k":[18,23]},"toilet":{"a":"Toilet","b":"1F6BD","j":["restroom","wc","washroom","bathroom","potty"],"k":[36,33]},"wastebasket":{"a":"Wastebasket","b":"1F5D1-FE0F","c":"1F5D1","j":["bin","trash","rubbish","garbage","toss"],"k":[30,7],"o":7},"flag-ki":{"a":"Kiribati Flag","b":"1F1F0-1F1EE","k":[2,50]},"shower":{"a":"Shower","b":"1F6BF","j":["clean","water","bathroom"],"k":[36,35]},"male-mechanic":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F527","non_qualified":null,"image":"1f468-1f3fb-200d-1f527.png","sheet_x":17,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F527","non_qualified":null,"image":"1f468-1f3fc-200d-1f527.png","sheet_x":17,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F527","non_qualified":null,"image":"1f468-1f3fd-200d-1f527.png","sheet_x":17,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F527","non_qualified":null,"image":"1f468-1f3fe-200d-1f527.png","sheet_x":17,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F527","non_qualified":null,"image":"1f468-1f3ff-200d-1f527.png","sheet_x":17,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Mechanic","b":"1F468-200D-1F527","k":[17,19]},"tm":{"a":"Trade Mark Sign","b":"2122-FE0F","c":"2122","j":["trademark","brand","law","legal"],"k":[46,31],"o":1},"hash":{"a":"Hash Key","b":"0023-FE0F-20E3","c":"0023-20E3","j":["symbol","blue-square","twitter"],"k":[0,0],"o":3},"flag-km":{"a":"Comoros Flag","b":"1F1F0-1F1F2","k":[2,51]},"bathtub":{"a":"Bathtub","b":"1F6C1","j":["clean","shower","bathroom"],"k":[36,42]},"female-mechanic":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F527","non_qualified":null,"image":"1f469-1f3fb-200d-1f527.png","sheet_x":19,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F527","non_qualified":null,"image":"1f469-1f3fc-200d-1f527.png","sheet_x":19,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F527","non_qualified":null,"image":"1f469-1f3fd-200d-1f527.png","sheet_x":19,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F527","non_qualified":null,"image":"1f469-1f3fe-200d-1f527.png","sheet_x":19,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F527","non_qualified":null,"image":"1f469-1f3ff-200d-1f527.png","sheet_x":19,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Mechanic","b":"1F469-200D-1F527","k":[19,29]},"lock":{"a":"Lock","b":"1F512","j":["security","password","padlock"],"k":[27,20]},"male-factory-worker":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fb-200d-1f3ed.png","sheet_x":16,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fc-200d-1f3ed.png","sheet_x":16,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fd-200d-1f3ed.png","sheet_x":16,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3ED","non_qualified":null,"image":"1f468-1f3fe-200d-1f3ed.png","sheet_x":16,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3ED","non_qualified":null,"image":"1f468-1f3ff-200d-1f3ed.png","sheet_x":16,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Factory Worker","b":"1F468-200D-1F3ED","k":[16,38]},"flag-kn":{"a":"St. Kitts & Nevis Flag","b":"1F1F0-1F1F3","k":[3,0]},"hourglass":{"a":"Hourglass","b":"231B","j":["time","clock","oldschool","limit","exam","quiz","test"],"k":[46,42],"o":1},"keycap_star":{"a":"Keycap Star","b":"002A-FE0F-20E3","c":"002A-20E3","k":[0,1],"o":3},"unlock":{"a":"Open Lock","b":"1F513","j":["privacy","security"],"k":[27,21]},"flag-kp":{"a":"North Korea Flag","b":"1F1F0-1F1F5","k":[3,1]},"female-factory-worker":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fb-200d-1f3ed.png","sheet_x":19,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fc-200d-1f3ed.png","sheet_x":19,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fd-200d-1f3ed.png","sheet_x":19,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3ED","non_qualified":null,"image":"1f469-1f3fe-200d-1f3ed.png","sheet_x":19,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3ED","non_qualified":null,"image":"1f469-1f3ff-200d-1f3ed.png","sheet_x":19,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Factory Worker","b":"1F469-200D-1F3ED","k":[19,1]},"zero":{"a":"Keycap 0","b":"0030-FE0F-20E3","c":"0030-20E3","j":["0","numbers","blue-square","null"],"k":[0,2],"o":3},"lock_with_ink_pen":{"a":"Lock with Ink Pen","b":"1F50F","j":["security","secret"],"k":[27,17]},"hourglass_flowing_sand":{"a":"Hourglass with Flowing Sand","b":"23F3","j":["oldschool","time","countdown"],"k":[47,3]},"one":{"a":"Keycap 1","b":"0031-FE0F-20E3","c":"0031-20E3","j":["blue-square","numbers","1"],"k":[0,3],"o":3},"kr":{"a":"South Korea Flag","b":"1F1F0-1F1F7","j":["south","korea","nation","flag","country","banner"],"k":[3,2],"n":["flag-kr"]},"watch":{"a":"Watch","b":"231A","j":["time","accessories"],"k":[46,41],"o":1},"male-office-worker":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fb-200d-1f4bc.png","sheet_x":17,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fc-200d-1f4bc.png","sheet_x":17,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fd-200d-1f4bc.png","sheet_x":17,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F4BC","non_qualified":null,"image":"1f468-1f3fe-200d-1f4bc.png","sheet_x":17,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F4BC","non_qualified":null,"image":"1f468-1f3ff-200d-1f4bc.png","sheet_x":17,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Office Worker","b":"1F468-200D-1F4BC","k":[17,13]},"closed_lock_with_key":{"a":"Closed Lock with Key","b":"1F510","j":["security","privacy"],"k":[27,18]},"female-office-worker":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fb-200d-1f4bc.png","sheet_x":19,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fc-200d-1f4bc.png","sheet_x":19,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fd-200d-1f4bc.png","sheet_x":19,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F4BC","non_qualified":null,"image":"1f469-1f3fe-200d-1f4bc.png","sheet_x":19,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F4BC","non_qualified":null,"image":"1f469-1f3ff-200d-1f4bc.png","sheet_x":19,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Office Worker","b":"1F469-200D-1F4BC","k":[19,23]},"two":{"a":"Keycap 2","b":"0032-FE0F-20E3","c":"0032-20E3","j":["numbers","2","prime","blue-square"],"k":[0,4],"o":3},"alarm_clock":{"a":"Alarm Clock","b":"23F0","j":["time","wake"],"k":[47,0]},"key":{"a":"Key","b":"1F511","j":["lock","door","password"],"k":[27,19]},"flag-kw":{"a":"Kuwait Flag","b":"1F1F0-1F1FC","k":[3,3]},"stopwatch":{"a":"Stopwatch","b":"23F1-FE0F","c":"23F1","j":["time","deadline"],"k":[47,1]},"male-scientist":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F52C","non_qualified":null,"image":"1f468-1f3fb-200d-1f52c.png","sheet_x":17,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F52C","non_qualified":null,"image":"1f468-1f3fc-200d-1f52c.png","sheet_x":17,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F52C","non_qualified":null,"image":"1f468-1f3fd-200d-1f52c.png","sheet_x":17,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F52C","non_qualified":null,"image":"1f468-1f3fe-200d-1f52c.png","sheet_x":17,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F52C","non_qualified":null,"image":"1f468-1f3ff-200d-1f52c.png","sheet_x":17,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Scientist","b":"1F468-200D-1F52C","k":[17,25]},"three":{"a":"Keycap 3","b":"0033-FE0F-20E3","c":"0033-20E3","j":["3","numbers","prime","blue-square"],"k":[0,5],"o":3},"flag-ky":{"a":"Cayman Islands Flag","b":"1F1F0-1F1FE","k":[3,4]},"old_key":{"a":"Old Key","b":"1F5DD-FE0F","c":"1F5DD","j":["lock","door","password"],"k":[30,11],"o":7},"flag-kz":{"a":"Kazakhstan Flag","b":"1F1F0-1F1FF","k":[3,5]},"hammer":{"a":"Hammer","b":"1F528","j":["tools","build","create"],"k":[27,42]},"female-scientist":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F52C","non_qualified":null,"image":"1f469-1f3fb-200d-1f52c.png","sheet_x":19,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F52C","non_qualified":null,"image":"1f469-1f3fc-200d-1f52c.png","sheet_x":19,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F52C","non_qualified":null,"image":"1f469-1f3fd-200d-1f52c.png","sheet_x":19,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F52C","non_qualified":null,"image":"1f469-1f3fe-200d-1f52c.png","sheet_x":19,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F52C","non_qualified":null,"image":"1f469-1f3ff-200d-1f52c.png","sheet_x":19,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Scientist","b":"1F469-200D-1F52C","k":[19,35]},"timer_clock":{"a":"Timer Clock","b":"23F2-FE0F","c":"23F2","j":["alarm"],"k":[47,2]},"four":{"a":"Keycap 4","b":"0034-FE0F-20E3","c":"0034-20E3","j":["4","numbers","blue-square"],"k":[0,6],"o":3},"male-technologist":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fb-200d-1f4bb.png","sheet_x":17,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fc-200d-1f4bb.png","sheet_x":17,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fd-200d-1f4bb.png","sheet_x":17,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F4BB","non_qualified":null,"image":"1f468-1f3fe-200d-1f4bb.png","sheet_x":17,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F4BB","non_qualified":null,"image":"1f468-1f3ff-200d-1f4bb.png","sheet_x":17,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Technologist","b":"1F468-200D-1F4BB","k":[17,7]},"mantelpiece_clock":{"a":"Mantelpiece Clock","b":"1F570-FE0F","c":"1F570","j":["time"],"k":[28,43],"o":7},"five":{"a":"Keycap 5","b":"0035-FE0F-20E3","c":"0035-20E3","j":["5","numbers","blue-square","prime"],"k":[0,7],"o":3},"flag-la":{"a":"Laos Flag","b":"1F1F1-1F1E6","k":[3,6]},"pick":{"a":"Pick","b":"26CF-FE0F","c":"26CF","j":["tools","dig"],"k":[48,32],"o":5},"flag-lb":{"a":"Lebanon Flag","b":"1F1F1-1F1E7","k":[3,7]},"clock12":{"a":"Clock Face Twelve Oclock","b":"1F55B","j":["time","noon","midnight","midday","late","early","schedule"],"k":[28,29]},"hammer_and_pick":{"a":"Hammer and Pick","b":"2692-FE0F","c":"2692","j":["tools","build","create"],"k":[48,11],"o":4},"six":{"a":"Keycap 6","b":"0036-FE0F-20E3","c":"0036-20E3","j":["6","numbers","blue-square"],"k":[0,8],"o":3},"female-technologist":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fb-200d-1f4bb.png","sheet_x":19,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fc-200d-1f4bb.png","sheet_x":19,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fd-200d-1f4bb.png","sheet_x":19,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F4BB","non_qualified":null,"image":"1f469-1f3fe-200d-1f4bb.png","sheet_x":19,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F4BB","non_qualified":null,"image":"1f469-1f3ff-200d-1f4bb.png","sheet_x":19,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Technologist","b":"1F469-200D-1F4BB","k":[19,17]},"hammer_and_wrench":{"a":"Hammer and Wrench","b":"1F6E0-FE0F","c":"1F6E0","j":["tools","build","create"],"k":[37,8],"o":7},"flag-lc":{"a":"St. Lucia Flag","b":"1F1F1-1F1E8","k":[3,8]},"clock1230":{"a":"Clock Face Twelve-Thirty","b":"1F567","j":["time","late","early","schedule"],"k":[28,41]},"seven":{"a":"Keycap 7","b":"0037-FE0F-20E3","c":"0037-20E3","j":["7","numbers","blue-square","prime"],"k":[0,9],"o":3},"male-singer":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fb-200d-1f3a4.png","sheet_x":16,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fc-200d-1f3a4.png","sheet_x":16,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fd-200d-1f3a4.png","sheet_x":16,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3A4","non_qualified":null,"image":"1f468-1f3fe-200d-1f3a4.png","sheet_x":16,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3A4","non_qualified":null,"image":"1f468-1f3ff-200d-1f3a4.png","sheet_x":16,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Singer","b":"1F468-200D-1F3A4","k":[16,20]},"eight":{"a":"Keycap 8","b":"0038-FE0F-20E3","c":"0038-20E3","j":["8","blue-square","numbers"],"k":[0,10],"o":3},"flag-li":{"a":"Liechtenstein Flag","b":"1F1F1-1F1EE","k":[3,9]},"dagger_knife":{"a":"Dagger Knife","b":"1F5E1-FE0F","c":"1F5E1","k":[30,13],"o":7},"clock1":{"a":"Clock Face One Oclock","b":"1F550","j":["time","late","early","schedule"],"k":[28,18]},"female-singer":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fb-200d-1f3a4.png","sheet_x":18,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fc-200d-1f3a4.png","sheet_x":18,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fd-200d-1f3a4.png","sheet_x":18,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3A4","non_qualified":null,"image":"1f469-1f3fe-200d-1f3a4.png","sheet_x":18,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3A4","non_qualified":null,"image":"1f469-1f3ff-200d-1f3a4.png","sheet_x":18,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Singer","b":"1F469-200D-1F3A4","k":[18,35]},"male-artist":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fb-200d-1f3a8.png","sheet_x":16,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fc-200d-1f3a8.png","sheet_x":16,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fd-200d-1f3a8.png","sheet_x":16,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F3A8","non_qualified":null,"image":"1f468-1f3fe-200d-1f3a8.png","sheet_x":16,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F3A8","non_qualified":null,"image":"1f468-1f3ff-200d-1f3a8.png","sheet_x":16,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Artist","b":"1F468-200D-1F3A8","k":[16,26]},"crossed_swords":{"a":"Crossed Swords","b":"2694-FE0F","c":"2694","j":["weapon"],"k":[48,13],"o":4},"nine":{"a":"Keycap 9","b":"0039-FE0F-20E3","c":"0039-20E3","j":["blue-square","numbers","9"],"k":[0,11],"o":3},"flag-lk":{"a":"Sri Lanka Flag","b":"1F1F1-1F1F0","k":[3,10]},"clock130":{"a":"Clock Face One-Thirty","b":"1F55C","j":["time","late","early","schedule"],"k":[28,30]},"clock2":{"a":"Clock Face Two Oclock","b":"1F551","j":["time","late","early","schedule"],"k":[28,19]},"gun":{"a":"Pistol","b":"1F52B","j":["violence","weapon","pistol","revolver"],"k":[27,45]},"keycap_ten":{"a":"Keycap Ten","b":"1F51F","j":["numbers","10","blue-square"],"k":[27,33]},"female-artist":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fb-200d-1f3a8.png","sheet_x":18,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fc-200d-1f3a8.png","sheet_x":18,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fd-200d-1f3a8.png","sheet_x":18,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F3A8","non_qualified":null,"image":"1f469-1f3fe-200d-1f3a8.png","sheet_x":18,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F3A8","non_qualified":null,"image":"1f469-1f3ff-200d-1f3a8.png","sheet_x":18,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Artist","b":"1F469-200D-1F3A8","k":[18,41]},"flag-lr":{"a":"Liberia Flag","b":"1F1F1-1F1F7","k":[3,11]},"clock230":{"a":"Clock Face Two-Thirty","b":"1F55D","j":["time","late","early","schedule"],"k":[28,31]},"bow_and_arrow":{"a":"Bow and Arrow","b":"1F3F9","j":["sports"],"k":[12,23],"o":8},"male-pilot":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-2708-FE0F","non_qualified":"1F468-1F3FB-200D-2708","image":"1f468-1f3fb-200d-2708-fe0f.png","sheet_x":18,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-2708-FE0F","non_qualified":"1F468-1F3FC-200D-2708","image":"1f468-1f3fc-200d-2708-fe0f.png","sheet_x":18,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-2708-FE0F","non_qualified":"1F468-1F3FD-200D-2708","image":"1f468-1f3fd-200d-2708-fe0f.png","sheet_x":18,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-2708-FE0F","non_qualified":"1F468-1F3FE-200D-2708","image":"1f468-1f3fe-200d-2708-fe0f.png","sheet_x":18,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-2708-FE0F","non_qualified":"1F468-1F3FF-200D-2708","image":"1f468-1f3ff-200d-2708-fe0f.png","sheet_x":18,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Male Pilot","b":"1F468-200D-2708-FE0F","c":"1F468-200D-2708","k":[18,3]},"flag-ls":{"a":"Lesotho Flag","b":"1F1F1-1F1F8","k":[3,12]},"flag-lt":{"a":"Lithuania Flag","b":"1F1F1-1F1F9","k":[3,13]},"capital_abcd":{"a":"Input Symbol for Latin Capital Letters","b":"1F520","j":["alphabet","words","blue-square"],"k":[27,34]},"female-pilot":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-2708-FE0F","non_qualified":"1F469-1F3FB-200D-2708","image":"1f469-1f3fb-200d-2708-fe0f.png","sheet_x":20,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-2708-FE0F","non_qualified":"1F469-1F3FC-200D-2708","image":"1f469-1f3fc-200d-2708-fe0f.png","sheet_x":20,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-2708-FE0F","non_qualified":"1F469-1F3FD-200D-2708","image":"1f469-1f3fd-200d-2708-fe0f.png","sheet_x":20,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-2708-FE0F","non_qualified":"1F469-1F3FE-200D-2708","image":"1f469-1f3fe-200d-2708-fe0f.png","sheet_x":20,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-2708-FE0F","non_qualified":"1F469-1F3FF-200D-2708","image":"1f469-1f3ff-200d-2708-fe0f.png","sheet_x":20,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Pilot","b":"1F469-200D-2708-FE0F","c":"1F469-200D-2708","k":[20,13]},"clock3":{"a":"Clock Face Three Oclock","b":"1F552","j":["time","late","early","schedule"],"k":[28,20]},"shield":{"a":"Shield","b":"1F6E1-FE0F","c":"1F6E1","j":["protection","security"],"k":[37,9],"o":7},"male-astronaut":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F680","non_qualified":null,"image":"1f468-1f3fb-200d-1f680.png","sheet_x":17,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F680","non_qualified":null,"image":"1f468-1f3fc-200d-1f680.png","sheet_x":17,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F680","non_qualified":null,"image":"1f468-1f3fd-200d-1f680.png","sheet_x":17,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F680","non_qualified":null,"image":"1f468-1f3fe-200d-1f680.png","sheet_x":17,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F680","non_qualified":null,"image":"1f468-1f3ff-200d-1f680.png","sheet_x":17,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Astronaut","b":"1F468-200D-1F680","k":[17,31]},"abcd":{"a":"Input Symbol for Latin Small Letters","b":"1F521","j":["blue-square","alphabet"],"k":[27,35]},"clock330":{"a":"Clock Face Three-Thirty","b":"1F55E","j":["time","late","early","schedule"],"k":[28,32]},"flag-lu":{"a":"Luxembourg Flag","b":"1F1F1-1F1FA","k":[3,14]},"wrench":{"a":"Wrench","b":"1F527","j":["tools","diy","ikea","fix","maintainer"],"k":[27,41]},"nut_and_bolt":{"a":"Nut and Bolt","b":"1F529","j":["handy","tools","fix"],"k":[27,43]},"clock4":{"a":"Clock Face Four Oclock","b":"1F553","j":["time","late","early","schedule"],"k":[28,21]},"female-astronaut":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F680","non_qualified":null,"image":"1f469-1f3fb-200d-1f680.png","sheet_x":19,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F680","non_qualified":null,"image":"1f469-1f3fc-200d-1f680.png","sheet_x":19,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F680","non_qualified":null,"image":"1f469-1f3fd-200d-1f680.png","sheet_x":19,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F680","non_qualified":null,"image":"1f469-1f3fe-200d-1f680.png","sheet_x":19,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F680","non_qualified":null,"image":"1f469-1f3ff-200d-1f680.png","sheet_x":19,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Astronaut","b":"1F469-200D-1F680","k":[19,41]},"flag-lv":{"a":"Latvia Flag","b":"1F1F1-1F1FB","k":[3,15]},"gear":{"a":"Gear","b":"2699-FE0F","c":"2699","j":["cog"],"k":[48,17],"o":4},"male-firefighter":{"skin_variations":{"1F3FB":{"unified":"1F468-1F3FB-200D-1F692","non_qualified":null,"image":"1f468-1f3fb-200d-1f692.png","sheet_x":17,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F468-1F3FC-200D-1F692","non_qualified":null,"image":"1f468-1f3fc-200d-1f692.png","sheet_x":17,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F468-1F3FD-200D-1F692","non_qualified":null,"image":"1f468-1f3fd-200d-1f692.png","sheet_x":17,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F468-1F3FE-200D-1F692","non_qualified":null,"image":"1f468-1f3fe-200d-1f692.png","sheet_x":17,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F468-1F3FF-200D-1F692","non_qualified":null,"image":"1f468-1f3ff-200d-1f692.png","sheet_x":17,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Male Firefighter","b":"1F468-200D-1F692","k":[17,37]},"flag-ly":{"a":"Libya Flag","b":"1F1F1-1F1FE","k":[3,16]},"symbols":{"a":"Input Symbol for Symbols","b":"1F523","j":["blue-square","music","note","ampersand","percent","glyphs","characters"],"k":[27,37]},"clock430":{"a":"Clock Face Four-Thirty","b":"1F55F","j":["time","late","early","schedule"],"k":[28,33]},"flag-ma":{"a":"Morocco Flag","b":"1F1F2-1F1E6","k":[3,17]},"compression":{"a":"Compression","b":"1F5DC-FE0F","c":"1F5DC","k":[30,10],"o":7},"female-firefighter":{"skin_variations":{"1F3FB":{"unified":"1F469-1F3FB-200D-1F692","non_qualified":null,"image":"1f469-1f3fb-200d-1f692.png","sheet_x":19,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F469-1F3FC-200D-1F692","non_qualified":null,"image":"1f469-1f3fc-200d-1f692.png","sheet_x":19,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F469-1F3FD-200D-1F692","non_qualified":null,"image":"1f469-1f3fd-200d-1f692.png","sheet_x":19,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F469-1F3FE-200D-1F692","non_qualified":null,"image":"1f469-1f3fe-200d-1f692.png","sheet_x":19,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F469-1F3FF-200D-1F692","non_qualified":null,"image":"1f469-1f3ff-200d-1f692.png","sheet_x":20,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Female Firefighter","b":"1F469-200D-1F692","k":[19,47]},"abc":{"a":"Input Symbol for Latin Letters","b":"1F524","j":["blue-square","alphabet"],"k":[27,38]},"clock5":{"a":"Clock Face Five Oclock","b":"1F554","j":["time","late","early","schedule"],"k":[28,22]},"clock530":{"a":"Clock Face Five-Thirty","b":"1F560","j":["time","late","early","schedule"],"k":[28,34]},"a":{"a":"Negative Squared Latin Capital Letter a","b":"1F170-FE0F","c":"1F170","j":["red-square","alphabet","letter"],"k":[0,16]},"alembic":{"a":"Alembic","b":"2697-FE0F","c":"2697","j":["distilling","science","experiment","chemistry"],"k":[48,16],"o":4},"flag-mc":{"a":"Monaco Flag","b":"1F1F2-1F1E8","k":[3,18]},"cop":{"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB","non_qualified":null,"image":"1f46e-1f3fb.png","sheet_x":20,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F46E-1F3FC","non_qualified":null,"image":"1f46e-1f3fc.png","sheet_x":20,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F46E-1F3FD","non_qualified":null,"image":"1f46e-1f3fd.png","sheet_x":20,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F46E-1F3FE","non_qualified":null,"image":"1f46e-1f3fe.png","sheet_x":20,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F46E-1F3FF","non_qualified":null,"image":"1f46e-1f3ff.png","sheet_x":20,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F46E-200D-2642-FE0F","a":"Police Officer","b":"1F46E","k":[20,45]},"scales":{"a":"Scales","b":"2696-FE0F","c":"2696","k":[48,15],"o":4},"clock6":{"a":"Clock Face Six Oclock","b":"1F555","j":["time","late","early","schedule","dawn","dusk"],"k":[28,23]},"flag-md":{"a":"Moldova Flag","b":"1F1F2-1F1E9","k":[3,19]},"ab":{"a":"Negative Squared Ab","b":"1F18E","j":["red-square","alphabet"],"k":[0,20]},"male-police-officer":{"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB-200D-2642-FE0F","non_qualified":"1F46E-1F3FB-200D-2642","image":"1f46e-1f3fb-200d-2642-fe0f.png","sheet_x":20,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F46E-1F3FC-200D-2642-FE0F","non_qualified":"1F46E-1F3FC-200D-2642","image":"1f46e-1f3fc-200d-2642-fe0f.png","sheet_x":20,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F46E-1F3FD-200D-2642-FE0F","non_qualified":"1F46E-1F3FD-200D-2642","image":"1f46e-1f3fd-200d-2642-fe0f.png","sheet_x":20,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F46E-1F3FE-200D-2642-FE0F","non_qualified":"1F46E-1F3FE-200D-2642","image":"1f46e-1f3fe-200d-2642-fe0f.png","sheet_x":20,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F46E-1F3FF-200D-2642-FE0F","non_qualified":"1F46E-1F3FF-200D-2642","image":"1f46e-1f3ff-200d-2642-fe0f.png","sheet_x":20,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F46E","a":"Male Police Officer","b":"1F46E-200D-2642-FE0F","c":"1F46E-200D-2642","k":[20,39]},"link":{"a":"Link Symbol","b":"1F517","j":["rings","url"],"k":[27,25]},"flag-me":{"a":"Montenegro Flag","b":"1F1F2-1F1EA","k":[3,20]},"clock630":{"a":"Clock Face Six-Thirty","b":"1F561","j":["time","late","early","schedule"],"k":[28,35]},"b":{"a":"Negative Squared Latin Capital Letter B","b":"1F171-FE0F","c":"1F171","j":["red-square","alphabet","letter"],"k":[0,17]},"female-police-officer":{"skin_variations":{"1F3FB":{"unified":"1F46E-1F3FB-200D-2640-FE0F","non_qualified":"1F46E-1F3FB-200D-2640","image":"1f46e-1f3fb-200d-2640-fe0f.png","sheet_x":20,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F46E-1F3FC-200D-2640-FE0F","non_qualified":"1F46E-1F3FC-200D-2640","image":"1f46e-1f3fc-200d-2640-fe0f.png","sheet_x":20,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F46E-1F3FD-200D-2640-FE0F","non_qualified":"1F46E-1F3FD-200D-2640","image":"1f46e-1f3fd-200d-2640-fe0f.png","sheet_x":20,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F46E-1F3FE-200D-2640-FE0F","non_qualified":"1F46E-1F3FE-200D-2640","image":"1f46e-1f3fe-200d-2640-fe0f.png","sheet_x":20,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F46E-1F3FF-200D-2640-FE0F","non_qualified":"1F46E-1F3FF-200D-2640","image":"1f46e-1f3ff-200d-2640-fe0f.png","sheet_x":20,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Police Officer","b":"1F46E-200D-2640-FE0F","c":"1F46E-200D-2640","k":[20,33]},"clock7":{"a":"Clock Face Seven Oclock","b":"1F556","j":["time","late","early","schedule"],"k":[28,24]},"cl":{"a":"Squared Cl","b":"1F191","j":["alphabet","words","red-square"],"k":[0,21]},"sleuth_or_spy":{"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB","non_qualified":null,"image":"1f575-1f3fb.png","sheet_x":29,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F575-1F3FC","non_qualified":null,"image":"1f575-1f3fc.png","sheet_x":29,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F575-1F3FD","non_qualified":null,"image":"1f575-1f3fd.png","sheet_x":29,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F575-1F3FE","non_qualified":null,"image":"1f575-1f3fe.png","sheet_x":29,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F575-1F3FF","non_qualified":null,"image":"1f575-1f3ff.png","sheet_x":29,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"1F575-FE0F-200D-2642-FE0F","a":"Sleuth or Spy","b":"1F575-FE0F","c":"1F575","k":[29,11],"o":7},"flag-mf":{"a":"St. Martin Flag","b":"1F1F2-1F1EB","k":[3,21]},"chains":{"a":"Chains","b":"26D3-FE0F","c":"26D3","j":["lock","arrest"],"k":[48,34],"o":5},"syringe":{"a":"Syringe","b":"1F489","j":["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],"k":[24,35]},"male-detective":{"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB-200D-2642-FE0F","non_qualified":"1F575-1F3FB-200D-2642","image":"1f575-1f3fb-200d-2642-fe0f.png","sheet_x":29,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F575-1F3FC-200D-2642-FE0F","non_qualified":"1F575-1F3FC-200D-2642","image":"1f575-1f3fc-200d-2642-fe0f.png","sheet_x":29,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F575-1F3FD-200D-2642-FE0F","non_qualified":"1F575-1F3FD-200D-2642","image":"1f575-1f3fd-200d-2642-fe0f.png","sheet_x":29,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F575-1F3FE-200D-2642-FE0F","non_qualified":"1F575-1F3FE-200D-2642","image":"1f575-1f3fe-200d-2642-fe0f.png","sheet_x":29,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F575-1F3FF-200D-2642-FE0F","non_qualified":"1F575-1F3FF-200D-2642","image":"1f575-1f3ff-200d-2642-fe0f.png","sheet_x":29,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F575-FE0F","a":"Male Detective","b":"1F575-FE0F-200D-2642-FE0F","k":[29,5],"o":7},"cool":{"a":"Squared Cool","b":"1F192","j":["words","blue-square"],"k":[0,22]},"clock730":{"a":"Clock Face Seven-Thirty","b":"1F562","j":["time","late","early","schedule"],"k":[28,36]},"flag-mg":{"a":"Madagascar Flag","b":"1F1F2-1F1EC","k":[3,22]},"free":{"a":"Squared Free","b":"1F193","j":["blue-square","words"],"k":[0,23]},"flag-mh":{"a":"Marshall Islands Flag","b":"1F1F2-1F1ED","k":[3,23]},"clock8":{"a":"Clock Face Eight Oclock","b":"1F557","j":["time","late","early","schedule"],"k":[28,25]},"pill":{"a":"Pill","b":"1F48A","j":["health","medicine","doctor","pharmacy","drug"],"k":[24,36]},"female-detective":{"skin_variations":{"1F3FB":{"unified":"1F575-1F3FB-200D-2640-FE0F","non_qualified":"1F575-1F3FB-200D-2640","image":"1f575-1f3fb-200d-2640-fe0f.png","sheet_x":29,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F575-1F3FC-200D-2640-FE0F","non_qualified":"1F575-1F3FC-200D-2640","image":"1f575-1f3fc-200d-2640-fe0f.png","sheet_x":29,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F575-1F3FD-200D-2640-FE0F","non_qualified":"1F575-1F3FD-200D-2640","image":"1f575-1f3fd-200d-2640-fe0f.png","sheet_x":29,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F575-1F3FE-200D-2640-FE0F","non_qualified":"1F575-1F3FE-200D-2640","image":"1f575-1f3fe-200d-2640-fe0f.png","sheet_x":29,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F575-1F3FF-200D-2640-FE0F","non_qualified":"1F575-1F3FF-200D-2640","image":"1f575-1f3ff-200d-2640-fe0f.png","sheet_x":29,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Detective","b":"1F575-FE0F-200D-2640-FE0F","k":[28,51],"o":7},"clock830":{"a":"Clock Face Eight-Thirty","b":"1F563","j":["time","late","early","schedule"],"k":[28,37]},"guardsman":{"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB","non_qualified":null,"image":"1f482-1f3fb.png","sheet_x":23,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F482-1F3FC","non_qualified":null,"image":"1f482-1f3fc.png","sheet_x":23,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F482-1F3FD","non_qualified":null,"image":"1f482-1f3fd.png","sheet_x":23,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F482-1F3FE","non_qualified":null,"image":"1f482-1f3fe.png","sheet_x":23,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F482-1F3FF","non_qualified":null,"image":"1f482-1f3ff.png","sheet_x":23,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F482-200D-2642-FE0F","a":"Guardsman","b":"1F482","j":["uk","gb","british","male","guy","royal"],"k":[23,31]},"information_source":{"a":"Information Source","b":"2139-FE0F","c":"2139","j":["blue-square","alphabet","letter"],"k":[46,32],"o":3},"flag-mk":{"a":"Macedonia Flag","b":"1F1F2-1F1F0","k":[3,24]},"smoking":{"a":"Smoking Symbol","b":"1F6AC","j":["kills","tobacco","cigarette","joint","smoke"],"k":[35,17]},"id":{"a":"Squared Id","b":"1F194","j":["purple-square","words"],"k":[0,24]},"clock9":{"a":"Clock Face Nine Oclock","b":"1F558","j":["time","late","early","schedule"],"k":[28,26]},"flag-ml":{"a":"Mali Flag","b":"1F1F2-1F1F1","k":[3,25]},"coffin":{"a":"Coffin","b":"26B0-FE0F","c":"26B0","j":["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],"k":[48,24],"o":4},"male-guard":{"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB-200D-2642-FE0F","non_qualified":"1F482-1F3FB-200D-2642","image":"1f482-1f3fb-200d-2642-fe0f.png","sheet_x":23,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F482-1F3FC-200D-2642-FE0F","non_qualified":"1F482-1F3FC-200D-2642","image":"1f482-1f3fc-200d-2642-fe0f.png","sheet_x":23,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F482-1F3FD-200D-2642-FE0F","non_qualified":"1F482-1F3FD-200D-2642","image":"1f482-1f3fd-200d-2642-fe0f.png","sheet_x":23,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F482-1F3FE-200D-2642-FE0F","non_qualified":"1F482-1F3FE-200D-2642","image":"1f482-1f3fe-200d-2642-fe0f.png","sheet_x":23,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F482-1F3FF-200D-2642-FE0F","non_qualified":"1F482-1F3FF-200D-2642","image":"1f482-1f3ff-200d-2642-fe0f.png","sheet_x":23,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F482","a":"Male Guard","b":"1F482-200D-2642-FE0F","c":"1F482-200D-2642","k":[23,25]},"m":{"a":"Circled Latin Capital Letter M","b":"24C2-FE0F","c":"24C2","j":["alphabet","blue-circle","letter"],"k":[47,7],"o":1},"funeral_urn":{"a":"Funeral Urn","b":"26B1-FE0F","c":"26B1","j":["dead","die","death","rip","ashes"],"k":[48,25],"o":4},"female-guard":{"skin_variations":{"1F3FB":{"unified":"1F482-1F3FB-200D-2640-FE0F","non_qualified":"1F482-1F3FB-200D-2640","image":"1f482-1f3fb-200d-2640-fe0f.png","sheet_x":23,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F482-1F3FC-200D-2640-FE0F","non_qualified":"1F482-1F3FC-200D-2640","image":"1f482-1f3fc-200d-2640-fe0f.png","sheet_x":23,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F482-1F3FD-200D-2640-FE0F","non_qualified":"1F482-1F3FD-200D-2640","image":"1f482-1f3fd-200d-2640-fe0f.png","sheet_x":23,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F482-1F3FE-200D-2640-FE0F","non_qualified":"1F482-1F3FE-200D-2640","image":"1f482-1f3fe-200d-2640-fe0f.png","sheet_x":23,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F482-1F3FF-200D-2640-FE0F","non_qualified":"1F482-1F3FF-200D-2640","image":"1f482-1f3ff-200d-2640-fe0f.png","sheet_x":23,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Guard","b":"1F482-200D-2640-FE0F","c":"1F482-200D-2640","k":[23,19]},"flag-mm":{"a":"Myanmar (burma) Flag","b":"1F1F2-1F1F2","k":[3,26]},"clock930":{"a":"Clock Face Nine-Thirty","b":"1F564","j":["time","late","early","schedule"],"k":[28,38]},"moyai":{"a":"Moyai","b":"1F5FF","j":["rock","easter island","moai"],"k":[30,23]},"new":{"a":"Squared New","b":"1F195","j":["blue-square","words","start"],"k":[0,25]},"flag-mn":{"a":"Mongolia Flag","b":"1F1F2-1F1F3","k":[3,27]},"construction_worker":{"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB","non_qualified":null,"image":"1f477-1f3fb.png","sheet_x":22,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F477-1F3FC","non_qualified":null,"image":"1f477-1f3fc.png","sheet_x":22,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F477-1F3FD","non_qualified":null,"image":"1f477-1f3fd.png","sheet_x":22,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F477-1F3FE","non_qualified":null,"image":"1f477-1f3fe.png","sheet_x":22,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F477-1F3FF","non_qualified":null,"image":"1f477-1f3ff.png","sheet_x":22,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F477-200D-2642-FE0F","a":"Construction Worker","b":"1F477","k":[22,28]},"clock10":{"a":"Clock Face Ten Oclock","b":"1F559","j":["time","late","early","schedule"],"k":[28,27]},"clock1030":{"a":"Clock Face Ten-Thirty","b":"1F565","j":["time","late","early","schedule"],"k":[28,39]},"ng":{"a":"Squared Ng","b":"1F196","j":["blue-square","words","shape","icon"],"k":[0,26]},"male-construction-worker":{"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB-200D-2642-FE0F","non_qualified":"1F477-1F3FB-200D-2642","image":"1f477-1f3fb-200d-2642-fe0f.png","sheet_x":22,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F477-1F3FC-200D-2642-FE0F","non_qualified":"1F477-1F3FC-200D-2642","image":"1f477-1f3fc-200d-2642-fe0f.png","sheet_x":22,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F477-1F3FD-200D-2642-FE0F","non_qualified":"1F477-1F3FD-200D-2642","image":"1f477-1f3fd-200d-2642-fe0f.png","sheet_x":22,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F477-1F3FE-200D-2642-FE0F","non_qualified":"1F477-1F3FE-200D-2642","image":"1f477-1f3fe-200d-2642-fe0f.png","sheet_x":22,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F477-1F3FF-200D-2642-FE0F","non_qualified":"1F477-1F3FF-200D-2642","image":"1f477-1f3ff-200d-2642-fe0f.png","sheet_x":22,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F477","a":"Male Construction Worker","b":"1F477-200D-2642-FE0F","c":"1F477-200D-2642","k":[22,22]},"flag-mo":{"a":"Macau Sar China Flag","b":"1F1F2-1F1F4","k":[3,28]},"oil_drum":{"a":"Oil Drum","b":"1F6E2-FE0F","c":"1F6E2","j":["barrell"],"k":[37,10],"o":7},"o2":{"a":"Negative Squared Latin Capital Letter O","b":"1F17E-FE0F","c":"1F17E","j":["alphabet","red-square","letter"],"k":[0,18]},"female-construction-worker":{"skin_variations":{"1F3FB":{"unified":"1F477-1F3FB-200D-2640-FE0F","non_qualified":"1F477-1F3FB-200D-2640","image":"1f477-1f3fb-200d-2640-fe0f.png","sheet_x":22,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F477-1F3FC-200D-2640-FE0F","non_qualified":"1F477-1F3FC-200D-2640","image":"1f477-1f3fc-200d-2640-fe0f.png","sheet_x":22,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F477-1F3FD-200D-2640-FE0F","non_qualified":"1F477-1F3FD-200D-2640","image":"1f477-1f3fd-200d-2640-fe0f.png","sheet_x":22,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F477-1F3FE-200D-2640-FE0F","non_qualified":"1F477-1F3FE-200D-2640","image":"1f477-1f3fe-200d-2640-fe0f.png","sheet_x":22,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F477-1F3FF-200D-2640-FE0F","non_qualified":"1F477-1F3FF-200D-2640","image":"1f477-1f3ff-200d-2640-fe0f.png","sheet_x":22,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Construction Worker","b":"1F477-200D-2640-FE0F","c":"1F477-200D-2640","k":[22,16]},"clock11":{"a":"Clock Face Eleven Oclock","b":"1F55A","j":["time","late","early","schedule"],"k":[28,28]},"crystal_ball":{"a":"Crystal Ball","b":"1F52E","j":["disco","party","magic","circus","fortune_teller"],"k":[27,48]},"flag-mp":{"a":"Northern Mariana Islands Flag","b":"1F1F2-1F1F5","k":[3,29]},"flag-mq":{"a":"Martinique Flag","b":"1F1F2-1F1F6","k":[3,30]},"prince":{"skin_variations":{"1F3FB":{"unified":"1F934-1F3FB","non_qualified":null,"image":"1f934-1f3fb.png","sheet_x":39,"sheet_y":29,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F934-1F3FC","non_qualified":null,"image":"1f934-1f3fc.png","sheet_x":39,"sheet_y":30,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F934-1F3FD","non_qualified":null,"image":"1f934-1f3fd.png","sheet_x":39,"sheet_y":31,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F934-1F3FE","non_qualified":null,"image":"1f934-1f3fe.png","sheet_x":39,"sheet_y":32,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F934-1F3FF","non_qualified":null,"image":"1f934-1f3ff.png","sheet_x":39,"sheet_y":33,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Prince","b":"1F934","j":["boy","man","male","crown","royal","king"],"k":[39,28],"o":9},"ok":{"a":"Squared Ok","b":"1F197","j":["good","agree","yes","blue-square"],"k":[0,27]},"clock1130":{"a":"Clock Face Eleven-Thirty","b":"1F566","j":["time","late","early","schedule"],"k":[28,40]},"shopping_trolley":{"a":"Shopping Trolley","b":"1F6D2","k":[37,7],"o":9},"flag-mr":{"a":"Mauritania Flag","b":"1F1F2-1F1F7","k":[3,31]},"princess":{"skin_variations":{"1F3FB":{"unified":"1F478-1F3FB","non_qualified":null,"image":"1f478-1f3fb.png","sheet_x":22,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F478-1F3FC","non_qualified":null,"image":"1f478-1f3fc.png","sheet_x":22,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F478-1F3FD","non_qualified":null,"image":"1f478-1f3fd.png","sheet_x":22,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F478-1F3FE","non_qualified":null,"image":"1f478-1f3fe.png","sheet_x":22,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F478-1F3FF","non_qualified":null,"image":"1f478-1f3ff.png","sheet_x":22,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Princess","b":"1F478","j":["girl","woman","female","blond","crown","royal","queen"],"k":[22,34]},"new_moon":{"a":"New Moon Symbol","b":"1F311","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,9]},"parking":{"a":"Negative Squared Latin Capital Letter P","b":"1F17F-FE0F","c":"1F17F","j":["cars","blue-square","alphabet","letter"],"k":[0,19],"o":5},"sos":{"a":"Squared Sos","b":"1F198","j":["help","red-square","words","emergency","911"],"k":[0,28]},"man_with_turban":{"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB","non_qualified":null,"image":"1f473-1f3fb.png","sheet_x":21,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F473-1F3FC","non_qualified":null,"image":"1f473-1f3fc.png","sheet_x":21,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F473-1F3FD","non_qualified":null,"image":"1f473-1f3fd.png","sheet_x":21,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F473-1F3FE","non_qualified":null,"image":"1f473-1f3fe.png","sheet_x":21,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F473-1F3FF","non_qualified":null,"image":"1f473-1f3ff.png","sheet_x":21,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F473-200D-2642-FE0F","a":"Man with Turban","b":"1F473","j":["male","indian","hinduism","arabs"],"k":[21,44]},"flag-ms":{"a":"Montserrat Flag","b":"1F1F2-1F1F8","k":[3,32]},"waxing_crescent_moon":{"a":"Waxing Crescent Moon Symbol","b":"1F312","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,10]},"up":{"a":"Squared Up with Exclamation Mark","b":"1F199","j":["blue-square","above","high"],"k":[0,29]},"first_quarter_moon":{"a":"First Quarter Moon Symbol","b":"1F313","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,11]},"flag-mt":{"a":"Malta Flag","b":"1F1F2-1F1F9","k":[3,33]},"man-wearing-turban":{"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB-200D-2642-FE0F","non_qualified":"1F473-1F3FB-200D-2642","image":"1f473-1f3fb-200d-2642-fe0f.png","sheet_x":21,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F473-1F3FC-200D-2642-FE0F","non_qualified":"1F473-1F3FC-200D-2642","image":"1f473-1f3fc-200d-2642-fe0f.png","sheet_x":21,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F473-1F3FD-200D-2642-FE0F","non_qualified":"1F473-1F3FD-200D-2642","image":"1f473-1f3fd-200d-2642-fe0f.png","sheet_x":21,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F473-1F3FE-200D-2642-FE0F","non_qualified":"1F473-1F3FE-200D-2642","image":"1f473-1f3fe-200d-2642-fe0f.png","sheet_x":21,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F473-1F3FF-200D-2642-FE0F","non_qualified":"1F473-1F3FF-200D-2642","image":"1f473-1f3ff-200d-2642-fe0f.png","sheet_x":21,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F473","a":"Man Wearing Turban","b":"1F473-200D-2642-FE0F","c":"1F473-200D-2642","k":[21,38]},"moon":{"a":"Waxing Gibbous Moon Symbol","b":"1F314","k":[6,12],"n":["waxing_gibbous_moon"]},"woman-wearing-turban":{"skin_variations":{"1F3FB":{"unified":"1F473-1F3FB-200D-2640-FE0F","non_qualified":"1F473-1F3FB-200D-2640","image":"1f473-1f3fb-200d-2640-fe0f.png","sheet_x":21,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F473-1F3FC-200D-2640-FE0F","non_qualified":"1F473-1F3FC-200D-2640","image":"1f473-1f3fc-200d-2640-fe0f.png","sheet_x":21,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F473-1F3FD-200D-2640-FE0F","non_qualified":"1F473-1F3FD-200D-2640","image":"1f473-1f3fd-200d-2640-fe0f.png","sheet_x":21,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F473-1F3FE-200D-2640-FE0F","non_qualified":"1F473-1F3FE-200D-2640","image":"1f473-1f3fe-200d-2640-fe0f.png","sheet_x":21,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F473-1F3FF-200D-2640-FE0F","non_qualified":"1F473-1F3FF-200D-2640","image":"1f473-1f3ff-200d-2640-fe0f.png","sheet_x":21,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Wearing Turban","b":"1F473-200D-2640-FE0F","c":"1F473-200D-2640","k":[21,32]},"vs":{"a":"Squared Vs","b":"1F19A","j":["words","orange-square"],"k":[0,30]},"flag-mu":{"a":"Mauritius Flag","b":"1F1F2-1F1FA","k":[3,34]},"man_with_gua_pi_mao":{"skin_variations":{"1F3FB":{"unified":"1F472-1F3FB","non_qualified":null,"image":"1f472-1f3fb.png","sheet_x":21,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F472-1F3FC","non_qualified":null,"image":"1f472-1f3fc.png","sheet_x":21,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F472-1F3FD","non_qualified":null,"image":"1f472-1f3fd.png","sheet_x":21,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F472-1F3FE","non_qualified":null,"image":"1f472-1f3fe.png","sheet_x":21,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F472-1F3FF","non_qualified":null,"image":"1f472-1f3ff.png","sheet_x":21,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Man with Gua Pi Mao","b":"1F472","j":["male","boy","chinese"],"k":[21,26]},"koko":{"a":"Squared Katakana Koko","b":"1F201","j":["blue-square","here","katakana","japanese","destination"],"k":[5,29]},"full_moon":{"a":"Full Moon Symbol","b":"1F315","j":["nature","yellow","twilight","planet","space","night","evening","sleep"],"k":[6,13]},"flag-mv":{"a":"Maldives Flag","b":"1F1F2-1F1FB","k":[3,35]},"person_with_headscarf":{"skin_variations":{"1F3FB":{"unified":"1F9D5-1F3FB","non_qualified":null,"image":"1f9d5-1f3fb.png","sheet_x":43,"sheet_y":23,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D5-1F3FC","non_qualified":null,"image":"1f9d5-1f3fc.png","sheet_x":43,"sheet_y":24,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D5-1F3FD","non_qualified":null,"image":"1f9d5-1f3fd.png","sheet_x":43,"sheet_y":25,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D5-1F3FE","non_qualified":null,"image":"1f9d5-1f3fe.png","sheet_x":43,"sheet_y":26,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D5-1F3FF","non_qualified":null,"image":"1f9d5-1f3ff.png","sheet_x":43,"sheet_y":27,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Person with Headscarf","b":"1F9D5","k":[43,22],"o":10},"waning_gibbous_moon":{"a":"Waning Gibbous Moon Symbol","b":"1F316","j":["nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"],"k":[6,14]},"sa":{"a":"Squared Katakana Sa","b":"1F202-FE0F","c":"1F202","j":["japanese","blue-square","katakana"],"k":[5,30]},"flag-mw":{"a":"Malawi Flag","b":"1F1F2-1F1FC","k":[3,36]},"last_quarter_moon":{"a":"Last Quarter Moon Symbol","b":"1F317","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,15]},"u6708":{"a":"Squared Cjk Unified Ideograph-6708","b":"1F237-FE0F","c":"1F237","j":["chinese","month","moon","japanese","orange-square","kanji"],"k":[5,38]},"bearded_person":{"skin_variations":{"1F3FB":{"unified":"1F9D4-1F3FB","non_qualified":null,"image":"1f9d4-1f3fb.png","sheet_x":43,"sheet_y":17,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F9D4-1F3FC","non_qualified":null,"image":"1f9d4-1f3fc.png","sheet_x":43,"sheet_y":18,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F9D4-1F3FD","non_qualified":null,"image":"1f9d4-1f3fd.png","sheet_x":43,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F9D4-1F3FE","non_qualified":null,"image":"1f9d4-1f3fe.png","sheet_x":43,"sheet_y":20,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F9D4-1F3FF","non_qualified":null,"image":"1f9d4-1f3ff.png","sheet_x":43,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Bearded Person","b":"1F9D4","k":[43,16],"o":10},"flag-mx":{"a":"Mexico Flag","b":"1F1F2-1F1FD","k":[3,37]},"u6709":{"a":"Squared Cjk Unified Ideograph-6709","b":"1F236","j":["orange-square","chinese","have","kanji"],"k":[5,37]},"person_with_blond_hair":{"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB","non_qualified":null,"image":"1f471-1f3fb.png","sheet_x":21,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F471-1F3FC","non_qualified":null,"image":"1f471-1f3fc.png","sheet_x":21,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F471-1F3FD","non_qualified":null,"image":"1f471-1f3fd.png","sheet_x":21,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F471-1F3FE","non_qualified":null,"image":"1f471-1f3fe.png","sheet_x":21,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F471-1F3FF","non_qualified":null,"image":"1f471-1f3ff.png","sheet_x":21,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F471-200D-2642-FE0F","a":"Person with Blond Hair","b":"1F471","k":[21,20]},"waning_crescent_moon":{"a":"Waning Crescent Moon Symbol","b":"1F318","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,16]},"flag-my":{"a":"Malaysia Flag","b":"1F1F2-1F1FE","k":[3,38]},"u6307":{"a":"Squared Cjk Unified Ideograph-6307","b":"1F22F","j":["chinese","point","green-square","kanji"],"k":[5,32],"o":5},"blond-haired-man":{"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB-200D-2642-FE0F","non_qualified":"1F471-1F3FB-200D-2642","image":"1f471-1f3fb-200d-2642-fe0f.png","sheet_x":21,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F471-1F3FC-200D-2642-FE0F","non_qualified":"1F471-1F3FC-200D-2642","image":"1f471-1f3fc-200d-2642-fe0f.png","sheet_x":21,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F471-1F3FD-200D-2642-FE0F","non_qualified":"1F471-1F3FD-200D-2642","image":"1f471-1f3fd-200d-2642-fe0f.png","sheet_x":21,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F471-1F3FE-200D-2642-FE0F","non_qualified":"1F471-1F3FE-200D-2642","image":"1f471-1f3fe-200d-2642-fe0f.png","sheet_x":21,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F471-1F3FF-200D-2642-FE0F","non_qualified":"1F471-1F3FF-200D-2642","image":"1f471-1f3ff-200d-2642-fe0f.png","sheet_x":21,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F471","a":"Blond Haired Man","b":"1F471-200D-2642-FE0F","c":"1F471-200D-2642","k":[21,14]},"crescent_moon":{"a":"Crescent Moon","b":"1F319","j":["night","sleep","sky","evening","magic"],"k":[6,17]},"flag-mz":{"a":"Mozambique Flag","b":"1F1F2-1F1FF","k":[3,39]},"new_moon_with_face":{"a":"New Moon with Face","b":"1F31A","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,18]},"flag-na":{"a":"Namibia Flag","b":"1F1F3-1F1E6","k":[3,40]},"blond-haired-woman":{"skin_variations":{"1F3FB":{"unified":"1F471-1F3FB-200D-2640-FE0F","non_qualified":"1F471-1F3FB-200D-2640","image":"1f471-1f3fb-200d-2640-fe0f.png","sheet_x":21,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F471-1F3FC-200D-2640-FE0F","non_qualified":"1F471-1F3FC-200D-2640","image":"1f471-1f3fc-200d-2640-fe0f.png","sheet_x":21,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F471-1F3FD-200D-2640-FE0F","non_qualified":"1F471-1F3FD-200D-2640","image":"1f471-1f3fd-200d-2640-fe0f.png","sheet_x":21,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F471-1F3FE-200D-2640-FE0F","non_qualified":"1F471-1F3FE-200D-2640","image":"1f471-1f3fe-200d-2640-fe0f.png","sheet_x":21,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F471-1F3FF-200D-2640-FE0F","non_qualified":"1F471-1F3FF-200D-2640","image":"1f471-1f3ff-200d-2640-fe0f.png","sheet_x":21,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Blond Haired Woman","b":"1F471-200D-2640-FE0F","c":"1F471-200D-2640","k":[21,8]},"ideograph_advantage":{"a":"Circled Ideograph Advantage","b":"1F250","j":["chinese","kanji","obtain","get","circle"],"k":[5,42]},"first_quarter_moon_with_face":{"a":"First Quarter Moon with Face","b":"1F31B","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,19]},"man_in_tuxedo":{"skin_variations":{"1F3FB":{"unified":"1F935-1F3FB","non_qualified":null,"image":"1f935-1f3fb.png","sheet_x":39,"sheet_y":35,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F935-1F3FC","non_qualified":null,"image":"1f935-1f3fc.png","sheet_x":39,"sheet_y":36,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F935-1F3FD","non_qualified":null,"image":"1f935-1f3fd.png","sheet_x":39,"sheet_y":37,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F935-1F3FE","non_qualified":null,"image":"1f935-1f3fe.png","sheet_x":39,"sheet_y":38,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F935-1F3FF","non_qualified":null,"image":"1f935-1f3ff.png","sheet_x":39,"sheet_y":39,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Man in Tuxedo","b":"1F935","j":["couple","marriage","wedding","groom"],"k":[39,34],"o":9},"flag-nc":{"a":"New Caledonia Flag","b":"1F1F3-1F1E8","k":[3,41]},"u5272":{"a":"Squared Cjk Unified Ideograph-5272","b":"1F239","j":["cut","divide","chinese","kanji","pink-square"],"k":[5,40]},"flag-ne":{"a":"Niger Flag","b":"1F1F3-1F1EA","k":[3,42]},"last_quarter_moon_with_face":{"a":"Last Quarter Moon with Face","b":"1F31C","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,20]},"u7121":{"a":"Squared Cjk Unified Ideograph-7121","b":"1F21A","j":["nothing","chinese","kanji","japanese","orange-square"],"k":[5,31],"o":5},"bride_with_veil":{"skin_variations":{"1F3FB":{"unified":"1F470-1F3FB","non_qualified":null,"image":"1f470-1f3fb.png","sheet_x":21,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F470-1F3FC","non_qualified":null,"image":"1f470-1f3fc.png","sheet_x":21,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F470-1F3FD","non_qualified":null,"image":"1f470-1f3fd.png","sheet_x":21,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F470-1F3FE","non_qualified":null,"image":"1f470-1f3fe.png","sheet_x":21,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F470-1F3FF","non_qualified":null,"image":"1f470-1f3ff.png","sheet_x":21,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Bride with Veil","b":"1F470","j":["couple","marriage","wedding","woman","bride"],"k":[21,2]},"u7981":{"a":"Squared Cjk Unified Ideograph-7981","b":"1F232","j":["kanji","japanese","chinese","forbidden","limit","restricted","red-square"],"k":[5,33]},"pregnant_woman":{"skin_variations":{"1F3FB":{"unified":"1F930-1F3FB","non_qualified":null,"image":"1f930-1f3fb.png","sheet_x":39,"sheet_y":5,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F930-1F3FC","non_qualified":null,"image":"1f930-1f3fc.png","sheet_x":39,"sheet_y":6,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F930-1F3FD","non_qualified":null,"image":"1f930-1f3fd.png","sheet_x":39,"sheet_y":7,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F930-1F3FE","non_qualified":null,"image":"1f930-1f3fe.png","sheet_x":39,"sheet_y":8,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F930-1F3FF","non_qualified":null,"image":"1f930-1f3ff.png","sheet_x":39,"sheet_y":9,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Pregnant Woman","b":"1F930","j":["baby"],"k":[39,4],"o":9},"thermometer":{"a":"Thermometer","b":"1F321-FE0F","c":"1F321","j":["weather","temperature","hot","cold"],"k":[6,25],"o":7},"flag-nf":{"a":"Norfolk Island Flag","b":"1F1F3-1F1EB","k":[3,43]},"sunny":{"a":"Black Sun with Rays","b":"2600-FE0F","c":"2600","j":["weather","nature","brightness","summer","beach","spring"],"k":[47,16],"o":1},"accept":{"a":"Circled Ideograph Accept","b":"1F251","j":["ok","good","chinese","kanji","agree","yes","orange-circle"],"k":[5,43]},"flag-ng":{"a":"Nigeria Flag","b":"1F1F3-1F1EC","k":[3,44]},"breast-feeding":{"skin_variations":{"1F3FB":{"unified":"1F931-1F3FB","non_qualified":null,"image":"1f931-1f3fb.png","sheet_x":39,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F931-1F3FC","non_qualified":null,"image":"1f931-1f3fc.png","sheet_x":39,"sheet_y":12,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F931-1F3FD","non_qualified":null,"image":"1f931-1f3fd.png","sheet_x":39,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F931-1F3FE","non_qualified":null,"image":"1f931-1f3fe.png","sheet_x":39,"sheet_y":14,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F931-1F3FF","non_qualified":null,"image":"1f931-1f3ff.png","sheet_x":39,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Breast-Feeding","b":"1F931","k":[39,10],"o":10},"full_moon_with_face":{"a":"Full Moon with Face","b":"1F31D","j":["nature","twilight","planet","space","night","evening","sleep"],"k":[6,21]},"flag-ni":{"a":"Nicaragua Flag","b":"1F1F3-1F1EE","k":[3,45]},"u7533":{"a":"Squared Cjk Unified Ideograph-7533","b":"1F238","j":["chinese","japanese","kanji","orange-square"],"k":[5,39]},"angel":{"skin_variations":{"1F3FB":{"unified":"1F47C-1F3FB","non_qualified":null,"image":"1f47c-1f3fb.png","sheet_x":22,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F47C-1F3FC","non_qualified":null,"image":"1f47c-1f3fc.png","sheet_x":22,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F47C-1F3FD","non_qualified":null,"image":"1f47c-1f3fd.png","sheet_x":22,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F47C-1F3FE","non_qualified":null,"image":"1f47c-1f3fe.png","sheet_x":22,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F47C-1F3FF","non_qualified":null,"image":"1f47c-1f3ff.png","sheet_x":22,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Baby Angel","b":"1F47C","j":["heaven","wings","halo"],"k":[22,43]},"sun_with_face":{"a":"Sun with Face","b":"1F31E","j":["nature","morning","sky"],"k":[6,22]},"santa":{"skin_variations":{"1F3FB":{"unified":"1F385-1F3FB","non_qualified":null,"image":"1f385-1f3fb.png","sheet_x":8,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F385-1F3FC","non_qualified":null,"image":"1f385-1f3fc.png","sheet_x":8,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F385-1F3FD","non_qualified":null,"image":"1f385-1f3fd.png","sheet_x":8,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F385-1F3FE","non_qualified":null,"image":"1f385-1f3fe.png","sheet_x":8,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F385-1F3FF","non_qualified":null,"image":"1f385-1f3ff.png","sheet_x":8,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Father Christmas","b":"1F385","j":["festival","man","male","xmas","father christmas"],"k":[8,19]},"u5408":{"a":"Squared Cjk Unified Ideograph-5408","b":"1F234","j":["japanese","chinese","join","kanji","red-square"],"k":[5,35]},"flag-nl":{"a":"Netherlands Flag","b":"1F1F3-1F1F1","k":[3,46]},"mrs_claus":{"skin_variations":{"1F3FB":{"unified":"1F936-1F3FB","non_qualified":null,"image":"1f936-1f3fb.png","sheet_x":39,"sheet_y":41,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F936-1F3FC","non_qualified":null,"image":"1f936-1f3fc.png","sheet_x":39,"sheet_y":42,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F936-1F3FD","non_qualified":null,"image":"1f936-1f3fd.png","sheet_x":39,"sheet_y":43,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F936-1F3FE","non_qualified":null,"image":"1f936-1f3fe.png","sheet_x":39,"sheet_y":44,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F936-1F3FF","non_qualified":null,"image":"1f936-1f3ff.png","sheet_x":39,"sheet_y":45,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Mother Christmas","b":"1F936","j":["woman","female","xmas","mother christmas"],"k":[39,40],"n":["mother_christmas"],"o":9},"u7a7a":{"a":"Squared Cjk Unified Ideograph-7a7a","b":"1F233","j":["kanji","japanese","chinese","empty","sky","blue-square"],"k":[5,34]},"star":{"a":"White Medium Star","b":"2B50","j":["night","yellow"],"k":[50,22],"o":5},"flag-no":{"a":"Norway Flag","b":"1F1F3-1F1F4","k":[3,47]},"mage":{"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB","non_qualified":null,"image":"1f9d9-1f3fb.png","sheet_x":44,"sheet_y":43,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9D9-1F3FC","non_qualified":null,"image":"1f9d9-1f3fc.png","sheet_x":44,"sheet_y":44,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9D9-1F3FD","non_qualified":null,"image":"1f9d9-1f3fd.png","sheet_x":44,"sheet_y":45,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9D9-1F3FE","non_qualified":null,"image":"1f9d9-1f3fe.png","sheet_x":44,"sheet_y":46,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9D9-1F3FF","non_qualified":null,"image":"1f9d9-1f3ff.png","sheet_x":44,"sheet_y":47,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D9-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9D9-200D-2640-FE0F","a":"Mage","b":"1F9D9","k":[44,42],"o":10},"star2":{"a":"Glowing Star","b":"1F31F","j":["night","sparkle","awesome","good","magic"],"k":[6,23]},"flag-np":{"a":"Nepal Flag","b":"1F1F3-1F1F5","k":[3,48]},"congratulations":{"a":"Circled Ideograph Congratulation","b":"3297-FE0F","c":"3297","j":["chinese","kanji","japanese","red-circle"],"k":[50,26],"o":1},"flag-nr":{"a":"Nauru Flag","b":"1F1F3-1F1F7","k":[3,49]},"stars":{"a":"Shooting Star","b":"1F320","j":["night","photo"],"k":[6,24]},"female_mage":{"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB-200D-2640-FE0F","non_qualified":"1F9D9-1F3FB-200D-2640","image":"1f9d9-1f3fb-200d-2640-fe0f.png","sheet_x":44,"sheet_y":31,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FB"},"1F3FC":{"unified":"1F9D9-1F3FC-200D-2640-FE0F","non_qualified":"1F9D9-1F3FC-200D-2640","image":"1f9d9-1f3fc-200d-2640-fe0f.png","sheet_x":44,"sheet_y":32,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FC"},"1F3FD":{"unified":"1F9D9-1F3FD-200D-2640-FE0F","non_qualified":"1F9D9-1F3FD-200D-2640","image":"1f9d9-1f3fd-200d-2640-fe0f.png","sheet_x":44,"sheet_y":33,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FD"},"1F3FE":{"unified":"1F9D9-1F3FE-200D-2640-FE0F","non_qualified":"1F9D9-1F3FE-200D-2640","image":"1f9d9-1f3fe-200d-2640-fe0f.png","sheet_x":44,"sheet_y":34,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FE"},"1F3FF":{"unified":"1F9D9-1F3FF-200D-2640-FE0F","non_qualified":"1F9D9-1F3FF-200D-2640","image":"1f9d9-1f3ff-200d-2640-fe0f.png","sheet_x":44,"sheet_y":35,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D9-1F3FF"}},"obsoletes":"1F9D9","a":"Female Mage","b":"1F9D9-200D-2640-FE0F","c":"1F9D9-200D-2640","k":[44,30],"o":10},"secret":{"a":"Circled Ideograph Secret","b":"3299-FE0F","c":"3299","j":["privacy","chinese","sshh","kanji","red-circle"],"k":[50,27],"o":1},"flag-nu":{"a":"Niue Flag","b":"1F1F3-1F1FA","k":[3,50]},"u55b6":{"a":"Squared Cjk Unified Ideograph-55b6","b":"1F23A","j":["japanese","opening hours","orange-square"],"k":[5,41]},"male_mage":{"skin_variations":{"1F3FB":{"unified":"1F9D9-1F3FB-200D-2642-FE0F","non_qualified":"1F9D9-1F3FB-200D-2642","image":"1f9d9-1f3fb-200d-2642-fe0f.png","sheet_x":44,"sheet_y":37,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9D9-1F3FC-200D-2642-FE0F","non_qualified":"1F9D9-1F3FC-200D-2642","image":"1f9d9-1f3fc-200d-2642-fe0f.png","sheet_x":44,"sheet_y":38,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9D9-1F3FD-200D-2642-FE0F","non_qualified":"1F9D9-1F3FD-200D-2642","image":"1f9d9-1f3fd-200d-2642-fe0f.png","sheet_x":44,"sheet_y":39,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9D9-1F3FE-200D-2642-FE0F","non_qualified":"1F9D9-1F3FE-200D-2642","image":"1f9d9-1f3fe-200d-2642-fe0f.png","sheet_x":44,"sheet_y":40,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9D9-1F3FF-200D-2642-FE0F","non_qualified":"1F9D9-1F3FF-200D-2642","image":"1f9d9-1f3ff-200d-2642-fe0f.png","sheet_x":44,"sheet_y":41,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Male Mage","b":"1F9D9-200D-2642-FE0F","c":"1F9D9-200D-2642","k":[44,36],"o":10},"cloud":{"a":"Cloud","b":"2601-FE0F","c":"2601","j":["weather","sky"],"k":[47,17],"o":1},"flag-nz":{"a":"New Zealand Flag","b":"1F1F3-1F1FF","k":[3,51]},"partly_sunny":{"a":"Sun Behind Cloud","b":"26C5","j":["weather","nature","cloudy","morning","fall","spring"],"k":[48,29],"o":5},"fairy":{"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB","non_qualified":null,"image":"1f9da-1f3fb.png","sheet_x":45,"sheet_y":9,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9DA-1F3FC","non_qualified":null,"image":"1f9da-1f3fc.png","sheet_x":45,"sheet_y":10,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9DA-1F3FD","non_qualified":null,"image":"1f9da-1f3fd.png","sheet_x":45,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9DA-1F3FE","non_qualified":null,"image":"1f9da-1f3fe.png","sheet_x":45,"sheet_y":12,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9DA-1F3FF","non_qualified":null,"image":"1f9da-1f3ff.png","sheet_x":45,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DA-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9DA-200D-2640-FE0F","a":"Fairy","b":"1F9DA","k":[45,8],"o":10},"u6e80":{"a":"Squared Cjk Unified Ideograph-6e80","b":"1F235","j":["full","chinese","japanese","red-square","kanji"],"k":[5,36]},"black_small_square":{"a":"Black Small Square","b":"25AA-FE0F","c":"25AA","j":["shape","icon"],"k":[47,8],"o":1},"thunder_cloud_and_rain":{"a":"Thunder Cloud and Rain","b":"26C8-FE0F","c":"26C8","k":[48,30],"o":5},"female_fairy":{"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB-200D-2640-FE0F","non_qualified":"1F9DA-1F3FB-200D-2640","image":"1f9da-1f3fb-200d-2640-fe0f.png","sheet_x":44,"sheet_y":49,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FB"},"1F3FC":{"unified":"1F9DA-1F3FC-200D-2640-FE0F","non_qualified":"1F9DA-1F3FC-200D-2640","image":"1f9da-1f3fc-200d-2640-fe0f.png","sheet_x":44,"sheet_y":50,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FC"},"1F3FD":{"unified":"1F9DA-1F3FD-200D-2640-FE0F","non_qualified":"1F9DA-1F3FD-200D-2640","image":"1f9da-1f3fd-200d-2640-fe0f.png","sheet_x":44,"sheet_y":51,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FD"},"1F3FE":{"unified":"1F9DA-1F3FE-200D-2640-FE0F","non_qualified":"1F9DA-1F3FE-200D-2640","image":"1f9da-1f3fe-200d-2640-fe0f.png","sheet_x":45,"sheet_y":0,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FE"},"1F3FF":{"unified":"1F9DA-1F3FF-200D-2640-FE0F","non_qualified":"1F9DA-1F3FF-200D-2640","image":"1f9da-1f3ff-200d-2640-fe0f.png","sheet_x":45,"sheet_y":1,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DA-1F3FF"}},"obsoletes":"1F9DA","a":"Female Fairy","b":"1F9DA-200D-2640-FE0F","c":"1F9DA-200D-2640","k":[44,48],"o":10},"flag-om":{"a":"Oman Flag","b":"1F1F4-1F1F2","k":[4,0]},"white_small_square":{"a":"White Small Square","b":"25AB-FE0F","c":"25AB","j":["shape","icon"],"k":[47,9],"o":1},"flag-pa":{"a":"Panama Flag","b":"1F1F5-1F1E6","k":[4,1]},"mostly_sunny":{"a":"Mostly Sunny","b":"1F324-FE0F","c":"1F324","k":[6,26],"n":["sun_small_cloud"],"o":7},"male_fairy":{"skin_variations":{"1F3FB":{"unified":"1F9DA-1F3FB-200D-2642-FE0F","non_qualified":"1F9DA-1F3FB-200D-2642","image":"1f9da-1f3fb-200d-2642-fe0f.png","sheet_x":45,"sheet_y":3,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9DA-1F3FC-200D-2642-FE0F","non_qualified":"1F9DA-1F3FC-200D-2642","image":"1f9da-1f3fc-200d-2642-fe0f.png","sheet_x":45,"sheet_y":4,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9DA-1F3FD-200D-2642-FE0F","non_qualified":"1F9DA-1F3FD-200D-2642","image":"1f9da-1f3fd-200d-2642-fe0f.png","sheet_x":45,"sheet_y":5,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9DA-1F3FE-200D-2642-FE0F","non_qualified":"1F9DA-1F3FE-200D-2642","image":"1f9da-1f3fe-200d-2642-fe0f.png","sheet_x":45,"sheet_y":6,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9DA-1F3FF-200D-2642-FE0F","non_qualified":"1F9DA-1F3FF-200D-2642","image":"1f9da-1f3ff-200d-2642-fe0f.png","sheet_x":45,"sheet_y":7,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Male Fairy","b":"1F9DA-200D-2642-FE0F","c":"1F9DA-200D-2642","k":[45,2],"o":10},"barely_sunny":{"a":"Barely Sunny","b":"1F325-FE0F","c":"1F325","k":[6,27],"n":["sun_behind_cloud"],"o":7},"white_medium_square":{"a":"White Medium Square","b":"25FB-FE0F","c":"25FB","j":["shape","stone","icon"],"k":[47,12],"o":3},"flag-pe":{"a":"Peru Flag","b":"1F1F5-1F1EA","k":[4,2]},"vampire":{"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB","non_qualified":null,"image":"1f9db-1f3fb.png","sheet_x":45,"sheet_y":27,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9DB-1F3FC","non_qualified":null,"image":"1f9db-1f3fc.png","sheet_x":45,"sheet_y":28,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9DB-1F3FD","non_qualified":null,"image":"1f9db-1f3fd.png","sheet_x":45,"sheet_y":29,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9DB-1F3FE","non_qualified":null,"image":"1f9db-1f3fe.png","sheet_x":45,"sheet_y":30,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9DB-1F3FF","non_qualified":null,"image":"1f9db-1f3ff.png","sheet_x":45,"sheet_y":31,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoleted_by":"1F9DB-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9DB-200D-2640-FE0F","a":"Vampire","b":"1F9DB","k":[45,26],"o":10},"female_vampire":{"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB-200D-2640-FE0F","non_qualified":"1F9DB-1F3FB-200D-2640","image":"1f9db-1f3fb-200d-2640-fe0f.png","sheet_x":45,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FB"},"1F3FC":{"unified":"1F9DB-1F3FC-200D-2640-FE0F","non_qualified":"1F9DB-1F3FC-200D-2640","image":"1f9db-1f3fc-200d-2640-fe0f.png","sheet_x":45,"sheet_y":16,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FC"},"1F3FD":{"unified":"1F9DB-1F3FD-200D-2640-FE0F","non_qualified":"1F9DB-1F3FD-200D-2640","image":"1f9db-1f3fd-200d-2640-fe0f.png","sheet_x":45,"sheet_y":17,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FD"},"1F3FE":{"unified":"1F9DB-1F3FE-200D-2640-FE0F","non_qualified":"1F9DB-1F3FE-200D-2640","image":"1f9db-1f3fe-200d-2640-fe0f.png","sheet_x":45,"sheet_y":18,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FE"},"1F3FF":{"unified":"1F9DB-1F3FF-200D-2640-FE0F","non_qualified":"1F9DB-1F3FF-200D-2640","image":"1f9db-1f3ff-200d-2640-fe0f.png","sheet_x":45,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DB-1F3FF"}},"obsoletes":"1F9DB","a":"Female Vampire","b":"1F9DB-200D-2640-FE0F","c":"1F9DB-200D-2640","k":[45,14],"o":10},"partly_sunny_rain":{"a":"Partly Sunny Rain","b":"1F326-FE0F","c":"1F326","k":[6,28],"n":["sun_behind_rain_cloud"],"o":7},"flag-pf":{"a":"French Polynesia Flag","b":"1F1F5-1F1EB","k":[4,3]},"black_medium_square":{"a":"Black Medium Square","b":"25FC-FE0F","c":"25FC","j":["shape","button","icon"],"k":[47,13],"o":3},"white_medium_small_square":{"a":"White Medium Small Square","b":"25FD","j":["shape","stone","icon","button"],"k":[47,14],"o":3},"rain_cloud":{"a":"Rain Cloud","b":"1F327-FE0F","c":"1F327","k":[6,29],"o":7},"flag-pg":{"a":"Papua New Guinea Flag","b":"1F1F5-1F1EC","k":[4,4]},"male_vampire":{"skin_variations":{"1F3FB":{"unified":"1F9DB-1F3FB-200D-2642-FE0F","non_qualified":"1F9DB-1F3FB-200D-2642","image":"1f9db-1f3fb-200d-2642-fe0f.png","sheet_x":45,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9DB-1F3FC-200D-2642-FE0F","non_qualified":"1F9DB-1F3FC-200D-2642","image":"1f9db-1f3fc-200d-2642-fe0f.png","sheet_x":45,"sheet_y":22,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9DB-1F3FD-200D-2642-FE0F","non_qualified":"1F9DB-1F3FD-200D-2642","image":"1f9db-1f3fd-200d-2642-fe0f.png","sheet_x":45,"sheet_y":23,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9DB-1F3FE-200D-2642-FE0F","non_qualified":"1F9DB-1F3FE-200D-2642","image":"1f9db-1f3fe-200d-2642-fe0f.png","sheet_x":45,"sheet_y":24,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9DB-1F3FF-200D-2642-FE0F","non_qualified":"1F9DB-1F3FF-200D-2642","image":"1f9db-1f3ff-200d-2642-fe0f.png","sheet_x":45,"sheet_y":25,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Male Vampire","b":"1F9DB-200D-2642-FE0F","c":"1F9DB-200D-2642","k":[45,20],"o":10},"flag-ph":{"a":"Philippines Flag","b":"1F1F5-1F1ED","k":[4,5]},"merperson":{"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB","non_qualified":null,"image":"1f9dc-1f3fb.png","sheet_x":45,"sheet_y":45,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FB-200D-2642-FE0F"},"1F3FC":{"unified":"1F9DC-1F3FC","non_qualified":null,"image":"1f9dc-1f3fc.png","sheet_x":45,"sheet_y":46,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FC-200D-2642-FE0F"},"1F3FD":{"unified":"1F9DC-1F3FD","non_qualified":null,"image":"1f9dc-1f3fd.png","sheet_x":45,"sheet_y":47,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FD-200D-2642-FE0F"},"1F3FE":{"unified":"1F9DC-1F3FE","non_qualified":null,"image":"1f9dc-1f3fe.png","sheet_x":45,"sheet_y":48,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FE-200D-2642-FE0F"},"1F3FF":{"unified":"1F9DC-1F3FF","non_qualified":null,"image":"1f9dc-1f3ff.png","sheet_x":45,"sheet_y":49,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DC-1F3FF-200D-2642-FE0F"}},"obsoleted_by":"1F9DC-200D-2642-FE0F","a":"Merperson","b":"1F9DC","k":[45,44],"o":10},"black_medium_small_square":{"a":"Black Medium Small Square","b":"25FE","j":["icon","shape","button"],"k":[47,15],"o":3},"snow_cloud":{"a":"Snow Cloud","b":"1F328-FE0F","c":"1F328","k":[6,30],"o":7},"lightning":{"a":"Lightning","b":"1F329-FE0F","c":"1F329","k":[6,31],"n":["lightning_cloud"],"o":7},"black_large_square":{"a":"Black Large Square","b":"2B1B","j":["shape","icon","button"],"k":[50,20],"o":5},"mermaid":{"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB-200D-2640-FE0F","non_qualified":"1F9DC-1F3FB-200D-2640","image":"1f9dc-1f3fb-200d-2640-fe0f.png","sheet_x":45,"sheet_y":33,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9DC-1F3FC-200D-2640-FE0F","non_qualified":"1F9DC-1F3FC-200D-2640","image":"1f9dc-1f3fc-200d-2640-fe0f.png","sheet_x":45,"sheet_y":34,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9DC-1F3FD-200D-2640-FE0F","non_qualified":"1F9DC-1F3FD-200D-2640","image":"1f9dc-1f3fd-200d-2640-fe0f.png","sheet_x":45,"sheet_y":35,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9DC-1F3FE-200D-2640-FE0F","non_qualified":"1F9DC-1F3FE-200D-2640","image":"1f9dc-1f3fe-200d-2640-fe0f.png","sheet_x":45,"sheet_y":36,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9DC-1F3FF-200D-2640-FE0F","non_qualified":"1F9DC-1F3FF-200D-2640","image":"1f9dc-1f3ff-200d-2640-fe0f.png","sheet_x":45,"sheet_y":37,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Mermaid","b":"1F9DC-200D-2640-FE0F","c":"1F9DC-200D-2640","k":[45,32],"o":10},"flag-pk":{"a":"Pakistan Flag","b":"1F1F5-1F1F0","k":[4,6]},"merman":{"skin_variations":{"1F3FB":{"unified":"1F9DC-1F3FB-200D-2642-FE0F","non_qualified":"1F9DC-1F3FB-200D-2642","image":"1f9dc-1f3fb-200d-2642-fe0f.png","sheet_x":45,"sheet_y":39,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FB"},"1F3FC":{"unified":"1F9DC-1F3FC-200D-2642-FE0F","non_qualified":"1F9DC-1F3FC-200D-2642","image":"1f9dc-1f3fc-200d-2642-fe0f.png","sheet_x":45,"sheet_y":40,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FC"},"1F3FD":{"unified":"1F9DC-1F3FD-200D-2642-FE0F","non_qualified":"1F9DC-1F3FD-200D-2642","image":"1f9dc-1f3fd-200d-2642-fe0f.png","sheet_x":45,"sheet_y":41,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FD"},"1F3FE":{"unified":"1F9DC-1F3FE-200D-2642-FE0F","non_qualified":"1F9DC-1F3FE-200D-2642","image":"1f9dc-1f3fe-200d-2642-fe0f.png","sheet_x":45,"sheet_y":42,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FE"},"1F3FF":{"unified":"1F9DC-1F3FF-200D-2642-FE0F","non_qualified":"1F9DC-1F3FF-200D-2642","image":"1f9dc-1f3ff-200d-2642-fe0f.png","sheet_x":45,"sheet_y":43,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DC-1F3FF"}},"obsoletes":"1F9DC","a":"Merman","b":"1F9DC-200D-2642-FE0F","c":"1F9DC-200D-2642","k":[45,38],"o":10},"white_large_square":{"a":"White Large Square","b":"2B1C","j":["shape","icon","stone","button"],"k":[50,21],"o":5},"tornado":{"a":"Tornado","b":"1F32A-FE0F","c":"1F32A","j":["weather","cyclone","twister"],"k":[6,32],"n":["tornado_cloud"],"o":7},"flag-pl":{"a":"Poland Flag","b":"1F1F5-1F1F1","k":[4,7]},"elf":{"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB","non_qualified":null,"image":"1f9dd-1f3fb.png","sheet_x":46,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FB-200D-2642-FE0F"},"1F3FC":{"unified":"1F9DD-1F3FC","non_qualified":null,"image":"1f9dd-1f3fc.png","sheet_x":46,"sheet_y":12,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FC-200D-2642-FE0F"},"1F3FD":{"unified":"1F9DD-1F3FD","non_qualified":null,"image":"1f9dd-1f3fd.png","sheet_x":46,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FD-200D-2642-FE0F"},"1F3FE":{"unified":"1F9DD-1F3FE","non_qualified":null,"image":"1f9dd-1f3fe.png","sheet_x":46,"sheet_y":14,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FE-200D-2642-FE0F"},"1F3FF":{"unified":"1F9DD-1F3FF","non_qualified":null,"image":"1f9dd-1f3ff.png","sheet_x":46,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9DD-1F3FF-200D-2642-FE0F"}},"obsoleted_by":"1F9DD-200D-2642-FE0F","a":"Elf","b":"1F9DD","k":[46,10],"o":10},"fog":{"a":"Fog","b":"1F32B-FE0F","c":"1F32B","j":["weather"],"k":[6,33],"o":7},"large_orange_diamond":{"a":"Large Orange Diamond","b":"1F536","j":["shape","jewel","gem"],"k":[28,4]},"flag-pm":{"a":"St. Pierre & Miquelon Flag","b":"1F1F5-1F1F2","k":[4,8]},"flag-pn":{"a":"Pitcairn Islands Flag","b":"1F1F5-1F1F3","k":[4,9]},"wind_blowing_face":{"a":"Wind Blowing Face","b":"1F32C-FE0F","c":"1F32C","k":[6,34],"o":7},"female_elf":{"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB-200D-2640-FE0F","non_qualified":"1F9DD-1F3FB-200D-2640","image":"1f9dd-1f3fb-200d-2640-fe0f.png","sheet_x":45,"sheet_y":51,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9DD-1F3FC-200D-2640-FE0F","non_qualified":"1F9DD-1F3FC-200D-2640","image":"1f9dd-1f3fc-200d-2640-fe0f.png","sheet_x":46,"sheet_y":0,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9DD-1F3FD-200D-2640-FE0F","non_qualified":"1F9DD-1F3FD-200D-2640","image":"1f9dd-1f3fd-200d-2640-fe0f.png","sheet_x":46,"sheet_y":1,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9DD-1F3FE-200D-2640-FE0F","non_qualified":"1F9DD-1F3FE-200D-2640","image":"1f9dd-1f3fe-200d-2640-fe0f.png","sheet_x":46,"sheet_y":2,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9DD-1F3FF-200D-2640-FE0F","non_qualified":"1F9DD-1F3FF-200D-2640","image":"1f9dd-1f3ff-200d-2640-fe0f.png","sheet_x":46,"sheet_y":3,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Female Elf","b":"1F9DD-200D-2640-FE0F","c":"1F9DD-200D-2640","k":[45,50],"o":10},"large_blue_diamond":{"a":"Large Blue Diamond","b":"1F537","j":["shape","jewel","gem"],"k":[28,5]},"male_elf":{"skin_variations":{"1F3FB":{"unified":"1F9DD-1F3FB-200D-2642-FE0F","non_qualified":"1F9DD-1F3FB-200D-2642","image":"1f9dd-1f3fb-200d-2642-fe0f.png","sheet_x":46,"sheet_y":5,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FB"},"1F3FC":{"unified":"1F9DD-1F3FC-200D-2642-FE0F","non_qualified":"1F9DD-1F3FC-200D-2642","image":"1f9dd-1f3fc-200d-2642-fe0f.png","sheet_x":46,"sheet_y":6,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FC"},"1F3FD":{"unified":"1F9DD-1F3FD-200D-2642-FE0F","non_qualified":"1F9DD-1F3FD-200D-2642","image":"1f9dd-1f3fd-200d-2642-fe0f.png","sheet_x":46,"sheet_y":7,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FD"},"1F3FE":{"unified":"1F9DD-1F3FE-200D-2642-FE0F","non_qualified":"1F9DD-1F3FE-200D-2642","image":"1f9dd-1f3fe-200d-2642-fe0f.png","sheet_x":46,"sheet_y":8,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FE"},"1F3FF":{"unified":"1F9DD-1F3FF-200D-2642-FE0F","non_qualified":"1F9DD-1F3FF-200D-2642","image":"1f9dd-1f3ff-200d-2642-fe0f.png","sheet_x":46,"sheet_y":9,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9DD-1F3FF"}},"obsoletes":"1F9DD","a":"Male Elf","b":"1F9DD-200D-2642-FE0F","c":"1F9DD-200D-2642","k":[46,4],"o":10},"small_orange_diamond":{"a":"Small Orange Diamond","b":"1F538","j":["shape","jewel","gem"],"k":[28,6]},"flag-pr":{"a":"Puerto Rico Flag","b":"1F1F5-1F1F7","k":[4,10]},"cyclone":{"a":"Cyclone","b":"1F300","j":["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],"k":[5,44]},"rainbow":{"a":"Rainbow","b":"1F308","j":["nature","happy","unicorn_face","photo","sky","spring"],"k":[6,0]},"small_blue_diamond":{"a":"Small Blue Diamond","b":"1F539","j":["shape","jewel","gem"],"k":[28,7]},"genie":{"obsoleted_by":"1F9DE-200D-2642-FE0F","a":"Genie","b":"1F9DE","k":[46,18],"o":10},"flag-ps":{"a":"Palestinian Territories Flag","b":"1F1F5-1F1F8","k":[4,11]},"small_red_triangle":{"a":"Up-Pointing Red Triangle","b":"1F53A","j":["shape","direction","up","top"],"k":[28,8]},"closed_umbrella":{"a":"Closed Umbrella","b":"1F302","j":["weather","rain","drizzle"],"k":[5,46]},"female_genie":{"a":"Female Genie","b":"1F9DE-200D-2640-FE0F","c":"1F9DE-200D-2640","k":[46,16],"o":10},"flag-pt":{"a":"Portugal Flag","b":"1F1F5-1F1F9","k":[4,12]},"flag-pw":{"a":"Palau Flag","b":"1F1F5-1F1FC","k":[4,13]},"small_red_triangle_down":{"a":"Down-Pointing Red Triangle","b":"1F53B","j":["shape","direction","bottom"],"k":[28,9]},"umbrella":{"a":"Umbrella","b":"2602-FE0F","c":"2602","j":["rainy","weather","spring"],"k":[47,18],"o":1},"male_genie":{"obsoletes":"1F9DE","a":"Male Genie","b":"1F9DE-200D-2642-FE0F","c":"1F9DE-200D-2642","k":[46,17],"o":10},"zombie":{"obsoleted_by":"1F9DF-200D-2642-FE0F","a":"Zombie","b":"1F9DF","k":[46,21],"o":10},"flag-py":{"a":"Paraguay Flag","b":"1F1F5-1F1FE","k":[4,14]},"diamond_shape_with_a_dot_inside":{"a":"Diamond Shape with a Dot Inside","b":"1F4A0","j":["jewel","blue","gem","crystal","fancy"],"k":[25,6]},"umbrella_with_rain_drops":{"a":"Umbrella with Rain Drops","b":"2614","k":[47,23],"o":4},"radio_button":{"a":"Radio Button","b":"1F518","j":["input","old","music","circle"],"k":[27,26]},"female_zombie":{"a":"Female Zombie","b":"1F9DF-200D-2640-FE0F","c":"1F9DF-200D-2640","k":[46,19],"o":10},"flag-qa":{"a":"Qatar Flag","b":"1F1F6-1F1E6","k":[4,15]},"umbrella_on_ground":{"a":"Umbrella on Ground","b":"26F1-FE0F","c":"26F1","k":[48,39],"o":5},"black_square_button":{"a":"Black Square Button","b":"1F532","j":["shape","input","frame"],"k":[28,0]},"zap":{"a":"High Voltage Sign","b":"26A1","j":["thunder","weather","lightning bolt","fast"],"k":[48,21],"o":4},"male_zombie":{"obsoletes":"1F9DF","a":"Male Zombie","b":"1F9DF-200D-2642-FE0F","c":"1F9DF-200D-2642","k":[46,20],"o":10},"flag-re":{"a":"Réunion Flag","b":"1F1F7-1F1EA","k":[4,16]},"flag-ro":{"a":"Romania Flag","b":"1F1F7-1F1F4","k":[4,17]},"snowflake":{"a":"Snowflake","b":"2744-FE0F","c":"2744","j":["winter","season","cold","weather","christmas","xmas"],"k":[49,51],"o":1},"white_square_button":{"a":"White Square Button","b":"1F533","j":["shape","input"],"k":[28,1]},"person_frowning":{"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB","non_qualified":null,"image":"1f64d-1f3fb.png","sheet_x":33,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F64D-1F3FC","non_qualified":null,"image":"1f64d-1f3fc.png","sheet_x":33,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F64D-1F3FD","non_qualified":null,"image":"1f64d-1f3fd.png","sheet_x":33,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F64D-1F3FE","non_qualified":null,"image":"1f64d-1f3fe.png","sheet_x":33,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F64D-1F3FF","non_qualified":null,"image":"1f64d-1f3ff.png","sheet_x":33,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F64D-200D-2640-FE0F","a":"Person Frowning","b":"1F64D","k":[33,30]},"flag-rs":{"a":"Serbia Flag","b":"1F1F7-1F1F8","k":[4,18]},"man-frowning":{"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB-200D-2642-FE0F","non_qualified":"1F64D-1F3FB-200D-2642","image":"1f64d-1f3fb-200d-2642-fe0f.png","sheet_x":33,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64D-1F3FC-200D-2642-FE0F","non_qualified":"1F64D-1F3FC-200D-2642","image":"1f64d-1f3fc-200d-2642-fe0f.png","sheet_x":33,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64D-1F3FD-200D-2642-FE0F","non_qualified":"1F64D-1F3FD-200D-2642","image":"1f64d-1f3fd-200d-2642-fe0f.png","sheet_x":33,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64D-1F3FE-200D-2642-FE0F","non_qualified":"1F64D-1F3FE-200D-2642","image":"1f64d-1f3fe-200d-2642-fe0f.png","sheet_x":33,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64D-1F3FF-200D-2642-FE0F","non_qualified":"1F64D-1F3FF-200D-2642","image":"1f64d-1f3ff-200d-2642-fe0f.png","sheet_x":33,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Frowning","b":"1F64D-200D-2642-FE0F","c":"1F64D-200D-2642","k":[33,24]},"white_circle":{"a":"Medium White Circle","b":"26AA","j":["shape","round"],"k":[48,22],"o":4},"snowman":{"a":"Snowman","b":"2603-FE0F","c":"2603","j":["winter","season","cold","weather","christmas","xmas","frozen","without_snow"],"k":[47,19],"o":1},"snowman_without_snow":{"a":"Snowman Without Snow","b":"26C4","k":[48,28],"o":5},"ru":{"a":"Russia Flag","b":"1F1F7-1F1FA","j":["russian","federation","flag","nation","country","banner"],"k":[4,19],"n":["flag-ru"]},"black_circle":{"a":"Medium Black Circle","b":"26AB","j":["shape","button","round"],"k":[48,23],"o":4},"woman-frowning":{"skin_variations":{"1F3FB":{"unified":"1F64D-1F3FB-200D-2640-FE0F","non_qualified":"1F64D-1F3FB-200D-2640","image":"1f64d-1f3fb-200d-2640-fe0f.png","sheet_x":33,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64D-1F3FC-200D-2640-FE0F","non_qualified":"1F64D-1F3FC-200D-2640","image":"1f64d-1f3fc-200d-2640-fe0f.png","sheet_x":33,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64D-1F3FD-200D-2640-FE0F","non_qualified":"1F64D-1F3FD-200D-2640","image":"1f64d-1f3fd-200d-2640-fe0f.png","sheet_x":33,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64D-1F3FE-200D-2640-FE0F","non_qualified":"1F64D-1F3FE-200D-2640","image":"1f64d-1f3fe-200d-2640-fe0f.png","sheet_x":33,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64D-1F3FF-200D-2640-FE0F","non_qualified":"1F64D-1F3FF-200D-2640","image":"1f64d-1f3ff-200d-2640-fe0f.png","sheet_x":33,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F64D","a":"Woman Frowning","b":"1F64D-200D-2640-FE0F","c":"1F64D-200D-2640","k":[33,18]},"flag-rw":{"a":"Rwanda Flag","b":"1F1F7-1F1FC","k":[4,20]},"comet":{"a":"Comet","b":"2604-FE0F","c":"2604","j":["space"],"k":[47,20],"o":1},"person_with_pouting_face":{"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB","non_qualified":null,"image":"1f64e-1f3fb.png","sheet_x":33,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F64E-1F3FC","non_qualified":null,"image":"1f64e-1f3fc.png","sheet_x":33,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F64E-1F3FD","non_qualified":null,"image":"1f64e-1f3fd.png","sheet_x":33,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F64E-1F3FE","non_qualified":null,"image":"1f64e-1f3fe.png","sheet_x":34,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F64E-1F3FF","non_qualified":null,"image":"1f64e-1f3ff.png","sheet_x":34,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F64E-200D-2640-FE0F","a":"Person with Pouting Face","b":"1F64E","k":[33,48]},"red_circle":{"a":"Large Red Circle","b":"1F534","j":["shape","error","danger"],"k":[28,2]},"large_blue_circle":{"a":"Large Blue Circle","b":"1F535","j":["shape","icon","button"],"k":[28,3]},"man-pouting":{"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB-200D-2642-FE0F","non_qualified":"1F64E-1F3FB-200D-2642","image":"1f64e-1f3fb-200d-2642-fe0f.png","sheet_x":33,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64E-1F3FC-200D-2642-FE0F","non_qualified":"1F64E-1F3FC-200D-2642","image":"1f64e-1f3fc-200d-2642-fe0f.png","sheet_x":33,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64E-1F3FD-200D-2642-FE0F","non_qualified":"1F64E-1F3FD-200D-2642","image":"1f64e-1f3fd-200d-2642-fe0f.png","sheet_x":33,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64E-1F3FE-200D-2642-FE0F","non_qualified":"1F64E-1F3FE-200D-2642","image":"1f64e-1f3fe-200d-2642-fe0f.png","sheet_x":33,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64E-1F3FF-200D-2642-FE0F","non_qualified":"1F64E-1F3FF-200D-2642","image":"1f64e-1f3ff-200d-2642-fe0f.png","sheet_x":33,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Pouting","b":"1F64E-200D-2642-FE0F","c":"1F64E-200D-2642","k":[33,42]},"flag-sa":{"a":"Saudi Arabia Flag","b":"1F1F8-1F1E6","k":[4,21]},"fire":{"a":"Fire","b":"1F525","j":["hot","cook","flame"],"k":[27,39]},"woman-pouting":{"skin_variations":{"1F3FB":{"unified":"1F64E-1F3FB-200D-2640-FE0F","non_qualified":"1F64E-1F3FB-200D-2640","image":"1f64e-1f3fb-200d-2640-fe0f.png","sheet_x":33,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64E-1F3FC-200D-2640-FE0F","non_qualified":"1F64E-1F3FC-200D-2640","image":"1f64e-1f3fc-200d-2640-fe0f.png","sheet_x":33,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64E-1F3FD-200D-2640-FE0F","non_qualified":"1F64E-1F3FD-200D-2640","image":"1f64e-1f3fd-200d-2640-fe0f.png","sheet_x":33,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64E-1F3FE-200D-2640-FE0F","non_qualified":"1F64E-1F3FE-200D-2640","image":"1f64e-1f3fe-200d-2640-fe0f.png","sheet_x":33,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64E-1F3FF-200D-2640-FE0F","non_qualified":"1F64E-1F3FF-200D-2640","image":"1f64e-1f3ff-200d-2640-fe0f.png","sheet_x":33,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F64E","a":"Woman Pouting","b":"1F64E-200D-2640-FE0F","c":"1F64E-200D-2640","k":[33,36]},"flag-sb":{"a":"Solomon Islands Flag","b":"1F1F8-1F1E7","k":[4,22]},"droplet":{"a":"Droplet","b":"1F4A7","j":["water","drip","faucet","spring"],"k":[25,13]},"no_good":{"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB","non_qualified":null,"image":"1f645-1f3fb.png","sheet_x":32,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F645-1F3FC","non_qualified":null,"image":"1f645-1f3fc.png","sheet_x":32,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F645-1F3FD","non_qualified":null,"image":"1f645-1f3fd.png","sheet_x":32,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F645-1F3FE","non_qualified":null,"image":"1f645-1f3fe.png","sheet_x":32,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F645-1F3FF","non_qualified":null,"image":"1f645-1f3ff.png","sheet_x":32,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F645-200D-2640-FE0F","a":"Face with No Good Gesture","b":"1F645","k":[32,1]},"flag-sc":{"a":"Seychelles Flag","b":"1F1F8-1F1E8","k":[4,23]},"ocean":{"a":"Water Wave","b":"1F30A","j":["sea","water","wave","nature","tsunami","disaster"],"k":[6,2]},"man-gesturing-no":{"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB-200D-2642-FE0F","non_qualified":"1F645-1F3FB-200D-2642","image":"1f645-1f3fb-200d-2642-fe0f.png","sheet_x":31,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F645-1F3FC-200D-2642-FE0F","non_qualified":"1F645-1F3FC-200D-2642","image":"1f645-1f3fc-200d-2642-fe0f.png","sheet_x":31,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F645-1F3FD-200D-2642-FE0F","non_qualified":"1F645-1F3FD-200D-2642","image":"1f645-1f3fd-200d-2642-fe0f.png","sheet_x":31,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F645-1F3FE-200D-2642-FE0F","non_qualified":"1F645-1F3FE-200D-2642","image":"1f645-1f3fe-200d-2642-fe0f.png","sheet_x":31,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F645-1F3FF-200D-2642-FE0F","non_qualified":"1F645-1F3FF-200D-2642","image":"1f645-1f3ff-200d-2642-fe0f.png","sheet_x":32,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Gesturing No","b":"1F645-200D-2642-FE0F","c":"1F645-200D-2642","k":[31,47]},"flag-sd":{"a":"Sudan Flag","b":"1F1F8-1F1E9","k":[4,24]},"woman-gesturing-no":{"skin_variations":{"1F3FB":{"unified":"1F645-1F3FB-200D-2640-FE0F","non_qualified":"1F645-1F3FB-200D-2640","image":"1f645-1f3fb-200d-2640-fe0f.png","sheet_x":31,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F645-1F3FC-200D-2640-FE0F","non_qualified":"1F645-1F3FC-200D-2640","image":"1f645-1f3fc-200d-2640-fe0f.png","sheet_x":31,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F645-1F3FD-200D-2640-FE0F","non_qualified":"1F645-1F3FD-200D-2640","image":"1f645-1f3fd-200d-2640-fe0f.png","sheet_x":31,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F645-1F3FE-200D-2640-FE0F","non_qualified":"1F645-1F3FE-200D-2640","image":"1f645-1f3fe-200d-2640-fe0f.png","sheet_x":31,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F645-1F3FF-200D-2640-FE0F","non_qualified":"1F645-1F3FF-200D-2640","image":"1f645-1f3ff-200d-2640-fe0f.png","sheet_x":31,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F645","a":"Woman Gesturing No","b":"1F645-200D-2640-FE0F","c":"1F645-200D-2640","k":[31,41]},"flag-se":{"a":"Sweden Flag","b":"1F1F8-1F1EA","k":[4,25]},"flag-sg":{"a":"Singapore Flag","b":"1F1F8-1F1EC","k":[4,26]},"ok_woman":{"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB","non_qualified":null,"image":"1f646-1f3fb.png","sheet_x":32,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F646-1F3FC","non_qualified":null,"image":"1f646-1f3fc.png","sheet_x":32,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F646-1F3FD","non_qualified":null,"image":"1f646-1f3fd.png","sheet_x":32,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F646-1F3FE","non_qualified":null,"image":"1f646-1f3fe.png","sheet_x":32,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F646-1F3FF","non_qualified":null,"image":"1f646-1f3ff.png","sheet_x":32,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F646-200D-2640-FE0F","a":"Face with Ok Gesture","b":"1F646","j":["women","girl","female","pink","human","woman"],"k":[32,19]},"flag-sh":{"a":"St. Helena Flag","b":"1F1F8-1F1ED","k":[4,27]},"man-gesturing-ok":{"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB-200D-2642-FE0F","non_qualified":"1F646-1F3FB-200D-2642","image":"1f646-1f3fb-200d-2642-fe0f.png","sheet_x":32,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F646-1F3FC-200D-2642-FE0F","non_qualified":"1F646-1F3FC-200D-2642","image":"1f646-1f3fc-200d-2642-fe0f.png","sheet_x":32,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F646-1F3FD-200D-2642-FE0F","non_qualified":"1F646-1F3FD-200D-2642","image":"1f646-1f3fd-200d-2642-fe0f.png","sheet_x":32,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F646-1F3FE-200D-2642-FE0F","non_qualified":"1F646-1F3FE-200D-2642","image":"1f646-1f3fe-200d-2642-fe0f.png","sheet_x":32,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F646-1F3FF-200D-2642-FE0F","non_qualified":"1F646-1F3FF-200D-2642","image":"1f646-1f3ff-200d-2642-fe0f.png","sheet_x":32,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Gesturing Ok","b":"1F646-200D-2642-FE0F","c":"1F646-200D-2642","k":[32,13]},"flag-si":{"a":"Slovenia Flag","b":"1F1F8-1F1EE","k":[4,28]},"woman-gesturing-ok":{"skin_variations":{"1F3FB":{"unified":"1F646-1F3FB-200D-2640-FE0F","non_qualified":"1F646-1F3FB-200D-2640","image":"1f646-1f3fb-200d-2640-fe0f.png","sheet_x":32,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F646-1F3FC-200D-2640-FE0F","non_qualified":"1F646-1F3FC-200D-2640","image":"1f646-1f3fc-200d-2640-fe0f.png","sheet_x":32,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F646-1F3FD-200D-2640-FE0F","non_qualified":"1F646-1F3FD-200D-2640","image":"1f646-1f3fd-200d-2640-fe0f.png","sheet_x":32,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F646-1F3FE-200D-2640-FE0F","non_qualified":"1F646-1F3FE-200D-2640","image":"1f646-1f3fe-200d-2640-fe0f.png","sheet_x":32,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F646-1F3FF-200D-2640-FE0F","non_qualified":"1F646-1F3FF-200D-2640","image":"1f646-1f3ff-200d-2640-fe0f.png","sheet_x":32,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F646","a":"Woman Gesturing Ok","b":"1F646-200D-2640-FE0F","c":"1F646-200D-2640","k":[32,7]},"information_desk_person":{"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB","non_qualified":null,"image":"1f481-1f3fb.png","sheet_x":23,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F481-1F3FC","non_qualified":null,"image":"1f481-1f3fc.png","sheet_x":23,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F481-1F3FD","non_qualified":null,"image":"1f481-1f3fd.png","sheet_x":23,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F481-1F3FE","non_qualified":null,"image":"1f481-1f3fe.png","sheet_x":23,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F481-1F3FF","non_qualified":null,"image":"1f481-1f3ff.png","sheet_x":23,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F481-200D-2640-FE0F","a":"Information Desk Person","b":"1F481","k":[23,13]},"flag-sj":{"a":"Svalbard & Jan Mayen Flag","b":"1F1F8-1F1EF","k":[4,29]},"man-tipping-hand":{"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB-200D-2642-FE0F","non_qualified":"1F481-1F3FB-200D-2642","image":"1f481-1f3fb-200d-2642-fe0f.png","sheet_x":23,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F481-1F3FC-200D-2642-FE0F","non_qualified":"1F481-1F3FC-200D-2642","image":"1f481-1f3fc-200d-2642-fe0f.png","sheet_x":23,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F481-1F3FD-200D-2642-FE0F","non_qualified":"1F481-1F3FD-200D-2642","image":"1f481-1f3fd-200d-2642-fe0f.png","sheet_x":23,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F481-1F3FE-200D-2642-FE0F","non_qualified":"1F481-1F3FE-200D-2642","image":"1f481-1f3fe-200d-2642-fe0f.png","sheet_x":23,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F481-1F3FF-200D-2642-FE0F","non_qualified":"1F481-1F3FF-200D-2642","image":"1f481-1f3ff-200d-2642-fe0f.png","sheet_x":23,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Tipping Hand","b":"1F481-200D-2642-FE0F","c":"1F481-200D-2642","k":[23,7]},"flag-sk":{"a":"Slovakia Flag","b":"1F1F8-1F1F0","k":[4,30]},"flag-sl":{"a":"Sierra Leone Flag","b":"1F1F8-1F1F1","k":[4,31]},"woman-tipping-hand":{"skin_variations":{"1F3FB":{"unified":"1F481-1F3FB-200D-2640-FE0F","non_qualified":"1F481-1F3FB-200D-2640","image":"1f481-1f3fb-200d-2640-fe0f.png","sheet_x":23,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F481-1F3FC-200D-2640-FE0F","non_qualified":"1F481-1F3FC-200D-2640","image":"1f481-1f3fc-200d-2640-fe0f.png","sheet_x":23,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F481-1F3FD-200D-2640-FE0F","non_qualified":"1F481-1F3FD-200D-2640","image":"1f481-1f3fd-200d-2640-fe0f.png","sheet_x":23,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F481-1F3FE-200D-2640-FE0F","non_qualified":"1F481-1F3FE-200D-2640","image":"1f481-1f3fe-200d-2640-fe0f.png","sheet_x":23,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F481-1F3FF-200D-2640-FE0F","non_qualified":"1F481-1F3FF-200D-2640","image":"1f481-1f3ff-200d-2640-fe0f.png","sheet_x":23,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F481","a":"Woman Tipping Hand","b":"1F481-200D-2640-FE0F","c":"1F481-200D-2640","k":[23,1]},"flag-sm":{"a":"San Marino Flag","b":"1F1F8-1F1F2","k":[4,32]},"raising_hand":{"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB","non_qualified":null,"image":"1f64b-1f3fb.png","sheet_x":33,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F64B-1F3FC","non_qualified":null,"image":"1f64b-1f3fc.png","sheet_x":33,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F64B-1F3FD","non_qualified":null,"image":"1f64b-1f3fd.png","sheet_x":33,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F64B-1F3FE","non_qualified":null,"image":"1f64b-1f3fe.png","sheet_x":33,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F64B-1F3FF","non_qualified":null,"image":"1f64b-1f3ff.png","sheet_x":33,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F64B-200D-2640-FE0F","a":"Happy Person Raising One Hand","b":"1F64B","k":[33,6]},"flag-sn":{"a":"Senegal Flag","b":"1F1F8-1F1F3","k":[4,33]},"man-raising-hand":{"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB-200D-2642-FE0F","non_qualified":"1F64B-1F3FB-200D-2642","image":"1f64b-1f3fb-200d-2642-fe0f.png","sheet_x":33,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64B-1F3FC-200D-2642-FE0F","non_qualified":"1F64B-1F3FC-200D-2642","image":"1f64b-1f3fc-200d-2642-fe0f.png","sheet_x":33,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64B-1F3FD-200D-2642-FE0F","non_qualified":"1F64B-1F3FD-200D-2642","image":"1f64b-1f3fd-200d-2642-fe0f.png","sheet_x":33,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64B-1F3FE-200D-2642-FE0F","non_qualified":"1F64B-1F3FE-200D-2642","image":"1f64b-1f3fe-200d-2642-fe0f.png","sheet_x":33,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64B-1F3FF-200D-2642-FE0F","non_qualified":"1F64B-1F3FF-200D-2642","image":"1f64b-1f3ff-200d-2642-fe0f.png","sheet_x":33,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Raising Hand","b":"1F64B-200D-2642-FE0F","c":"1F64B-200D-2642","k":[33,0]},"flag-so":{"a":"Somalia Flag","b":"1F1F8-1F1F4","k":[4,34]},"woman-raising-hand":{"skin_variations":{"1F3FB":{"unified":"1F64B-1F3FB-200D-2640-FE0F","non_qualified":"1F64B-1F3FB-200D-2640","image":"1f64b-1f3fb-200d-2640-fe0f.png","sheet_x":32,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F64B-1F3FC-200D-2640-FE0F","non_qualified":"1F64B-1F3FC-200D-2640","image":"1f64b-1f3fc-200d-2640-fe0f.png","sheet_x":32,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F64B-1F3FD-200D-2640-FE0F","non_qualified":"1F64B-1F3FD-200D-2640","image":"1f64b-1f3fd-200d-2640-fe0f.png","sheet_x":32,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F64B-1F3FE-200D-2640-FE0F","non_qualified":"1F64B-1F3FE-200D-2640","image":"1f64b-1f3fe-200d-2640-fe0f.png","sheet_x":32,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F64B-1F3FF-200D-2640-FE0F","non_qualified":"1F64B-1F3FF-200D-2640","image":"1f64b-1f3ff-200d-2640-fe0f.png","sheet_x":32,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F64B","a":"Woman Raising Hand","b":"1F64B-200D-2640-FE0F","c":"1F64B-200D-2640","k":[32,46]},"flag-sr":{"a":"Suriname Flag","b":"1F1F8-1F1F7","k":[4,35]},"bow":{"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB","non_qualified":null,"image":"1f647-1f3fb.png","sheet_x":32,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F647-1F3FC","non_qualified":null,"image":"1f647-1f3fc.png","sheet_x":32,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F647-1F3FD","non_qualified":null,"image":"1f647-1f3fd.png","sheet_x":32,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F647-1F3FE","non_qualified":null,"image":"1f647-1f3fe.png","sheet_x":32,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F647-1F3FF","non_qualified":null,"image":"1f647-1f3ff.png","sheet_x":32,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F647-200D-2642-FE0F","a":"Person Bowing Deeply","b":"1F647","k":[32,37]},"man-bowing":{"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB-200D-2642-FE0F","non_qualified":"1F647-1F3FB-200D-2642","image":"1f647-1f3fb-200d-2642-fe0f.png","sheet_x":32,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F647-1F3FC-200D-2642-FE0F","non_qualified":"1F647-1F3FC-200D-2642","image":"1f647-1f3fc-200d-2642-fe0f.png","sheet_x":32,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F647-1F3FD-200D-2642-FE0F","non_qualified":"1F647-1F3FD-200D-2642","image":"1f647-1f3fd-200d-2642-fe0f.png","sheet_x":32,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F647-1F3FE-200D-2642-FE0F","non_qualified":"1F647-1F3FE-200D-2642","image":"1f647-1f3fe-200d-2642-fe0f.png","sheet_x":32,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F647-1F3FF-200D-2642-FE0F","non_qualified":"1F647-1F3FF-200D-2642","image":"1f647-1f3ff-200d-2642-fe0f.png","sheet_x":32,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F647","a":"Man Bowing","b":"1F647-200D-2642-FE0F","c":"1F647-200D-2642","k":[32,31]},"flag-ss":{"a":"South Sudan Flag","b":"1F1F8-1F1F8","k":[4,36]},"woman-bowing":{"skin_variations":{"1F3FB":{"unified":"1F647-1F3FB-200D-2640-FE0F","non_qualified":"1F647-1F3FB-200D-2640","image":"1f647-1f3fb-200d-2640-fe0f.png","sheet_x":32,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F647-1F3FC-200D-2640-FE0F","non_qualified":"1F647-1F3FC-200D-2640","image":"1f647-1f3fc-200d-2640-fe0f.png","sheet_x":32,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F647-1F3FD-200D-2640-FE0F","non_qualified":"1F647-1F3FD-200D-2640","image":"1f647-1f3fd-200d-2640-fe0f.png","sheet_x":32,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F647-1F3FE-200D-2640-FE0F","non_qualified":"1F647-1F3FE-200D-2640","image":"1f647-1f3fe-200d-2640-fe0f.png","sheet_x":32,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F647-1F3FF-200D-2640-FE0F","non_qualified":"1F647-1F3FF-200D-2640","image":"1f647-1f3ff-200d-2640-fe0f.png","sheet_x":32,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Bowing","b":"1F647-200D-2640-FE0F","c":"1F647-200D-2640","k":[32,25]},"flag-st":{"a":"São Tomé & Príncipe Flag","b":"1F1F8-1F1F9","k":[4,37]},"face_palm":{"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB","non_qualified":null,"image":"1f926-1f3fb.png","sheet_x":38,"sheet_y":42,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F926-1F3FC","non_qualified":null,"image":"1f926-1f3fc.png","sheet_x":38,"sheet_y":43,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F926-1F3FD","non_qualified":null,"image":"1f926-1f3fd.png","sheet_x":38,"sheet_y":44,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F926-1F3FE","non_qualified":null,"image":"1f926-1f3fe.png","sheet_x":38,"sheet_y":45,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F926-1F3FF","non_qualified":null,"image":"1f926-1f3ff.png","sheet_x":38,"sheet_y":46,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Face Palm","b":"1F926","k":[38,41],"o":9},"flag-sv":{"a":"El Salvador Flag","b":"1F1F8-1F1FB","k":[4,38]},"man-facepalming":{"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB-200D-2642-FE0F","non_qualified":"1F926-1F3FB-200D-2642","image":"1f926-1f3fb-200d-2642-fe0f.png","sheet_x":38,"sheet_y":36,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F926-1F3FC-200D-2642-FE0F","non_qualified":"1F926-1F3FC-200D-2642","image":"1f926-1f3fc-200d-2642-fe0f.png","sheet_x":38,"sheet_y":37,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F926-1F3FD-200D-2642-FE0F","non_qualified":"1F926-1F3FD-200D-2642","image":"1f926-1f3fd-200d-2642-fe0f.png","sheet_x":38,"sheet_y":38,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F926-1F3FE-200D-2642-FE0F","non_qualified":"1F926-1F3FE-200D-2642","image":"1f926-1f3fe-200d-2642-fe0f.png","sheet_x":38,"sheet_y":39,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F926-1F3FF-200D-2642-FE0F","non_qualified":"1F926-1F3FF-200D-2642","image":"1f926-1f3ff-200d-2642-fe0f.png","sheet_x":38,"sheet_y":40,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Facepalming","b":"1F926-200D-2642-FE0F","c":"1F926-200D-2642","k":[38,35],"o":9},"flag-sx":{"a":"Sint Maarten Flag","b":"1F1F8-1F1FD","k":[4,39]},"flag-sy":{"a":"Syria Flag","b":"1F1F8-1F1FE","k":[4,40]},"woman-facepalming":{"skin_variations":{"1F3FB":{"unified":"1F926-1F3FB-200D-2640-FE0F","non_qualified":"1F926-1F3FB-200D-2640","image":"1f926-1f3fb-200d-2640-fe0f.png","sheet_x":38,"sheet_y":30,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F926-1F3FC-200D-2640-FE0F","non_qualified":"1F926-1F3FC-200D-2640","image":"1f926-1f3fc-200d-2640-fe0f.png","sheet_x":38,"sheet_y":31,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F926-1F3FD-200D-2640-FE0F","non_qualified":"1F926-1F3FD-200D-2640","image":"1f926-1f3fd-200d-2640-fe0f.png","sheet_x":38,"sheet_y":32,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F926-1F3FE-200D-2640-FE0F","non_qualified":"1F926-1F3FE-200D-2640","image":"1f926-1f3fe-200d-2640-fe0f.png","sheet_x":38,"sheet_y":33,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F926-1F3FF-200D-2640-FE0F","non_qualified":"1F926-1F3FF-200D-2640","image":"1f926-1f3ff-200d-2640-fe0f.png","sheet_x":38,"sheet_y":34,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Facepalming","b":"1F926-200D-2640-FE0F","c":"1F926-200D-2640","k":[38,29],"o":9},"shrug":{"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB","non_qualified":null,"image":"1f937-1f3fb.png","sheet_x":40,"sheet_y":7,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F937-1F3FC","non_qualified":null,"image":"1f937-1f3fc.png","sheet_x":40,"sheet_y":8,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F937-1F3FD","non_qualified":null,"image":"1f937-1f3fd.png","sheet_x":40,"sheet_y":9,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F937-1F3FE","non_qualified":null,"image":"1f937-1f3fe.png","sheet_x":40,"sheet_y":10,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F937-1F3FF","non_qualified":null,"image":"1f937-1f3ff.png","sheet_x":40,"sheet_y":11,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Shrug","b":"1F937","k":[40,6],"o":9},"flag-sz":{"a":"Swaziland Flag","b":"1F1F8-1F1FF","k":[4,41]},"flag-ta":{"a":"Tristan Da Cunha Flag","b":"1F1F9-1F1E6","k":[4,42]},"man-shrugging":{"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB-200D-2642-FE0F","non_qualified":"1F937-1F3FB-200D-2642","image":"1f937-1f3fb-200d-2642-fe0f.png","sheet_x":40,"sheet_y":1,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F937-1F3FC-200D-2642-FE0F","non_qualified":"1F937-1F3FC-200D-2642","image":"1f937-1f3fc-200d-2642-fe0f.png","sheet_x":40,"sheet_y":2,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F937-1F3FD-200D-2642-FE0F","non_qualified":"1F937-1F3FD-200D-2642","image":"1f937-1f3fd-200d-2642-fe0f.png","sheet_x":40,"sheet_y":3,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F937-1F3FE-200D-2642-FE0F","non_qualified":"1F937-1F3FE-200D-2642","image":"1f937-1f3fe-200d-2642-fe0f.png","sheet_x":40,"sheet_y":4,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F937-1F3FF-200D-2642-FE0F","non_qualified":"1F937-1F3FF-200D-2642","image":"1f937-1f3ff-200d-2642-fe0f.png","sheet_x":40,"sheet_y":5,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Shrugging","b":"1F937-200D-2642-FE0F","c":"1F937-200D-2642","k":[40,0],"o":9},"woman-shrugging":{"skin_variations":{"1F3FB":{"unified":"1F937-1F3FB-200D-2640-FE0F","non_qualified":"1F937-1F3FB-200D-2640","image":"1f937-1f3fb-200d-2640-fe0f.png","sheet_x":39,"sheet_y":47,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F937-1F3FC-200D-2640-FE0F","non_qualified":"1F937-1F3FC-200D-2640","image":"1f937-1f3fc-200d-2640-fe0f.png","sheet_x":39,"sheet_y":48,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F937-1F3FD-200D-2640-FE0F","non_qualified":"1F937-1F3FD-200D-2640","image":"1f937-1f3fd-200d-2640-fe0f.png","sheet_x":39,"sheet_y":49,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F937-1F3FE-200D-2640-FE0F","non_qualified":"1F937-1F3FE-200D-2640","image":"1f937-1f3fe-200d-2640-fe0f.png","sheet_x":39,"sheet_y":50,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F937-1F3FF-200D-2640-FE0F","non_qualified":"1F937-1F3FF-200D-2640","image":"1f937-1f3ff-200d-2640-fe0f.png","sheet_x":39,"sheet_y":51,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Shrugging","b":"1F937-200D-2640-FE0F","c":"1F937-200D-2640","k":[39,46],"o":9},"flag-tc":{"a":"Turks & Caicos Islands Flag","b":"1F1F9-1F1E8","k":[4,43]},"massage":{"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB","non_qualified":null,"image":"1f486-1f3fb.png","sheet_x":24,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F486-1F3FC","non_qualified":null,"image":"1f486-1f3fc.png","sheet_x":24,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F486-1F3FD","non_qualified":null,"image":"1f486-1f3fd.png","sheet_x":24,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F486-1F3FE","non_qualified":null,"image":"1f486-1f3fe.png","sheet_x":24,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F486-1F3FF","non_qualified":null,"image":"1f486-1f3ff.png","sheet_x":24,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F486-200D-2640-FE0F","a":"Face Massage","b":"1F486","k":[24,10]},"flag-td":{"a":"Chad Flag","b":"1F1F9-1F1E9","k":[4,44]},"man-getting-massage":{"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB-200D-2642-FE0F","non_qualified":"1F486-1F3FB-200D-2642","image":"1f486-1f3fb-200d-2642-fe0f.png","sheet_x":24,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F486-1F3FC-200D-2642-FE0F","non_qualified":"1F486-1F3FC-200D-2642","image":"1f486-1f3fc-200d-2642-fe0f.png","sheet_x":24,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F486-1F3FD-200D-2642-FE0F","non_qualified":"1F486-1F3FD-200D-2642","image":"1f486-1f3fd-200d-2642-fe0f.png","sheet_x":24,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F486-1F3FE-200D-2642-FE0F","non_qualified":"1F486-1F3FE-200D-2642","image":"1f486-1f3fe-200d-2642-fe0f.png","sheet_x":24,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F486-1F3FF-200D-2642-FE0F","non_qualified":"1F486-1F3FF-200D-2642","image":"1f486-1f3ff-200d-2642-fe0f.png","sheet_x":24,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Getting Massage","b":"1F486-200D-2642-FE0F","c":"1F486-200D-2642","k":[24,4]},"flag-tf":{"a":"French Southern Territories Flag","b":"1F1F9-1F1EB","k":[4,45]},"woman-getting-massage":{"skin_variations":{"1F3FB":{"unified":"1F486-1F3FB-200D-2640-FE0F","non_qualified":"1F486-1F3FB-200D-2640","image":"1f486-1f3fb-200d-2640-fe0f.png","sheet_x":23,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F486-1F3FC-200D-2640-FE0F","non_qualified":"1F486-1F3FC-200D-2640","image":"1f486-1f3fc-200d-2640-fe0f.png","sheet_x":24,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F486-1F3FD-200D-2640-FE0F","non_qualified":"1F486-1F3FD-200D-2640","image":"1f486-1f3fd-200d-2640-fe0f.png","sheet_x":24,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F486-1F3FE-200D-2640-FE0F","non_qualified":"1F486-1F3FE-200D-2640","image":"1f486-1f3fe-200d-2640-fe0f.png","sheet_x":24,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F486-1F3FF-200D-2640-FE0F","non_qualified":"1F486-1F3FF-200D-2640","image":"1f486-1f3ff-200d-2640-fe0f.png","sheet_x":24,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F486","a":"Woman Getting Massage","b":"1F486-200D-2640-FE0F","c":"1F486-200D-2640","k":[23,50]},"flag-tg":{"a":"Togo Flag","b":"1F1F9-1F1EC","k":[4,46]},"haircut":{"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB","non_qualified":null,"image":"1f487-1f3fb.png","sheet_x":24,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F487-1F3FC","non_qualified":null,"image":"1f487-1f3fc.png","sheet_x":24,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F487-1F3FD","non_qualified":null,"image":"1f487-1f3fd.png","sheet_x":24,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F487-1F3FE","non_qualified":null,"image":"1f487-1f3fe.png","sheet_x":24,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F487-1F3FF","non_qualified":null,"image":"1f487-1f3ff.png","sheet_x":24,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F487-200D-2640-FE0F","a":"Haircut","b":"1F487","k":[24,28]},"flag-th":{"a":"Thailand Flag","b":"1F1F9-1F1ED","k":[4,47]},"man-getting-haircut":{"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB-200D-2642-FE0F","non_qualified":"1F487-1F3FB-200D-2642","image":"1f487-1f3fb-200d-2642-fe0f.png","sheet_x":24,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F487-1F3FC-200D-2642-FE0F","non_qualified":"1F487-1F3FC-200D-2642","image":"1f487-1f3fc-200d-2642-fe0f.png","sheet_x":24,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F487-1F3FD-200D-2642-FE0F","non_qualified":"1F487-1F3FD-200D-2642","image":"1f487-1f3fd-200d-2642-fe0f.png","sheet_x":24,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F487-1F3FE-200D-2642-FE0F","non_qualified":"1F487-1F3FE-200D-2642","image":"1f487-1f3fe-200d-2642-fe0f.png","sheet_x":24,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F487-1F3FF-200D-2642-FE0F","non_qualified":"1F487-1F3FF-200D-2642","image":"1f487-1f3ff-200d-2642-fe0f.png","sheet_x":24,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Getting Haircut","b":"1F487-200D-2642-FE0F","c":"1F487-200D-2642","k":[24,22]},"flag-tj":{"a":"Tajikistan Flag","b":"1F1F9-1F1EF","k":[4,48]},"flag-tk":{"a":"Tokelau Flag","b":"1F1F9-1F1F0","k":[4,49]},"woman-getting-haircut":{"skin_variations":{"1F3FB":{"unified":"1F487-1F3FB-200D-2640-FE0F","non_qualified":"1F487-1F3FB-200D-2640","image":"1f487-1f3fb-200d-2640-fe0f.png","sheet_x":24,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F487-1F3FC-200D-2640-FE0F","non_qualified":"1F487-1F3FC-200D-2640","image":"1f487-1f3fc-200d-2640-fe0f.png","sheet_x":24,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F487-1F3FD-200D-2640-FE0F","non_qualified":"1F487-1F3FD-200D-2640","image":"1f487-1f3fd-200d-2640-fe0f.png","sheet_x":24,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F487-1F3FE-200D-2640-FE0F","non_qualified":"1F487-1F3FE-200D-2640","image":"1f487-1f3fe-200d-2640-fe0f.png","sheet_x":24,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F487-1F3FF-200D-2640-FE0F","non_qualified":"1F487-1F3FF-200D-2640","image":"1f487-1f3ff-200d-2640-fe0f.png","sheet_x":24,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F487","a":"Woman Getting Haircut","b":"1F487-200D-2640-FE0F","c":"1F487-200D-2640","k":[24,16]},"walking":{"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB","non_qualified":null,"image":"1f6b6-1f3fb.png","sheet_x":36,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F6B6-1F3FC","non_qualified":null,"image":"1f6b6-1f3fc.png","sheet_x":36,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F6B6-1F3FD","non_qualified":null,"image":"1f6b6-1f3fd.png","sheet_x":36,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F6B6-1F3FE","non_qualified":null,"image":"1f6b6-1f3fe.png","sheet_x":36,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F6B6-1F3FF","non_qualified":null,"image":"1f6b6-1f3ff.png","sheet_x":36,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F6B6-200D-2642-FE0F","a":"Pedestrian","b":"1F6B6","k":[36,21]},"flag-tl":{"a":"Timor-Leste Flag","b":"1F1F9-1F1F1","k":[4,50]},"man-walking":{"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB-200D-2642-FE0F","non_qualified":"1F6B6-1F3FB-200D-2642","image":"1f6b6-1f3fb-200d-2642-fe0f.png","sheet_x":36,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B6-1F3FC-200D-2642-FE0F","non_qualified":"1F6B6-1F3FC-200D-2642","image":"1f6b6-1f3fc-200d-2642-fe0f.png","sheet_x":36,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B6-1F3FD-200D-2642-FE0F","non_qualified":"1F6B6-1F3FD-200D-2642","image":"1f6b6-1f3fd-200d-2642-fe0f.png","sheet_x":36,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B6-1F3FE-200D-2642-FE0F","non_qualified":"1F6B6-1F3FE-200D-2642","image":"1f6b6-1f3fe-200d-2642-fe0f.png","sheet_x":36,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B6-1F3FF-200D-2642-FE0F","non_qualified":"1F6B6-1F3FF-200D-2642","image":"1f6b6-1f3ff-200d-2642-fe0f.png","sheet_x":36,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F6B6","a":"Man Walking","b":"1F6B6-200D-2642-FE0F","c":"1F6B6-200D-2642","k":[36,15]},"flag-tm":{"a":"Turkmenistan Flag","b":"1F1F9-1F1F2","k":[4,51]},"woman-walking":{"skin_variations":{"1F3FB":{"unified":"1F6B6-1F3FB-200D-2640-FE0F","non_qualified":"1F6B6-1F3FB-200D-2640","image":"1f6b6-1f3fb-200d-2640-fe0f.png","sheet_x":36,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B6-1F3FC-200D-2640-FE0F","non_qualified":"1F6B6-1F3FC-200D-2640","image":"1f6b6-1f3fc-200d-2640-fe0f.png","sheet_x":36,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B6-1F3FD-200D-2640-FE0F","non_qualified":"1F6B6-1F3FD-200D-2640","image":"1f6b6-1f3fd-200d-2640-fe0f.png","sheet_x":36,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B6-1F3FE-200D-2640-FE0F","non_qualified":"1F6B6-1F3FE-200D-2640","image":"1f6b6-1f3fe-200d-2640-fe0f.png","sheet_x":36,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B6-1F3FF-200D-2640-FE0F","non_qualified":"1F6B6-1F3FF-200D-2640","image":"1f6b6-1f3ff-200d-2640-fe0f.png","sheet_x":36,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Walking","b":"1F6B6-200D-2640-FE0F","c":"1F6B6-200D-2640","k":[36,9]},"flag-tn":{"a":"Tunisia Flag","b":"1F1F9-1F1F3","k":[5,0]},"runner":{"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB","non_qualified":null,"image":"1f3c3-1f3fb.png","sheet_x":9,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F3C3-1F3FC","non_qualified":null,"image":"1f3c3-1f3fc.png","sheet_x":9,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F3C3-1F3FD","non_qualified":null,"image":"1f3c3-1f3fd.png","sheet_x":9,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F3C3-1F3FE","non_qualified":null,"image":"1f3c3-1f3fe.png","sheet_x":9,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F3C3-1F3FF","non_qualified":null,"image":"1f3c3-1f3ff.png","sheet_x":9,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F3C3-200D-2642-FE0F","a":"Runner","b":"1F3C3","k":[9,46],"n":["running"]},"flag-to":{"a":"Tonga Flag","b":"1F1F9-1F1F4","k":[5,1]},"man-running":{"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB-200D-2642-FE0F","non_qualified":"1F3C3-1F3FB-200D-2642","image":"1f3c3-1f3fb-200d-2642-fe0f.png","sheet_x":9,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3C3-1F3FC-200D-2642-FE0F","non_qualified":"1F3C3-1F3FC-200D-2642","image":"1f3c3-1f3fc-200d-2642-fe0f.png","sheet_x":9,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3C3-1F3FD-200D-2642-FE0F","non_qualified":"1F3C3-1F3FD-200D-2642","image":"1f3c3-1f3fd-200d-2642-fe0f.png","sheet_x":9,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3C3-1F3FE-200D-2642-FE0F","non_qualified":"1F3C3-1F3FE-200D-2642","image":"1f3c3-1f3fe-200d-2642-fe0f.png","sheet_x":9,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3C3-1F3FF-200D-2642-FE0F","non_qualified":"1F3C3-1F3FF-200D-2642","image":"1f3c3-1f3ff-200d-2642-fe0f.png","sheet_x":9,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3C3","a":"Man Running","b":"1F3C3-200D-2642-FE0F","c":"1F3C3-200D-2642","k":[9,40]},"flag-tr":{"a":"Turkey Flag","b":"1F1F9-1F1F7","k":[5,2]},"flag-tt":{"a":"Trinidad & Tobago Flag","b":"1F1F9-1F1F9","k":[5,3]},"woman-running":{"skin_variations":{"1F3FB":{"unified":"1F3C3-1F3FB-200D-2640-FE0F","non_qualified":"1F3C3-1F3FB-200D-2640","image":"1f3c3-1f3fb-200d-2640-fe0f.png","sheet_x":9,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3C3-1F3FC-200D-2640-FE0F","non_qualified":"1F3C3-1F3FC-200D-2640","image":"1f3c3-1f3fc-200d-2640-fe0f.png","sheet_x":9,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3C3-1F3FD-200D-2640-FE0F","non_qualified":"1F3C3-1F3FD-200D-2640","image":"1f3c3-1f3fd-200d-2640-fe0f.png","sheet_x":9,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3C3-1F3FE-200D-2640-FE0F","non_qualified":"1F3C3-1F3FE-200D-2640","image":"1f3c3-1f3fe-200d-2640-fe0f.png","sheet_x":9,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3C3-1F3FF-200D-2640-FE0F","non_qualified":"1F3C3-1F3FF-200D-2640","image":"1f3c3-1f3ff-200d-2640-fe0f.png","sheet_x":9,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Running","b":"1F3C3-200D-2640-FE0F","c":"1F3C3-200D-2640","k":[9,34]},"flag-tv":{"a":"Tuvalu Flag","b":"1F1F9-1F1FB","k":[5,4]},"dancer":{"skin_variations":{"1F3FB":{"unified":"1F483-1F3FB","non_qualified":null,"image":"1f483-1f3fb.png","sheet_x":23,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F483-1F3FC","non_qualified":null,"image":"1f483-1f3fc.png","sheet_x":23,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F483-1F3FD","non_qualified":null,"image":"1f483-1f3fd.png","sheet_x":23,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F483-1F3FE","non_qualified":null,"image":"1f483-1f3fe.png","sheet_x":23,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F483-1F3FF","non_qualified":null,"image":"1f483-1f3ff.png","sheet_x":23,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Dancer","b":"1F483","j":["female","girl","woman","fun"],"k":[23,37]},"flag-tw":{"a":"Taiwan Flag","b":"1F1F9-1F1FC","k":[5,5]},"man_dancing":{"skin_variations":{"1F3FB":{"unified":"1F57A-1F3FB","non_qualified":null,"image":"1f57a-1f3fb.png","sheet_x":29,"sheet_y":22,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F57A-1F3FC","non_qualified":null,"image":"1f57a-1f3fc.png","sheet_x":29,"sheet_y":23,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F57A-1F3FD","non_qualified":null,"image":"1f57a-1f3fd.png","sheet_x":29,"sheet_y":24,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F57A-1F3FE","non_qualified":null,"image":"1f57a-1f3fe.png","sheet_x":29,"sheet_y":25,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F57A-1F3FF","non_qualified":null,"image":"1f57a-1f3ff.png","sheet_x":29,"sheet_y":26,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Man Dancing","b":"1F57A","j":["male","boy","fun","dancer"],"k":[29,21],"o":9},"dancers":{"obsoleted_by":"1F46F-200D-2640-FE0F","a":"Woman with Bunny Ears","b":"1F46F","k":[21,1]},"flag-tz":{"a":"Tanzania Flag","b":"1F1F9-1F1FF","k":[5,6]},"flag-ua":{"a":"Ukraine Flag","b":"1F1FA-1F1E6","k":[5,7]},"man-with-bunny-ears-partying":{"a":"Man with Bunny Ears Partying","b":"1F46F-200D-2642-FE0F","c":"1F46F-200D-2642","k":[21,0]},"woman-with-bunny-ears-partying":{"obsoletes":"1F46F","a":"Woman with Bunny Ears Partying","b":"1F46F-200D-2640-FE0F","c":"1F46F-200D-2640","k":[20,51]},"flag-ug":{"a":"Uganda Flag","b":"1F1FA-1F1EC","k":[5,8]},"flag-um":{"a":"U.s. Outlying Islands Flag","b":"1F1FA-1F1F2","k":[5,9]},"person_in_steamy_room":{"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB","non_qualified":null,"image":"1f9d6-1f3fb.png","sheet_x":43,"sheet_y":41,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FB-200D-2642-FE0F"},"1F3FC":{"unified":"1F9D6-1F3FC","non_qualified":null,"image":"1f9d6-1f3fc.png","sheet_x":43,"sheet_y":42,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FC-200D-2642-FE0F"},"1F3FD":{"unified":"1F9D6-1F3FD","non_qualified":null,"image":"1f9d6-1f3fd.png","sheet_x":43,"sheet_y":43,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FD-200D-2642-FE0F"},"1F3FE":{"unified":"1F9D6-1F3FE","non_qualified":null,"image":"1f9d6-1f3fe.png","sheet_x":43,"sheet_y":44,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FE-200D-2642-FE0F"},"1F3FF":{"unified":"1F9D6-1F3FF","non_qualified":null,"image":"1f9d6-1f3ff.png","sheet_x":43,"sheet_y":45,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D6-1F3FF-200D-2642-FE0F"}},"obsoleted_by":"1F9D6-200D-2642-FE0F","a":"Person in Steamy Room","b":"1F9D6","k":[43,40],"o":10},"woman_in_steamy_room":{"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB-200D-2640-FE0F","non_qualified":"1F9D6-1F3FB-200D-2640","image":"1f9d6-1f3fb-200d-2640-fe0f.png","sheet_x":43,"sheet_y":29,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9D6-1F3FC-200D-2640-FE0F","non_qualified":"1F9D6-1F3FC-200D-2640","image":"1f9d6-1f3fc-200d-2640-fe0f.png","sheet_x":43,"sheet_y":30,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9D6-1F3FD-200D-2640-FE0F","non_qualified":"1F9D6-1F3FD-200D-2640","image":"1f9d6-1f3fd-200d-2640-fe0f.png","sheet_x":43,"sheet_y":31,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9D6-1F3FE-200D-2640-FE0F","non_qualified":"1F9D6-1F3FE-200D-2640","image":"1f9d6-1f3fe-200d-2640-fe0f.png","sheet_x":43,"sheet_y":32,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9D6-1F3FF-200D-2640-FE0F","non_qualified":"1F9D6-1F3FF-200D-2640","image":"1f9d6-1f3ff-200d-2640-fe0f.png","sheet_x":43,"sheet_y":33,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman in Steamy Room","b":"1F9D6-200D-2640-FE0F","c":"1F9D6-200D-2640","k":[43,28],"o":10},"us":{"a":"United States Flag","b":"1F1FA-1F1F8","j":["united","states","america","flag","nation","country","banner"],"k":[5,11],"n":["flag-us"]},"man_in_steamy_room":{"skin_variations":{"1F3FB":{"unified":"1F9D6-1F3FB-200D-2642-FE0F","non_qualified":"1F9D6-1F3FB-200D-2642","image":"1f9d6-1f3fb-200d-2642-fe0f.png","sheet_x":43,"sheet_y":35,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FB"},"1F3FC":{"unified":"1F9D6-1F3FC-200D-2642-FE0F","non_qualified":"1F9D6-1F3FC-200D-2642","image":"1f9d6-1f3fc-200d-2642-fe0f.png","sheet_x":43,"sheet_y":36,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FC"},"1F3FD":{"unified":"1F9D6-1F3FD-200D-2642-FE0F","non_qualified":"1F9D6-1F3FD-200D-2642","image":"1f9d6-1f3fd-200d-2642-fe0f.png","sheet_x":43,"sheet_y":37,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FD"},"1F3FE":{"unified":"1F9D6-1F3FE-200D-2642-FE0F","non_qualified":"1F9D6-1F3FE-200D-2642","image":"1f9d6-1f3fe-200d-2642-fe0f.png","sheet_x":43,"sheet_y":38,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FE"},"1F3FF":{"unified":"1F9D6-1F3FF-200D-2642-FE0F","non_qualified":"1F9D6-1F3FF-200D-2642","image":"1f9d6-1f3ff-200d-2642-fe0f.png","sheet_x":43,"sheet_y":39,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D6-1F3FF"}},"obsoletes":"1F9D6","a":"Man in Steamy Room","b":"1F9D6-200D-2642-FE0F","c":"1F9D6-200D-2642","k":[43,34],"o":10},"person_climbing":{"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB","non_qualified":null,"image":"1f9d7-1f3fb.png","sheet_x":44,"sheet_y":7,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9D7-1F3FC","non_qualified":null,"image":"1f9d7-1f3fc.png","sheet_x":44,"sheet_y":8,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9D7-1F3FD","non_qualified":null,"image":"1f9d7-1f3fd.png","sheet_x":44,"sheet_y":9,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9D7-1F3FE","non_qualified":null,"image":"1f9d7-1f3fe.png","sheet_x":44,"sheet_y":10,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9D7-1F3FF","non_qualified":null,"image":"1f9d7-1f3ff.png","sheet_x":44,"sheet_y":11,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D7-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9D7-200D-2640-FE0F","a":"Person Climbing","b":"1F9D7","k":[44,6],"o":10},"flag-uy":{"a":"Uruguay Flag","b":"1F1FA-1F1FE","k":[5,12]},"woman_climbing":{"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB-200D-2640-FE0F","non_qualified":"1F9D7-1F3FB-200D-2640","image":"1f9d7-1f3fb-200d-2640-fe0f.png","sheet_x":43,"sheet_y":47,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FB"},"1F3FC":{"unified":"1F9D7-1F3FC-200D-2640-FE0F","non_qualified":"1F9D7-1F3FC-200D-2640","image":"1f9d7-1f3fc-200d-2640-fe0f.png","sheet_x":43,"sheet_y":48,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FC"},"1F3FD":{"unified":"1F9D7-1F3FD-200D-2640-FE0F","non_qualified":"1F9D7-1F3FD-200D-2640","image":"1f9d7-1f3fd-200d-2640-fe0f.png","sheet_x":43,"sheet_y":49,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FD"},"1F3FE":{"unified":"1F9D7-1F3FE-200D-2640-FE0F","non_qualified":"1F9D7-1F3FE-200D-2640","image":"1f9d7-1f3fe-200d-2640-fe0f.png","sheet_x":43,"sheet_y":50,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FE"},"1F3FF":{"unified":"1F9D7-1F3FF-200D-2640-FE0F","non_qualified":"1F9D7-1F3FF-200D-2640","image":"1f9d7-1f3ff-200d-2640-fe0f.png","sheet_x":43,"sheet_y":51,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D7-1F3FF"}},"obsoletes":"1F9D7","a":"Woman Climbing","b":"1F9D7-200D-2640-FE0F","c":"1F9D7-200D-2640","k":[43,46],"o":10},"flag-uz":{"a":"Uzbekistan Flag","b":"1F1FA-1F1FF","k":[5,13]},"man_climbing":{"skin_variations":{"1F3FB":{"unified":"1F9D7-1F3FB-200D-2642-FE0F","non_qualified":"1F9D7-1F3FB-200D-2642","image":"1f9d7-1f3fb-200d-2642-fe0f.png","sheet_x":44,"sheet_y":1,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9D7-1F3FC-200D-2642-FE0F","non_qualified":"1F9D7-1F3FC-200D-2642","image":"1f9d7-1f3fc-200d-2642-fe0f.png","sheet_x":44,"sheet_y":2,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9D7-1F3FD-200D-2642-FE0F","non_qualified":"1F9D7-1F3FD-200D-2642","image":"1f9d7-1f3fd-200d-2642-fe0f.png","sheet_x":44,"sheet_y":3,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9D7-1F3FE-200D-2642-FE0F","non_qualified":"1F9D7-1F3FE-200D-2642","image":"1f9d7-1f3fe-200d-2642-fe0f.png","sheet_x":44,"sheet_y":4,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9D7-1F3FF-200D-2642-FE0F","non_qualified":"1F9D7-1F3FF-200D-2642","image":"1f9d7-1f3ff-200d-2642-fe0f.png","sheet_x":44,"sheet_y":5,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Climbing","b":"1F9D7-200D-2642-FE0F","c":"1F9D7-200D-2642","k":[44,0],"o":10},"flag-va":{"a":"Vatican City Flag","b":"1F1FB-1F1E6","k":[5,14]},"person_in_lotus_position":{"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB","non_qualified":null,"image":"1f9d8-1f3fb.png","sheet_x":44,"sheet_y":25,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FB-200D-2640-FE0F"},"1F3FC":{"unified":"1F9D8-1F3FC","non_qualified":null,"image":"1f9d8-1f3fc.png","sheet_x":44,"sheet_y":26,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FC-200D-2640-FE0F"},"1F3FD":{"unified":"1F9D8-1F3FD","non_qualified":null,"image":"1f9d8-1f3fd.png","sheet_x":44,"sheet_y":27,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FD-200D-2640-FE0F"},"1F3FE":{"unified":"1F9D8-1F3FE","non_qualified":null,"image":"1f9d8-1f3fe.png","sheet_x":44,"sheet_y":28,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FE-200D-2640-FE0F"},"1F3FF":{"unified":"1F9D8-1F3FF","non_qualified":null,"image":"1f9d8-1f3ff.png","sheet_x":44,"sheet_y":29,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false,"obsoleted_by":"1F9D8-1F3FF-200D-2640-FE0F"}},"obsoleted_by":"1F9D8-200D-2640-FE0F","a":"Person in Lotus Position","b":"1F9D8","k":[44,24],"o":10},"flag-vc":{"a":"St. Vincent & Grenadines Flag","b":"1F1FB-1F1E8","k":[5,15]},"flag-ve":{"a":"Venezuela Flag","b":"1F1FB-1F1EA","k":[5,16]},"woman_in_lotus_position":{"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB-200D-2640-FE0F","non_qualified":"1F9D8-1F3FB-200D-2640","image":"1f9d8-1f3fb-200d-2640-fe0f.png","sheet_x":44,"sheet_y":13,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FB"},"1F3FC":{"unified":"1F9D8-1F3FC-200D-2640-FE0F","non_qualified":"1F9D8-1F3FC-200D-2640","image":"1f9d8-1f3fc-200d-2640-fe0f.png","sheet_x":44,"sheet_y":14,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FC"},"1F3FD":{"unified":"1F9D8-1F3FD-200D-2640-FE0F","non_qualified":"1F9D8-1F3FD-200D-2640","image":"1f9d8-1f3fd-200d-2640-fe0f.png","sheet_x":44,"sheet_y":15,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FD"},"1F3FE":{"unified":"1F9D8-1F3FE-200D-2640-FE0F","non_qualified":"1F9D8-1F3FE-200D-2640","image":"1f9d8-1f3fe-200d-2640-fe0f.png","sheet_x":44,"sheet_y":16,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FE"},"1F3FF":{"unified":"1F9D8-1F3FF-200D-2640-FE0F","non_qualified":"1F9D8-1F3FF-200D-2640","image":"1f9d8-1f3ff-200d-2640-fe0f.png","sheet_x":44,"sheet_y":17,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false,"obsoletes":"1F9D8-1F3FF"}},"obsoletes":"1F9D8","a":"Woman in Lotus Position","b":"1F9D8-200D-2640-FE0F","c":"1F9D8-200D-2640","k":[44,12],"o":10},"man_in_lotus_position":{"skin_variations":{"1F3FB":{"unified":"1F9D8-1F3FB-200D-2642-FE0F","non_qualified":"1F9D8-1F3FB-200D-2642","image":"1f9d8-1f3fb-200d-2642-fe0f.png","sheet_x":44,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F9D8-1F3FC-200D-2642-FE0F","non_qualified":"1F9D8-1F3FC-200D-2642","image":"1f9d8-1f3fc-200d-2642-fe0f.png","sheet_x":44,"sheet_y":20,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F9D8-1F3FD-200D-2642-FE0F","non_qualified":"1F9D8-1F3FD-200D-2642","image":"1f9d8-1f3fd-200d-2642-fe0f.png","sheet_x":44,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F9D8-1F3FE-200D-2642-FE0F","non_qualified":"1F9D8-1F3FE-200D-2642","image":"1f9d8-1f3fe-200d-2642-fe0f.png","sheet_x":44,"sheet_y":22,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F9D8-1F3FF-200D-2642-FE0F","non_qualified":"1F9D8-1F3FF-200D-2642","image":"1f9d8-1f3ff-200d-2642-fe0f.png","sheet_x":44,"sheet_y":23,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man in Lotus Position","b":"1F9D8-200D-2642-FE0F","c":"1F9D8-200D-2642","k":[44,18],"o":10},"flag-vg":{"a":"British Virgin Islands Flag","b":"1F1FB-1F1EC","k":[5,17]},"flag-vi":{"a":"U.s. Virgin Islands Flag","b":"1F1FB-1F1EE","k":[5,18]},"bath":{"skin_variations":{"1F3FB":{"unified":"1F6C0-1F3FB","non_qualified":null,"image":"1f6c0-1f3fb.png","sheet_x":36,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F6C0-1F3FC","non_qualified":null,"image":"1f6c0-1f3fc.png","sheet_x":36,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F6C0-1F3FD","non_qualified":null,"image":"1f6c0-1f3fd.png","sheet_x":36,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F6C0-1F3FE","non_qualified":null,"image":"1f6c0-1f3fe.png","sheet_x":36,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F6C0-1F3FF","non_qualified":null,"image":"1f6c0-1f3ff.png","sheet_x":36,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Bath","b":"1F6C0","j":["clean","shower","bathroom"],"k":[36,36]},"sleeping_accommodation":{"skin_variations":{"1F3FB":{"unified":"1F6CC-1F3FB","non_qualified":null,"image":"1f6cc-1f3fb.png","sheet_x":36,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F6CC-1F3FC","non_qualified":null,"image":"1f6cc-1f3fc.png","sheet_x":36,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F6CC-1F3FD","non_qualified":null,"image":"1f6cc-1f3fd.png","sheet_x":36,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F6CC-1F3FE","non_qualified":null,"image":"1f6cc-1f3fe.png","sheet_x":37,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F6CC-1F3FF","non_qualified":null,"image":"1f6cc-1f3ff.png","sheet_x":37,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Sleeping Accommodation","b":"1F6CC","k":[36,48],"o":7},"flag-vn":{"a":"Vietnam Flag","b":"1F1FB-1F1F3","k":[5,19]},"man_in_business_suit_levitating":{"skin_variations":{"1F3FB":{"unified":"1F574-1F3FB","non_qualified":null,"image":"1f574-1f3fb.png","sheet_x":28,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F574-1F3FC","non_qualified":null,"image":"1f574-1f3fc.png","sheet_x":28,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F574-1F3FD","non_qualified":null,"image":"1f574-1f3fd.png","sheet_x":28,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F574-1F3FE","non_qualified":null,"image":"1f574-1f3fe.png","sheet_x":28,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F574-1F3FF","non_qualified":null,"image":"1f574-1f3ff.png","sheet_x":28,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Man in Business Suit Levitating","b":"1F574-FE0F","c":"1F574","k":[28,45],"o":7},"flag-vu":{"a":"Vanuatu Flag","b":"1F1FB-1F1FA","k":[5,20]},"flag-wf":{"a":"Wallis & Futuna Flag","b":"1F1FC-1F1EB","k":[5,21]},"speaking_head_in_silhouette":{"a":"Speaking Head in Silhouette","b":"1F5E3-FE0F","c":"1F5E3","k":[30,14],"o":7},"bust_in_silhouette":{"a":"Bust in Silhouette","b":"1F464","j":["user","person","human"],"k":[15,40]},"flag-ws":{"a":"Samoa Flag","b":"1F1FC-1F1F8","k":[5,22]},"busts_in_silhouette":{"a":"Busts in Silhouette","b":"1F465","j":["user","person","human","group","team"],"k":[15,41]},"flag-xk":{"a":"Kosovo Flag","b":"1F1FD-1F1F0","k":[5,23]},"fencer":{"a":"Fencer","b":"1F93A","k":[40,48],"o":9},"flag-ye":{"a":"Yemen Flag","b":"1F1FE-1F1EA","k":[5,24]},"flag-yt":{"a":"Mayotte Flag","b":"1F1FE-1F1F9","k":[5,25]},"horse_racing":{"skin_variations":{"1F3FB":{"unified":"1F3C7-1F3FB","non_qualified":null,"image":"1f3c7-1f3fb.png","sheet_x":10,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F3C7-1F3FC","non_qualified":null,"image":"1f3c7-1f3fc.png","sheet_x":10,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F3C7-1F3FD","non_qualified":null,"image":"1f3c7-1f3fd.png","sheet_x":10,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F3C7-1F3FE","non_qualified":null,"image":"1f3c7-1f3fe.png","sheet_x":10,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F3C7-1F3FF","non_qualified":null,"image":"1f3c7-1f3ff.png","sheet_x":10,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Horse Racing","b":"1F3C7","j":["animal","betting","competition","gambling","luck"],"k":[10,20]},"flag-za":{"a":"South Africa Flag","b":"1F1FF-1F1E6","k":[5,26]},"skier":{"a":"Skier","b":"26F7-FE0F","c":"26F7","j":["sports","winter","snow"],"k":[48,44],"o":5},"flag-zm":{"a":"Zambia Flag","b":"1F1FF-1F1F2","k":[5,27]},"snowboarder":{"skin_variations":{"1F3FB":{"unified":"1F3C2-1F3FB","non_qualified":null,"image":"1f3c2-1f3fb.png","sheet_x":9,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F3C2-1F3FC","non_qualified":null,"image":"1f3c2-1f3fc.png","sheet_x":9,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F3C2-1F3FD","non_qualified":null,"image":"1f3c2-1f3fd.png","sheet_x":9,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F3C2-1F3FE","non_qualified":null,"image":"1f3c2-1f3fe.png","sheet_x":9,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F3C2-1F3FF","non_qualified":null,"image":"1f3c2-1f3ff.png","sheet_x":9,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Snowboarder","b":"1F3C2","j":["sports","winter"],"k":[9,28]},"golfer":{"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB","non_qualified":null,"image":"1f3cc-1f3fb.png","sheet_x":11,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CC-1F3FC","non_qualified":null,"image":"1f3cc-1f3fc.png","sheet_x":11,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CC-1F3FD","non_qualified":null,"image":"1f3cc-1f3fd.png","sheet_x":11,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CC-1F3FE","non_qualified":null,"image":"1f3cc-1f3fe.png","sheet_x":11,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CC-1F3FF","non_qualified":null,"image":"1f3cc-1f3ff.png","sheet_x":11,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"1F3CC-FE0F-200D-2642-FE0F","a":"Golfer","b":"1F3CC-FE0F","c":"1F3CC","k":[11,24],"o":7},"flag-zw":{"a":"Zimbabwe Flag","b":"1F1FF-1F1FC","k":[5,28]},"man-golfing":{"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB-200D-2642-FE0F","non_qualified":"1F3CC-1F3FB-200D-2642","image":"1f3cc-1f3fb-200d-2642-fe0f.png","sheet_x":11,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CC-1F3FC-200D-2642-FE0F","non_qualified":"1F3CC-1F3FC-200D-2642","image":"1f3cc-1f3fc-200d-2642-fe0f.png","sheet_x":11,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CC-1F3FD-200D-2642-FE0F","non_qualified":"1F3CC-1F3FD-200D-2642","image":"1f3cc-1f3fd-200d-2642-fe0f.png","sheet_x":11,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CC-1F3FE-200D-2642-FE0F","non_qualified":"1F3CC-1F3FE-200D-2642","image":"1f3cc-1f3fe-200d-2642-fe0f.png","sheet_x":11,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CC-1F3FF-200D-2642-FE0F","non_qualified":"1F3CC-1F3FF-200D-2642","image":"1f3cc-1f3ff-200d-2642-fe0f.png","sheet_x":11,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3CC-FE0F","a":"Man Golfing","b":"1F3CC-FE0F-200D-2642-FE0F","k":[11,18],"o":7},"flag-england":{"a":"England Flag","b":"1F3F4-E0067-E0062-E0065-E006E-E0067-E007F","k":[12,16],"o":7},"woman-golfing":{"skin_variations":{"1F3FB":{"unified":"1F3CC-1F3FB-200D-2640-FE0F","non_qualified":"1F3CC-1F3FB-200D-2640","image":"1f3cc-1f3fb-200d-2640-fe0f.png","sheet_x":11,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CC-1F3FC-200D-2640-FE0F","non_qualified":"1F3CC-1F3FC-200D-2640","image":"1f3cc-1f3fc-200d-2640-fe0f.png","sheet_x":11,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CC-1F3FD-200D-2640-FE0F","non_qualified":"1F3CC-1F3FD-200D-2640","image":"1f3cc-1f3fd-200d-2640-fe0f.png","sheet_x":11,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CC-1F3FE-200D-2640-FE0F","non_qualified":"1F3CC-1F3FE-200D-2640","image":"1f3cc-1f3fe-200d-2640-fe0f.png","sheet_x":11,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CC-1F3FF-200D-2640-FE0F","non_qualified":"1F3CC-1F3FF-200D-2640","image":"1f3cc-1f3ff-200d-2640-fe0f.png","sheet_x":11,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Golfing","b":"1F3CC-FE0F-200D-2640-FE0F","k":[11,12],"o":7},"flag-scotland":{"a":"Scotland Flag","b":"1F3F4-E0067-E0062-E0073-E0063-E0074-E007F","k":[12,17],"o":7},"flag-wales":{"a":"Wales Flag","b":"1F3F4-E0067-E0062-E0077-E006C-E0073-E007F","k":[12,18],"o":7},"surfer":{"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB","non_qualified":null,"image":"1f3c4-1f3fb.png","sheet_x":10,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F3C4-1F3FC","non_qualified":null,"image":"1f3c4-1f3fc.png","sheet_x":10,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F3C4-1F3FD","non_qualified":null,"image":"1f3c4-1f3fd.png","sheet_x":10,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F3C4-1F3FE","non_qualified":null,"image":"1f3c4-1f3fe.png","sheet_x":10,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F3C4-1F3FF","non_qualified":null,"image":"1f3c4-1f3ff.png","sheet_x":10,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F3C4-200D-2642-FE0F","a":"Surfer","b":"1F3C4","k":[10,12]},"man-surfing":{"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB-200D-2642-FE0F","non_qualified":"1F3C4-1F3FB-200D-2642","image":"1f3c4-1f3fb-200d-2642-fe0f.png","sheet_x":10,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3C4-1F3FC-200D-2642-FE0F","non_qualified":"1F3C4-1F3FC-200D-2642","image":"1f3c4-1f3fc-200d-2642-fe0f.png","sheet_x":10,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3C4-1F3FD-200D-2642-FE0F","non_qualified":"1F3C4-1F3FD-200D-2642","image":"1f3c4-1f3fd-200d-2642-fe0f.png","sheet_x":10,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3C4-1F3FE-200D-2642-FE0F","non_qualified":"1F3C4-1F3FE-200D-2642","image":"1f3c4-1f3fe-200d-2642-fe0f.png","sheet_x":10,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3C4-1F3FF-200D-2642-FE0F","non_qualified":"1F3C4-1F3FF-200D-2642","image":"1f3c4-1f3ff-200d-2642-fe0f.png","sheet_x":10,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3C4","a":"Man Surfing","b":"1F3C4-200D-2642-FE0F","c":"1F3C4-200D-2642","k":[10,6]},"woman-surfing":{"skin_variations":{"1F3FB":{"unified":"1F3C4-1F3FB-200D-2640-FE0F","non_qualified":"1F3C4-1F3FB-200D-2640","image":"1f3c4-1f3fb-200d-2640-fe0f.png","sheet_x":10,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3C4-1F3FC-200D-2640-FE0F","non_qualified":"1F3C4-1F3FC-200D-2640","image":"1f3c4-1f3fc-200d-2640-fe0f.png","sheet_x":10,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3C4-1F3FD-200D-2640-FE0F","non_qualified":"1F3C4-1F3FD-200D-2640","image":"1f3c4-1f3fd-200d-2640-fe0f.png","sheet_x":10,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3C4-1F3FE-200D-2640-FE0F","non_qualified":"1F3C4-1F3FE-200D-2640","image":"1f3c4-1f3fe-200d-2640-fe0f.png","sheet_x":10,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3C4-1F3FF-200D-2640-FE0F","non_qualified":"1F3C4-1F3FF-200D-2640","image":"1f3c4-1f3ff-200d-2640-fe0f.png","sheet_x":10,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Surfing","b":"1F3C4-200D-2640-FE0F","c":"1F3C4-200D-2640","k":[10,0]},"rowboat":{"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB","non_qualified":null,"image":"1f6a3-1f3fb.png","sheet_x":35,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6A3-1F3FC","non_qualified":null,"image":"1f6a3-1f3fc.png","sheet_x":35,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6A3-1F3FD","non_qualified":null,"image":"1f6a3-1f3fd.png","sheet_x":35,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6A3-1F3FE","non_qualified":null,"image":"1f6a3-1f3fe.png","sheet_x":35,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6A3-1F3FF","non_qualified":null,"image":"1f6a3-1f3ff.png","sheet_x":35,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"1F6A3-200D-2642-FE0F","a":"Rowboat","b":"1F6A3","k":[35,3]},"man-rowing-boat":{"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB-200D-2642-FE0F","non_qualified":"1F6A3-1F3FB-200D-2642","image":"1f6a3-1f3fb-200d-2642-fe0f.png","sheet_x":34,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6A3-1F3FC-200D-2642-FE0F","non_qualified":"1F6A3-1F3FC-200D-2642","image":"1f6a3-1f3fc-200d-2642-fe0f.png","sheet_x":34,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6A3-1F3FD-200D-2642-FE0F","non_qualified":"1F6A3-1F3FD-200D-2642","image":"1f6a3-1f3fd-200d-2642-fe0f.png","sheet_x":35,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6A3-1F3FE-200D-2642-FE0F","non_qualified":"1F6A3-1F3FE-200D-2642","image":"1f6a3-1f3fe-200d-2642-fe0f.png","sheet_x":35,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6A3-1F3FF-200D-2642-FE0F","non_qualified":"1F6A3-1F3FF-200D-2642","image":"1f6a3-1f3ff-200d-2642-fe0f.png","sheet_x":35,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F6A3","a":"Man Rowing Boat","b":"1F6A3-200D-2642-FE0F","c":"1F6A3-200D-2642","k":[34,49]},"woman-rowing-boat":{"skin_variations":{"1F3FB":{"unified":"1F6A3-1F3FB-200D-2640-FE0F","non_qualified":"1F6A3-1F3FB-200D-2640","image":"1f6a3-1f3fb-200d-2640-fe0f.png","sheet_x":34,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6A3-1F3FC-200D-2640-FE0F","non_qualified":"1F6A3-1F3FC-200D-2640","image":"1f6a3-1f3fc-200d-2640-fe0f.png","sheet_x":34,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6A3-1F3FD-200D-2640-FE0F","non_qualified":"1F6A3-1F3FD-200D-2640","image":"1f6a3-1f3fd-200d-2640-fe0f.png","sheet_x":34,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6A3-1F3FE-200D-2640-FE0F","non_qualified":"1F6A3-1F3FE-200D-2640","image":"1f6a3-1f3fe-200d-2640-fe0f.png","sheet_x":34,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6A3-1F3FF-200D-2640-FE0F","non_qualified":"1F6A3-1F3FF-200D-2640","image":"1f6a3-1f3ff-200d-2640-fe0f.png","sheet_x":34,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Rowing Boat","b":"1F6A3-200D-2640-FE0F","c":"1F6A3-200D-2640","k":[34,43]},"swimmer":{"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB","non_qualified":null,"image":"1f3ca-1f3fb.png","sheet_x":10,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F3CA-1F3FC","non_qualified":null,"image":"1f3ca-1f3fc.png","sheet_x":10,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F3CA-1F3FD","non_qualified":null,"image":"1f3ca-1f3fd.png","sheet_x":10,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F3CA-1F3FE","non_qualified":null,"image":"1f3ca-1f3fe.png","sheet_x":10,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F3CA-1F3FF","non_qualified":null,"image":"1f3ca-1f3ff.png","sheet_x":10,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F3CA-200D-2642-FE0F","a":"Swimmer","b":"1F3CA","k":[10,40]},"man-swimming":{"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB-200D-2642-FE0F","non_qualified":"1F3CA-1F3FB-200D-2642","image":"1f3ca-1f3fb-200d-2642-fe0f.png","sheet_x":10,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CA-1F3FC-200D-2642-FE0F","non_qualified":"1F3CA-1F3FC-200D-2642","image":"1f3ca-1f3fc-200d-2642-fe0f.png","sheet_x":10,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CA-1F3FD-200D-2642-FE0F","non_qualified":"1F3CA-1F3FD-200D-2642","image":"1f3ca-1f3fd-200d-2642-fe0f.png","sheet_x":10,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CA-1F3FE-200D-2642-FE0F","non_qualified":"1F3CA-1F3FE-200D-2642","image":"1f3ca-1f3fe-200d-2642-fe0f.png","sheet_x":10,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CA-1F3FF-200D-2642-FE0F","non_qualified":"1F3CA-1F3FF-200D-2642","image":"1f3ca-1f3ff-200d-2642-fe0f.png","sheet_x":10,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3CA","a":"Man Swimming","b":"1F3CA-200D-2642-FE0F","c":"1F3CA-200D-2642","k":[10,34]},"woman-swimming":{"skin_variations":{"1F3FB":{"unified":"1F3CA-1F3FB-200D-2640-FE0F","non_qualified":"1F3CA-1F3FB-200D-2640","image":"1f3ca-1f3fb-200d-2640-fe0f.png","sheet_x":10,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CA-1F3FC-200D-2640-FE0F","non_qualified":"1F3CA-1F3FC-200D-2640","image":"1f3ca-1f3fc-200d-2640-fe0f.png","sheet_x":10,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CA-1F3FD-200D-2640-FE0F","non_qualified":"1F3CA-1F3FD-200D-2640","image":"1f3ca-1f3fd-200d-2640-fe0f.png","sheet_x":10,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CA-1F3FE-200D-2640-FE0F","non_qualified":"1F3CA-1F3FE-200D-2640","image":"1f3ca-1f3fe-200d-2640-fe0f.png","sheet_x":10,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CA-1F3FF-200D-2640-FE0F","non_qualified":"1F3CA-1F3FF-200D-2640","image":"1f3ca-1f3ff-200d-2640-fe0f.png","sheet_x":10,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Swimming","b":"1F3CA-200D-2640-FE0F","c":"1F3CA-200D-2640","k":[10,28]},"person_with_ball":{"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB","non_qualified":null,"image":"26f9-1f3fb.png","sheet_x":49,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"26F9-1F3FC","non_qualified":null,"image":"26f9-1f3fc.png","sheet_x":49,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"26F9-1F3FD","non_qualified":null,"image":"26f9-1f3fd.png","sheet_x":49,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"26F9-1F3FE","non_qualified":null,"image":"26f9-1f3fe.png","sheet_x":49,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"26F9-1F3FF","non_qualified":null,"image":"26f9-1f3ff.png","sheet_x":49,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"26F9-FE0F-200D-2642-FE0F","a":"Person with Ball","b":"26F9-FE0F","c":"26F9","k":[49,6],"o":5},"man-bouncing-ball":{"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB-200D-2642-FE0F","non_qualified":"26F9-1F3FB-200D-2642","image":"26f9-1f3fb-200d-2642-fe0f.png","sheet_x":49,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"26F9-1F3FC-200D-2642-FE0F","non_qualified":"26F9-1F3FC-200D-2642","image":"26f9-1f3fc-200d-2642-fe0f.png","sheet_x":49,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"26F9-1F3FD-200D-2642-FE0F","non_qualified":"26F9-1F3FD-200D-2642","image":"26f9-1f3fd-200d-2642-fe0f.png","sheet_x":49,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"26F9-1F3FE-200D-2642-FE0F","non_qualified":"26F9-1F3FE-200D-2642","image":"26f9-1f3fe-200d-2642-fe0f.png","sheet_x":49,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"26F9-1F3FF-200D-2642-FE0F","non_qualified":"26F9-1F3FF-200D-2642","image":"26f9-1f3ff-200d-2642-fe0f.png","sheet_x":49,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"26F9-FE0F","a":"Man Bouncing Ball","b":"26F9-FE0F-200D-2642-FE0F","k":[49,0],"o":5},"woman-bouncing-ball":{"skin_variations":{"1F3FB":{"unified":"26F9-1F3FB-200D-2640-FE0F","non_qualified":"26F9-1F3FB-200D-2640","image":"26f9-1f3fb-200d-2640-fe0f.png","sheet_x":48,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"26F9-1F3FC-200D-2640-FE0F","non_qualified":"26F9-1F3FC-200D-2640","image":"26f9-1f3fc-200d-2640-fe0f.png","sheet_x":48,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"26F9-1F3FD-200D-2640-FE0F","non_qualified":"26F9-1F3FD-200D-2640","image":"26f9-1f3fd-200d-2640-fe0f.png","sheet_x":48,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"26F9-1F3FE-200D-2640-FE0F","non_qualified":"26F9-1F3FE-200D-2640","image":"26f9-1f3fe-200d-2640-fe0f.png","sheet_x":48,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"26F9-1F3FF-200D-2640-FE0F","non_qualified":"26F9-1F3FF-200D-2640","image":"26f9-1f3ff-200d-2640-fe0f.png","sheet_x":48,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Bouncing Ball","b":"26F9-FE0F-200D-2640-FE0F","k":[48,46],"o":5},"weight_lifter":{"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB","non_qualified":null,"image":"1f3cb-1f3fb.png","sheet_x":11,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CB-1F3FC","non_qualified":null,"image":"1f3cb-1f3fc.png","sheet_x":11,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CB-1F3FD","non_qualified":null,"image":"1f3cb-1f3fd.png","sheet_x":11,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CB-1F3FE","non_qualified":null,"image":"1f3cb-1f3fe.png","sheet_x":11,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CB-1F3FF","non_qualified":null,"image":"1f3cb-1f3ff.png","sheet_x":11,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoleted_by":"1F3CB-FE0F-200D-2642-FE0F","a":"Weight Lifter","b":"1F3CB-FE0F","c":"1F3CB","k":[11,6],"o":7},"man-lifting-weights":{"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB-200D-2642-FE0F","non_qualified":"1F3CB-1F3FB-200D-2642","image":"1f3cb-1f3fb-200d-2642-fe0f.png","sheet_x":11,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CB-1F3FC-200D-2642-FE0F","non_qualified":"1F3CB-1F3FC-200D-2642","image":"1f3cb-1f3fc-200d-2642-fe0f.png","sheet_x":11,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CB-1F3FD-200D-2642-FE0F","non_qualified":"1F3CB-1F3FD-200D-2642","image":"1f3cb-1f3fd-200d-2642-fe0f.png","sheet_x":11,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CB-1F3FE-200D-2642-FE0F","non_qualified":"1F3CB-1F3FE-200D-2642","image":"1f3cb-1f3fe-200d-2642-fe0f.png","sheet_x":11,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CB-1F3FF-200D-2642-FE0F","non_qualified":"1F3CB-1F3FF-200D-2642","image":"1f3cb-1f3ff-200d-2642-fe0f.png","sheet_x":11,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F3CB-FE0F","a":"Man Lifting Weights","b":"1F3CB-FE0F-200D-2642-FE0F","k":[11,0],"o":7},"woman-lifting-weights":{"skin_variations":{"1F3FB":{"unified":"1F3CB-1F3FB-200D-2640-FE0F","non_qualified":"1F3CB-1F3FB-200D-2640","image":"1f3cb-1f3fb-200d-2640-fe0f.png","sheet_x":10,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F3CB-1F3FC-200D-2640-FE0F","non_qualified":"1F3CB-1F3FC-200D-2640","image":"1f3cb-1f3fc-200d-2640-fe0f.png","sheet_x":10,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F3CB-1F3FD-200D-2640-FE0F","non_qualified":"1F3CB-1F3FD-200D-2640","image":"1f3cb-1f3fd-200d-2640-fe0f.png","sheet_x":10,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F3CB-1F3FE-200D-2640-FE0F","non_qualified":"1F3CB-1F3FE-200D-2640","image":"1f3cb-1f3fe-200d-2640-fe0f.png","sheet_x":10,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F3CB-1F3FF-200D-2640-FE0F","non_qualified":"1F3CB-1F3FF-200D-2640","image":"1f3cb-1f3ff-200d-2640-fe0f.png","sheet_x":10,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Lifting Weights","b":"1F3CB-FE0F-200D-2640-FE0F","k":[10,46],"o":7},"bicyclist":{"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB","non_qualified":null,"image":"1f6b4-1f3fb.png","sheet_x":35,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F6B4-1F3FC","non_qualified":null,"image":"1f6b4-1f3fc.png","sheet_x":35,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F6B4-1F3FD","non_qualified":null,"image":"1f6b4-1f3fd.png","sheet_x":35,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F6B4-1F3FE","non_qualified":null,"image":"1f6b4-1f3fe.png","sheet_x":35,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F6B4-1F3FF","non_qualified":null,"image":"1f6b4-1f3ff.png","sheet_x":35,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F6B4-200D-2642-FE0F","a":"Bicyclist","b":"1F6B4","k":[35,37]},"man-biking":{"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB-200D-2642-FE0F","non_qualified":"1F6B4-1F3FB-200D-2642","image":"1f6b4-1f3fb-200d-2642-fe0f.png","sheet_x":35,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B4-1F3FC-200D-2642-FE0F","non_qualified":"1F6B4-1F3FC-200D-2642","image":"1f6b4-1f3fc-200d-2642-fe0f.png","sheet_x":35,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B4-1F3FD-200D-2642-FE0F","non_qualified":"1F6B4-1F3FD-200D-2642","image":"1f6b4-1f3fd-200d-2642-fe0f.png","sheet_x":35,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B4-1F3FE-200D-2642-FE0F","non_qualified":"1F6B4-1F3FE-200D-2642","image":"1f6b4-1f3fe-200d-2642-fe0f.png","sheet_x":35,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B4-1F3FF-200D-2642-FE0F","non_qualified":"1F6B4-1F3FF-200D-2642","image":"1f6b4-1f3ff-200d-2642-fe0f.png","sheet_x":35,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F6B4","a":"Man Biking","b":"1F6B4-200D-2642-FE0F","c":"1F6B4-200D-2642","k":[35,31]},"woman-biking":{"skin_variations":{"1F3FB":{"unified":"1F6B4-1F3FB-200D-2640-FE0F","non_qualified":"1F6B4-1F3FB-200D-2640","image":"1f6b4-1f3fb-200d-2640-fe0f.png","sheet_x":35,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B4-1F3FC-200D-2640-FE0F","non_qualified":"1F6B4-1F3FC-200D-2640","image":"1f6b4-1f3fc-200d-2640-fe0f.png","sheet_x":35,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B4-1F3FD-200D-2640-FE0F","non_qualified":"1F6B4-1F3FD-200D-2640","image":"1f6b4-1f3fd-200d-2640-fe0f.png","sheet_x":35,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B4-1F3FE-200D-2640-FE0F","non_qualified":"1F6B4-1F3FE-200D-2640","image":"1f6b4-1f3fe-200d-2640-fe0f.png","sheet_x":35,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B4-1F3FF-200D-2640-FE0F","non_qualified":"1F6B4-1F3FF-200D-2640","image":"1f6b4-1f3ff-200d-2640-fe0f.png","sheet_x":35,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Biking","b":"1F6B4-200D-2640-FE0F","c":"1F6B4-200D-2640","k":[35,25]},"mountain_bicyclist":{"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB","non_qualified":null,"image":"1f6b5-1f3fb.png","sheet_x":36,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FC":{"unified":"1F6B5-1F3FC","non_qualified":null,"image":"1f6b5-1f3fc.png","sheet_x":36,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FD":{"unified":"1F6B5-1F3FD","non_qualified":null,"image":"1f6b5-1f3fd.png","sheet_x":36,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FE":{"unified":"1F6B5-1F3FE","non_qualified":null,"image":"1f6b5-1f3fe.png","sheet_x":36,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true},"1F3FF":{"unified":"1F6B5-1F3FF","non_qualified":null,"image":"1f6b5-1f3ff.png","sheet_x":36,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":true}},"obsoleted_by":"1F6B5-200D-2642-FE0F","a":"Mountain Bicyclist","b":"1F6B5","k":[36,3]},"man-mountain-biking":{"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB-200D-2642-FE0F","non_qualified":"1F6B5-1F3FB-200D-2642","image":"1f6b5-1f3fb-200d-2642-fe0f.png","sheet_x":35,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B5-1F3FC-200D-2642-FE0F","non_qualified":"1F6B5-1F3FC-200D-2642","image":"1f6b5-1f3fc-200d-2642-fe0f.png","sheet_x":35,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B5-1F3FD-200D-2642-FE0F","non_qualified":"1F6B5-1F3FD-200D-2642","image":"1f6b5-1f3fd-200d-2642-fe0f.png","sheet_x":36,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B5-1F3FE-200D-2642-FE0F","non_qualified":"1F6B5-1F3FE-200D-2642","image":"1f6b5-1f3fe-200d-2642-fe0f.png","sheet_x":36,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B5-1F3FF-200D-2642-FE0F","non_qualified":"1F6B5-1F3FF-200D-2642","image":"1f6b5-1f3ff-200d-2642-fe0f.png","sheet_x":36,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"obsoletes":"1F6B5","a":"Man Mountain Biking","b":"1F6B5-200D-2642-FE0F","c":"1F6B5-200D-2642","k":[35,49]},"woman-mountain-biking":{"skin_variations":{"1F3FB":{"unified":"1F6B5-1F3FB-200D-2640-FE0F","non_qualified":"1F6B5-1F3FB-200D-2640","image":"1f6b5-1f3fb-200d-2640-fe0f.png","sheet_x":35,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F6B5-1F3FC-200D-2640-FE0F","non_qualified":"1F6B5-1F3FC-200D-2640","image":"1f6b5-1f3fc-200d-2640-fe0f.png","sheet_x":35,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F6B5-1F3FD-200D-2640-FE0F","non_qualified":"1F6B5-1F3FD-200D-2640","image":"1f6b5-1f3fd-200d-2640-fe0f.png","sheet_x":35,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F6B5-1F3FE-200D-2640-FE0F","non_qualified":"1F6B5-1F3FE-200D-2640","image":"1f6b5-1f3fe-200d-2640-fe0f.png","sheet_x":35,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F6B5-1F3FF-200D-2640-FE0F","non_qualified":"1F6B5-1F3FF-200D-2640","image":"1f6b5-1f3ff-200d-2640-fe0f.png","sheet_x":35,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Mountain Biking","b":"1F6B5-200D-2640-FE0F","c":"1F6B5-200D-2640","k":[35,43]},"racing_car":{"a":"Racing Car","b":"1F3CE-FE0F","c":"1F3CE","j":["sports","race","fast","formula","f1"],"k":[11,31],"o":7},"racing_motorcycle":{"a":"Racing Motorcycle","b":"1F3CD-FE0F","c":"1F3CD","k":[11,30],"o":7},"person_doing_cartwheel":{"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB","non_qualified":null,"image":"1f938-1f3fb.png","sheet_x":40,"sheet_y":25,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F938-1F3FC","non_qualified":null,"image":"1f938-1f3fc.png","sheet_x":40,"sheet_y":26,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F938-1F3FD","non_qualified":null,"image":"1f938-1f3fd.png","sheet_x":40,"sheet_y":27,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F938-1F3FE","non_qualified":null,"image":"1f938-1f3fe.png","sheet_x":40,"sheet_y":28,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F938-1F3FF","non_qualified":null,"image":"1f938-1f3ff.png","sheet_x":40,"sheet_y":29,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Person Doing Cartwheel","b":"1F938","k":[40,24],"o":9},"man-cartwheeling":{"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB-200D-2642-FE0F","non_qualified":"1F938-1F3FB-200D-2642","image":"1f938-1f3fb-200d-2642-fe0f.png","sheet_x":40,"sheet_y":19,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F938-1F3FC-200D-2642-FE0F","non_qualified":"1F938-1F3FC-200D-2642","image":"1f938-1f3fc-200d-2642-fe0f.png","sheet_x":40,"sheet_y":20,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F938-1F3FD-200D-2642-FE0F","non_qualified":"1F938-1F3FD-200D-2642","image":"1f938-1f3fd-200d-2642-fe0f.png","sheet_x":40,"sheet_y":21,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F938-1F3FE-200D-2642-FE0F","non_qualified":"1F938-1F3FE-200D-2642","image":"1f938-1f3fe-200d-2642-fe0f.png","sheet_x":40,"sheet_y":22,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F938-1F3FF-200D-2642-FE0F","non_qualified":"1F938-1F3FF-200D-2642","image":"1f938-1f3ff-200d-2642-fe0f.png","sheet_x":40,"sheet_y":23,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Cartwheeling","b":"1F938-200D-2642-FE0F","c":"1F938-200D-2642","k":[40,18],"o":9},"woman-cartwheeling":{"skin_variations":{"1F3FB":{"unified":"1F938-1F3FB-200D-2640-FE0F","non_qualified":"1F938-1F3FB-200D-2640","image":"1f938-1f3fb-200d-2640-fe0f.png","sheet_x":40,"sheet_y":13,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F938-1F3FC-200D-2640-FE0F","non_qualified":"1F938-1F3FC-200D-2640","image":"1f938-1f3fc-200d-2640-fe0f.png","sheet_x":40,"sheet_y":14,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F938-1F3FD-200D-2640-FE0F","non_qualified":"1F938-1F3FD-200D-2640","image":"1f938-1f3fd-200d-2640-fe0f.png","sheet_x":40,"sheet_y":15,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F938-1F3FE-200D-2640-FE0F","non_qualified":"1F938-1F3FE-200D-2640","image":"1f938-1f3fe-200d-2640-fe0f.png","sheet_x":40,"sheet_y":16,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F938-1F3FF-200D-2640-FE0F","non_qualified":"1F938-1F3FF-200D-2640","image":"1f938-1f3ff-200d-2640-fe0f.png","sheet_x":40,"sheet_y":17,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Cartwheeling","b":"1F938-200D-2640-FE0F","c":"1F938-200D-2640","k":[40,12],"o":9},"wrestlers":{"a":"Wrestlers","b":"1F93C","k":[40,51],"o":9},"man-wrestling":{"a":"Man Wrestling","b":"1F93C-200D-2642-FE0F","c":"1F93C-200D-2642","k":[40,50],"o":9},"woman-wrestling":{"a":"Woman Wrestling","b":"1F93C-200D-2640-FE0F","c":"1F93C-200D-2640","k":[40,49],"o":9},"water_polo":{"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB","non_qualified":null,"image":"1f93d-1f3fb.png","sheet_x":41,"sheet_y":13,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93D-1F3FC","non_qualified":null,"image":"1f93d-1f3fc.png","sheet_x":41,"sheet_y":14,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93D-1F3FD","non_qualified":null,"image":"1f93d-1f3fd.png","sheet_x":41,"sheet_y":15,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93D-1F3FE","non_qualified":null,"image":"1f93d-1f3fe.png","sheet_x":41,"sheet_y":16,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93D-1F3FF","non_qualified":null,"image":"1f93d-1f3ff.png","sheet_x":41,"sheet_y":17,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Water Polo","b":"1F93D","k":[41,12],"o":9},"man-playing-water-polo":{"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB-200D-2642-FE0F","non_qualified":"1F93D-1F3FB-200D-2642","image":"1f93d-1f3fb-200d-2642-fe0f.png","sheet_x":41,"sheet_y":7,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93D-1F3FC-200D-2642-FE0F","non_qualified":"1F93D-1F3FC-200D-2642","image":"1f93d-1f3fc-200d-2642-fe0f.png","sheet_x":41,"sheet_y":8,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93D-1F3FD-200D-2642-FE0F","non_qualified":"1F93D-1F3FD-200D-2642","image":"1f93d-1f3fd-200d-2642-fe0f.png","sheet_x":41,"sheet_y":9,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93D-1F3FE-200D-2642-FE0F","non_qualified":"1F93D-1F3FE-200D-2642","image":"1f93d-1f3fe-200d-2642-fe0f.png","sheet_x":41,"sheet_y":10,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93D-1F3FF-200D-2642-FE0F","non_qualified":"1F93D-1F3FF-200D-2642","image":"1f93d-1f3ff-200d-2642-fe0f.png","sheet_x":41,"sheet_y":11,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Playing Water Polo","b":"1F93D-200D-2642-FE0F","c":"1F93D-200D-2642","k":[41,6],"o":9},"woman-playing-water-polo":{"skin_variations":{"1F3FB":{"unified":"1F93D-1F3FB-200D-2640-FE0F","non_qualified":"1F93D-1F3FB-200D-2640","image":"1f93d-1f3fb-200d-2640-fe0f.png","sheet_x":41,"sheet_y":1,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93D-1F3FC-200D-2640-FE0F","non_qualified":"1F93D-1F3FC-200D-2640","image":"1f93d-1f3fc-200d-2640-fe0f.png","sheet_x":41,"sheet_y":2,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93D-1F3FD-200D-2640-FE0F","non_qualified":"1F93D-1F3FD-200D-2640","image":"1f93d-1f3fd-200d-2640-fe0f.png","sheet_x":41,"sheet_y":3,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93D-1F3FE-200D-2640-FE0F","non_qualified":"1F93D-1F3FE-200D-2640","image":"1f93d-1f3fe-200d-2640-fe0f.png","sheet_x":41,"sheet_y":4,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93D-1F3FF-200D-2640-FE0F","non_qualified":"1F93D-1F3FF-200D-2640","image":"1f93d-1f3ff-200d-2640-fe0f.png","sheet_x":41,"sheet_y":5,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Playing Water Polo","b":"1F93D-200D-2640-FE0F","c":"1F93D-200D-2640","k":[41,0],"o":9},"handball":{"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB","non_qualified":null,"image":"1f93e-1f3fb.png","sheet_x":41,"sheet_y":31,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93E-1F3FC","non_qualified":null,"image":"1f93e-1f3fc.png","sheet_x":41,"sheet_y":32,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93E-1F3FD","non_qualified":null,"image":"1f93e-1f3fd.png","sheet_x":41,"sheet_y":33,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93E-1F3FE","non_qualified":null,"image":"1f93e-1f3fe.png","sheet_x":41,"sheet_y":34,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93E-1F3FF","non_qualified":null,"image":"1f93e-1f3ff.png","sheet_x":41,"sheet_y":35,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Handball","b":"1F93E","k":[41,30],"o":9},"man-playing-handball":{"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB-200D-2642-FE0F","non_qualified":"1F93E-1F3FB-200D-2642","image":"1f93e-1f3fb-200d-2642-fe0f.png","sheet_x":41,"sheet_y":25,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93E-1F3FC-200D-2642-FE0F","non_qualified":"1F93E-1F3FC-200D-2642","image":"1f93e-1f3fc-200d-2642-fe0f.png","sheet_x":41,"sheet_y":26,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93E-1F3FD-200D-2642-FE0F","non_qualified":"1F93E-1F3FD-200D-2642","image":"1f93e-1f3fd-200d-2642-fe0f.png","sheet_x":41,"sheet_y":27,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93E-1F3FE-200D-2642-FE0F","non_qualified":"1F93E-1F3FE-200D-2642","image":"1f93e-1f3fe-200d-2642-fe0f.png","sheet_x":41,"sheet_y":28,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93E-1F3FF-200D-2642-FE0F","non_qualified":"1F93E-1F3FF-200D-2642","image":"1f93e-1f3ff-200d-2642-fe0f.png","sheet_x":41,"sheet_y":29,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Playing Handball","b":"1F93E-200D-2642-FE0F","c":"1F93E-200D-2642","k":[41,24],"o":9},"woman-playing-handball":{"skin_variations":{"1F3FB":{"unified":"1F93E-1F3FB-200D-2640-FE0F","non_qualified":"1F93E-1F3FB-200D-2640","image":"1f93e-1f3fb-200d-2640-fe0f.png","sheet_x":41,"sheet_y":19,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F93E-1F3FC-200D-2640-FE0F","non_qualified":"1F93E-1F3FC-200D-2640","image":"1f93e-1f3fc-200d-2640-fe0f.png","sheet_x":41,"sheet_y":20,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F93E-1F3FD-200D-2640-FE0F","non_qualified":"1F93E-1F3FD-200D-2640","image":"1f93e-1f3fd-200d-2640-fe0f.png","sheet_x":41,"sheet_y":21,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F93E-1F3FE-200D-2640-FE0F","non_qualified":"1F93E-1F3FE-200D-2640","image":"1f93e-1f3fe-200d-2640-fe0f.png","sheet_x":41,"sheet_y":22,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F93E-1F3FF-200D-2640-FE0F","non_qualified":"1F93E-1F3FF-200D-2640","image":"1f93e-1f3ff-200d-2640-fe0f.png","sheet_x":41,"sheet_y":23,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Playing Handball","b":"1F93E-200D-2640-FE0F","c":"1F93E-200D-2640","k":[41,18],"o":9},"juggling":{"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB","non_qualified":null,"image":"1f939-1f3fb.png","sheet_x":40,"sheet_y":43,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F939-1F3FC","non_qualified":null,"image":"1f939-1f3fc.png","sheet_x":40,"sheet_y":44,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F939-1F3FD","non_qualified":null,"image":"1f939-1f3fd.png","sheet_x":40,"sheet_y":45,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F939-1F3FE","non_qualified":null,"image":"1f939-1f3fe.png","sheet_x":40,"sheet_y":46,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F939-1F3FF","non_qualified":null,"image":"1f939-1f3ff.png","sheet_x":40,"sheet_y":47,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Juggling","b":"1F939","k":[40,42],"o":9},"man-juggling":{"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB-200D-2642-FE0F","non_qualified":"1F939-1F3FB-200D-2642","image":"1f939-1f3fb-200d-2642-fe0f.png","sheet_x":40,"sheet_y":37,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F939-1F3FC-200D-2642-FE0F","non_qualified":"1F939-1F3FC-200D-2642","image":"1f939-1f3fc-200d-2642-fe0f.png","sheet_x":40,"sheet_y":38,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F939-1F3FD-200D-2642-FE0F","non_qualified":"1F939-1F3FD-200D-2642","image":"1f939-1f3fd-200d-2642-fe0f.png","sheet_x":40,"sheet_y":39,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F939-1F3FE-200D-2642-FE0F","non_qualified":"1F939-1F3FE-200D-2642","image":"1f939-1f3fe-200d-2642-fe0f.png","sheet_x":40,"sheet_y":40,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F939-1F3FF-200D-2642-FE0F","non_qualified":"1F939-1F3FF-200D-2642","image":"1f939-1f3ff-200d-2642-fe0f.png","sheet_x":40,"sheet_y":41,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Man Juggling","b":"1F939-200D-2642-FE0F","c":"1F939-200D-2642","k":[40,36],"o":9},"woman-juggling":{"skin_variations":{"1F3FB":{"unified":"1F939-1F3FB-200D-2640-FE0F","non_qualified":"1F939-1F3FB-200D-2640","image":"1f939-1f3fb-200d-2640-fe0f.png","sheet_x":40,"sheet_y":31,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FC":{"unified":"1F939-1F3FC-200D-2640-FE0F","non_qualified":"1F939-1F3FC-200D-2640","image":"1f939-1f3fc-200d-2640-fe0f.png","sheet_x":40,"sheet_y":32,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FD":{"unified":"1F939-1F3FD-200D-2640-FE0F","non_qualified":"1F939-1F3FD-200D-2640","image":"1f939-1f3fd-200d-2640-fe0f.png","sheet_x":40,"sheet_y":33,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FE":{"unified":"1F939-1F3FE-200D-2640-FE0F","non_qualified":"1F939-1F3FE-200D-2640","image":"1f939-1f3fe-200d-2640-fe0f.png","sheet_x":40,"sheet_y":34,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false},"1F3FF":{"unified":"1F939-1F3FF-200D-2640-FE0F","non_qualified":"1F939-1F3FF-200D-2640","image":"1f939-1f3ff-200d-2640-fe0f.png","sheet_x":40,"sheet_y":35,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":false,"has_img_messenger":false}},"a":"Woman Juggling","b":"1F939-200D-2640-FE0F","c":"1F939-200D-2640","k":[40,30],"o":9},"couple":{"a":"Man and Woman Holding Hands","b":"1F46B","j":["pair","people","human","love","date","dating","like","affection","valentines","marriage"],"k":[20,30],"n":["man_and_woman_holding_hands"]},"two_men_holding_hands":{"a":"Two Men Holding Hands","b":"1F46C","j":["pair","couple","love","like","bromance","friendship","people","human"],"k":[20,31]},"two_women_holding_hands":{"a":"Two Women Holding Hands","b":"1F46D","j":["pair","friendship","couple","love","like","female","people","human"],"k":[20,32]},"couplekiss":{"obsoleted_by":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","a":"Kiss","b":"1F48F","k":[24,41]},"woman-kiss-man":{"obsoletes":"1F48F","a":"Woman Kiss Man","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F468","c":"1F469-200D-2764-200D-1F48B-200D-1F468","k":[20,21]},"man-kiss-man":{"a":"Man Kiss Man","b":"1F468-200D-2764-FE0F-200D-1F48B-200D-1F468","c":"1F468-200D-2764-200D-1F48B-200D-1F468","k":[18,10]},"woman-kiss-woman":{"a":"Woman Kiss Woman","b":"1F469-200D-2764-FE0F-200D-1F48B-200D-1F469","c":"1F469-200D-2764-200D-1F48B-200D-1F469","k":[20,22]},"couple_with_heart":{"obsoleted_by":"1F469-200D-2764-FE0F-200D-1F468","a":"Couple with Heart","b":"1F491","k":[24,43]},"woman-heart-man":{"obsoletes":"1F491","a":"Woman Heart Man","b":"1F469-200D-2764-FE0F-200D-1F468","c":"1F469-200D-2764-200D-1F468","k":[20,19]},"man-heart-man":{"a":"Man Heart Man","b":"1F468-200D-2764-FE0F-200D-1F468","c":"1F468-200D-2764-200D-1F468","k":[18,9]},"woman-heart-woman":{"a":"Woman Heart Woman","b":"1F469-200D-2764-FE0F-200D-1F469","c":"1F469-200D-2764-200D-1F469","k":[20,20]},"family":{"obsoleted_by":"1F468-200D-1F469-200D-1F466","a":"Family","b":"1F46A","k":[20,29],"n":["man-woman-boy"]},"man-woman-boy":{"obsoletes":"1F46A","a":"Man Woman Boy","b":"1F468-200D-1F469-200D-1F466","k":[17,2],"n":["family"]},"man-woman-girl":{"a":"Man Woman Girl","b":"1F468-200D-1F469-200D-1F467","k":[17,4]},"man-woman-girl-boy":{"a":"Man Woman Girl Boy","b":"1F468-200D-1F469-200D-1F467-200D-1F466","k":[17,5]},"man-woman-boy-boy":{"a":"Man Woman Boy Boy","b":"1F468-200D-1F469-200D-1F466-200D-1F466","k":[17,3]},"man-woman-girl-girl":{"a":"Man Woman Girl Girl","b":"1F468-200D-1F469-200D-1F467-200D-1F467","k":[17,6]},"man-man-boy":{"a":"Man Man Boy","b":"1F468-200D-1F468-200D-1F466","k":[16,49]},"man-man-girl":{"a":"Man Man Girl","b":"1F468-200D-1F468-200D-1F467","k":[16,51]},"man-man-girl-boy":{"a":"Man Man Girl Boy","b":"1F468-200D-1F468-200D-1F467-200D-1F466","k":[17,0]},"man-man-boy-boy":{"a":"Man Man Boy Boy","b":"1F468-200D-1F468-200D-1F466-200D-1F466","k":[16,50]},"man-man-girl-girl":{"a":"Man Man Girl Girl","b":"1F468-200D-1F468-200D-1F467-200D-1F467","k":[17,1]},"woman-woman-boy":{"a":"Woman Woman Boy","b":"1F469-200D-1F469-200D-1F466","k":[19,12]},"woman-woman-girl":{"a":"Woman Woman Girl","b":"1F469-200D-1F469-200D-1F467","k":[19,14]},"woman-woman-girl-boy":{"a":"Woman Woman Girl Boy","b":"1F469-200D-1F469-200D-1F467-200D-1F466","k":[19,15]},"woman-woman-boy-boy":{"a":"Woman Woman Boy Boy","b":"1F469-200D-1F469-200D-1F466-200D-1F466","k":[19,13]},"woman-woman-girl-girl":{"a":"Woman Woman Girl Girl","b":"1F469-200D-1F469-200D-1F467-200D-1F467","k":[19,16]},"man-boy":{"a":"Man Boy","b":"1F468-200D-1F466","k":[16,45]},"man-boy-boy":{"a":"Man Boy Boy","b":"1F468-200D-1F466-200D-1F466","k":[16,44]},"man-girl":{"a":"Man Girl","b":"1F468-200D-1F467","k":[16,48]},"man-girl-boy":{"a":"Man Girl Boy","b":"1F468-200D-1F467-200D-1F466","k":[16,46]},"man-girl-girl":{"a":"Man Girl Girl","b":"1F468-200D-1F467-200D-1F467","k":[16,47]},"woman-boy":{"a":"Woman Boy","b":"1F469-200D-1F466","k":[19,8]},"woman-boy-boy":{"a":"Woman Boy Boy","b":"1F469-200D-1F466-200D-1F466","k":[19,7]},"woman-girl":{"a":"Woman Girl","b":"1F469-200D-1F467","k":[19,11]},"woman-girl-boy":{"a":"Woman Girl Boy","b":"1F469-200D-1F467-200D-1F466","k":[19,9]},"woman-girl-girl":{"a":"Woman Girl Girl","b":"1F469-200D-1F467-200D-1F467","k":[19,10]},"selfie":{"skin_variations":{"1F3FB":{"unified":"1F933-1F3FB","non_qualified":null,"image":"1f933-1f3fb.png","sheet_x":39,"sheet_y":23,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F933-1F3FC","non_qualified":null,"image":"1f933-1f3fc.png","sheet_x":39,"sheet_y":24,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F933-1F3FD","non_qualified":null,"image":"1f933-1f3fd.png","sheet_x":39,"sheet_y":25,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F933-1F3FE","non_qualified":null,"image":"1f933-1f3fe.png","sheet_x":39,"sheet_y":26,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F933-1F3FF","non_qualified":null,"image":"1f933-1f3ff.png","sheet_x":39,"sheet_y":27,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Selfie","b":"1F933","j":["camera","phone"],"k":[39,22],"o":9},"muscle":{"skin_variations":{"1F3FB":{"unified":"1F4AA-1F3FB","non_qualified":null,"image":"1f4aa-1f3fb.png","sheet_x":25,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F4AA-1F3FC","non_qualified":null,"image":"1f4aa-1f3fc.png","sheet_x":25,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F4AA-1F3FD","non_qualified":null,"image":"1f4aa-1f3fd.png","sheet_x":25,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F4AA-1F3FE","non_qualified":null,"image":"1f4aa-1f3fe.png","sheet_x":25,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F4AA-1F3FF","non_qualified":null,"image":"1f4aa-1f3ff.png","sheet_x":25,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Flexed Biceps","b":"1F4AA","j":["arm","flex","hand","summer","strong","biceps"],"k":[25,16]},"point_left":{"skin_variations":{"1F3FB":{"unified":"1F448-1F3FB","non_qualified":null,"image":"1f448-1f3fb.png","sheet_x":14,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F448-1F3FC","non_qualified":null,"image":"1f448-1f3fc.png","sheet_x":14,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F448-1F3FD","non_qualified":null,"image":"1f448-1f3fd.png","sheet_x":14,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F448-1F3FE","non_qualified":null,"image":"1f448-1f3fe.png","sheet_x":14,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F448-1F3FF","non_qualified":null,"image":"1f448-1f3ff.png","sheet_x":14,"sheet_y":24,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"White Left Pointing Backhand Index","b":"1F448","j":["direction","fingers","hand","left"],"k":[14,19]},"point_right":{"skin_variations":{"1F3FB":{"unified":"1F449-1F3FB","non_qualified":null,"image":"1f449-1f3fb.png","sheet_x":14,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F449-1F3FC","non_qualified":null,"image":"1f449-1f3fc.png","sheet_x":14,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F449-1F3FD","non_qualified":null,"image":"1f449-1f3fd.png","sheet_x":14,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F449-1F3FE","non_qualified":null,"image":"1f449-1f3fe.png","sheet_x":14,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F449-1F3FF","non_qualified":null,"image":"1f449-1f3ff.png","sheet_x":14,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"White Right Pointing Backhand Index","b":"1F449","j":["fingers","hand","direction","right"],"k":[14,25]},"point_up":{"skin_variations":{"1F3FB":{"unified":"261D-1F3FB","non_qualified":null,"image":"261d-1f3fb.png","sheet_x":47,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"261D-1F3FC","non_qualified":null,"image":"261d-1f3fc.png","sheet_x":47,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"261D-1F3FD","non_qualified":null,"image":"261d-1f3fd.png","sheet_x":47,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"261D-1F3FE","non_qualified":null,"image":"261d-1f3fe.png","sheet_x":47,"sheet_y":30,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"261D-1F3FF","non_qualified":null,"image":"261d-1f3ff.png","sheet_x":47,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"White Up Pointing Index","b":"261D-FE0F","c":"261D","j":["hand","fingers","direction","up"],"k":[47,26],"o":1},"point_up_2":{"skin_variations":{"1F3FB":{"unified":"1F446-1F3FB","non_qualified":null,"image":"1f446-1f3fb.png","sheet_x":14,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F446-1F3FC","non_qualified":null,"image":"1f446-1f3fc.png","sheet_x":14,"sheet_y":9,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F446-1F3FD","non_qualified":null,"image":"1f446-1f3fd.png","sheet_x":14,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F446-1F3FE","non_qualified":null,"image":"1f446-1f3fe.png","sheet_x":14,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F446-1F3FF","non_qualified":null,"image":"1f446-1f3ff.png","sheet_x":14,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"White Up Pointing Backhand Index","b":"1F446","j":["fingers","hand","direction","up"],"k":[14,7]},"middle_finger":{"skin_variations":{"1F3FB":{"unified":"1F595-1F3FB","non_qualified":null,"image":"1f595-1f3fb.png","sheet_x":29,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F595-1F3FC","non_qualified":null,"image":"1f595-1f3fc.png","sheet_x":29,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F595-1F3FD","non_qualified":null,"image":"1f595-1f3fd.png","sheet_x":29,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F595-1F3FE","non_qualified":null,"image":"1f595-1f3fe.png","sheet_x":29,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F595-1F3FF","non_qualified":null,"image":"1f595-1f3ff.png","sheet_x":29,"sheet_y":43,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Reversed Hand with Middle Finger Extended","b":"1F595","k":[29,38],"n":["reversed_hand_with_middle_finger_extended"],"o":7},"point_down":{"skin_variations":{"1F3FB":{"unified":"1F447-1F3FB","non_qualified":null,"image":"1f447-1f3fb.png","sheet_x":14,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F447-1F3FC","non_qualified":null,"image":"1f447-1f3fc.png","sheet_x":14,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F447-1F3FD","non_qualified":null,"image":"1f447-1f3fd.png","sheet_x":14,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F447-1F3FE","non_qualified":null,"image":"1f447-1f3fe.png","sheet_x":14,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F447-1F3FF","non_qualified":null,"image":"1f447-1f3ff.png","sheet_x":14,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"White Down Pointing Backhand Index","b":"1F447","j":["fingers","hand","direction","down"],"k":[14,13]},"v":{"skin_variations":{"1F3FB":{"unified":"270C-1F3FB","non_qualified":null,"image":"270c-1f3fb.png","sheet_x":49,"sheet_y":31,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"270C-1F3FC","non_qualified":null,"image":"270c-1f3fc.png","sheet_x":49,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"270C-1F3FD","non_qualified":null,"image":"270c-1f3fd.png","sheet_x":49,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"270C-1F3FE","non_qualified":null,"image":"270c-1f3fe.png","sheet_x":49,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"270C-1F3FF","non_qualified":null,"image":"270c-1f3ff.png","sheet_x":49,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Victory Hand","b":"270C-FE0F","c":"270C","j":["fingers","ohyeah","hand","peace","victory","two"],"k":[49,30],"o":1},"crossed_fingers":{"skin_variations":{"1F3FB":{"unified":"1F91E-1F3FB","non_qualified":null,"image":"1f91e-1f3fb.png","sheet_x":38,"sheet_y":12,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91E-1F3FC","non_qualified":null,"image":"1f91e-1f3fc.png","sheet_x":38,"sheet_y":13,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91E-1F3FD","non_qualified":null,"image":"1f91e-1f3fd.png","sheet_x":38,"sheet_y":14,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91E-1F3FE","non_qualified":null,"image":"1f91e-1f3fe.png","sheet_x":38,"sheet_y":15,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91E-1F3FF","non_qualified":null,"image":"1f91e-1f3ff.png","sheet_x":38,"sheet_y":16,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Hand with Index and Middle Fingers Crossed","b":"1F91E","j":["good","lucky"],"k":[38,11],"n":["hand_with_index_and_middle_fingers_crossed"],"o":9},"spock-hand":{"skin_variations":{"1F3FB":{"unified":"1F596-1F3FB","non_qualified":null,"image":"1f596-1f3fb.png","sheet_x":29,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F596-1F3FC","non_qualified":null,"image":"1f596-1f3fc.png","sheet_x":29,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F596-1F3FD","non_qualified":null,"image":"1f596-1f3fd.png","sheet_x":29,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F596-1F3FE","non_qualified":null,"image":"1f596-1f3fe.png","sheet_x":29,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F596-1F3FF","non_qualified":null,"image":"1f596-1f3ff.png","sheet_x":29,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Raised Hand with Part Between Middle and Ring Fingers","b":"1F596","k":[29,44],"o":7},"the_horns":{"skin_variations":{"1F3FB":{"unified":"1F918-1F3FB","non_qualified":null,"image":"1f918-1f3fb.png","sheet_x":37,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F918-1F3FC","non_qualified":null,"image":"1f918-1f3fc.png","sheet_x":37,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F918-1F3FD","non_qualified":null,"image":"1f918-1f3fd.png","sheet_x":37,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F918-1F3FE","non_qualified":null,"image":"1f918-1f3fe.png","sheet_x":37,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F918-1F3FF","non_qualified":null,"image":"1f918-1f3ff.png","sheet_x":37,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Sign of the Horns","b":"1F918","k":[37,32],"n":["sign_of_the_horns"],"o":8},"call_me_hand":{"skin_variations":{"1F3FB":{"unified":"1F919-1F3FB","non_qualified":null,"image":"1f919-1f3fb.png","sheet_x":37,"sheet_y":39,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F919-1F3FC","non_qualified":null,"image":"1f919-1f3fc.png","sheet_x":37,"sheet_y":40,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F919-1F3FD","non_qualified":null,"image":"1f919-1f3fd.png","sheet_x":37,"sheet_y":41,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F919-1F3FE","non_qualified":null,"image":"1f919-1f3fe.png","sheet_x":37,"sheet_y":42,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F919-1F3FF","non_qualified":null,"image":"1f919-1f3ff.png","sheet_x":37,"sheet_y":43,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Call Me Hand","b":"1F919","j":["hands","gesture"],"k":[37,38],"o":9},"raised_hand_with_fingers_splayed":{"skin_variations":{"1F3FB":{"unified":"1F590-1F3FB","non_qualified":null,"image":"1f590-1f3fb.png","sheet_x":29,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F590-1F3FC","non_qualified":null,"image":"1f590-1f3fc.png","sheet_x":29,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F590-1F3FD","non_qualified":null,"image":"1f590-1f3fd.png","sheet_x":29,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F590-1F3FE","non_qualified":null,"image":"1f590-1f3fe.png","sheet_x":29,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F590-1F3FF","non_qualified":null,"image":"1f590-1f3ff.png","sheet_x":29,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Raised Hand with Fingers Splayed","b":"1F590-FE0F","c":"1F590","j":["hand","fingers","palm"],"k":[29,32],"o":7},"hand":{"skin_variations":{"1F3FB":{"unified":"270B-1F3FB","non_qualified":null,"image":"270b-1f3fb.png","sheet_x":49,"sheet_y":25,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"270B-1F3FC","non_qualified":null,"image":"270b-1f3fc.png","sheet_x":49,"sheet_y":26,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"270B-1F3FD","non_qualified":null,"image":"270b-1f3fd.png","sheet_x":49,"sheet_y":27,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"270B-1F3FE","non_qualified":null,"image":"270b-1f3fe.png","sheet_x":49,"sheet_y":28,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"270B-1F3FF","non_qualified":null,"image":"270b-1f3ff.png","sheet_x":49,"sheet_y":29,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Raised Hand","b":"270B","k":[49,24],"n":["raised_hand"]},"ok_hand":{"skin_variations":{"1F3FB":{"unified":"1F44C-1F3FB","non_qualified":null,"image":"1f44c-1f3fb.png","sheet_x":14,"sheet_y":44,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44C-1F3FC","non_qualified":null,"image":"1f44c-1f3fc.png","sheet_x":14,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44C-1F3FD","non_qualified":null,"image":"1f44c-1f3fd.png","sheet_x":14,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44C-1F3FE","non_qualified":null,"image":"1f44c-1f3fe.png","sheet_x":14,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44C-1F3FF","non_qualified":null,"image":"1f44c-1f3ff.png","sheet_x":14,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Ok Hand Sign","b":"1F44C","j":["fingers","limbs","perfect","ok","okay"],"k":[14,43]},"+1":{"skin_variations":{"1F3FB":{"unified":"1F44D-1F3FB","non_qualified":null,"image":"1f44d-1f3fb.png","sheet_x":14,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44D-1F3FC","non_qualified":null,"image":"1f44d-1f3fc.png","sheet_x":14,"sheet_y":51,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44D-1F3FD","non_qualified":null,"image":"1f44d-1f3fd.png","sheet_x":15,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44D-1F3FE","non_qualified":null,"image":"1f44d-1f3fe.png","sheet_x":15,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44D-1F3FF","non_qualified":null,"image":"1f44d-1f3ff.png","sheet_x":15,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Thumbs Up Sign","b":"1F44D","j":["thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],"k":[14,49],"n":["thumbsup"]},"-1":{"skin_variations":{"1F3FB":{"unified":"1F44E-1F3FB","non_qualified":null,"image":"1f44e-1f3fb.png","sheet_x":15,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44E-1F3FC","non_qualified":null,"image":"1f44e-1f3fc.png","sheet_x":15,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44E-1F3FD","non_qualified":null,"image":"1f44e-1f3fd.png","sheet_x":15,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44E-1F3FE","non_qualified":null,"image":"1f44e-1f3fe.png","sheet_x":15,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44E-1F3FF","non_qualified":null,"image":"1f44e-1f3ff.png","sheet_x":15,"sheet_y":8,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Thumbs Down Sign","b":"1F44E","j":["thumbsdown","no","dislike","hand"],"k":[15,3],"n":["thumbsdown"]},"fist":{"skin_variations":{"1F3FB":{"unified":"270A-1F3FB","non_qualified":null,"image":"270a-1f3fb.png","sheet_x":49,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"270A-1F3FC","non_qualified":null,"image":"270a-1f3fc.png","sheet_x":49,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"270A-1F3FD","non_qualified":null,"image":"270a-1f3fd.png","sheet_x":49,"sheet_y":21,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"270A-1F3FE","non_qualified":null,"image":"270a-1f3fe.png","sheet_x":49,"sheet_y":22,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"270A-1F3FF","non_qualified":null,"image":"270a-1f3ff.png","sheet_x":49,"sheet_y":23,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Raised Fist","b":"270A","j":["fingers","hand","grasp"],"k":[49,18]},"facepunch":{"skin_variations":{"1F3FB":{"unified":"1F44A-1F3FB","non_qualified":null,"image":"1f44a-1f3fb.png","sheet_x":14,"sheet_y":32,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44A-1F3FC","non_qualified":null,"image":"1f44a-1f3fc.png","sheet_x":14,"sheet_y":33,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44A-1F3FD","non_qualified":null,"image":"1f44a-1f3fd.png","sheet_x":14,"sheet_y":34,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44A-1F3FE","non_qualified":null,"image":"1f44a-1f3fe.png","sheet_x":14,"sheet_y":35,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44A-1F3FF","non_qualified":null,"image":"1f44a-1f3ff.png","sheet_x":14,"sheet_y":36,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Fisted Hand Sign","b":"1F44A","j":["angry","violence","fist","hit","attack","hand"],"k":[14,31],"n":["punch"]},"left-facing_fist":{"skin_variations":{"1F3FB":{"unified":"1F91B-1F3FB","non_qualified":null,"image":"1f91b-1f3fb.png","sheet_x":37,"sheet_y":51,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91B-1F3FC","non_qualified":null,"image":"1f91b-1f3fc.png","sheet_x":38,"sheet_y":0,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91B-1F3FD","non_qualified":null,"image":"1f91b-1f3fd.png","sheet_x":38,"sheet_y":1,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91B-1F3FE","non_qualified":null,"image":"1f91b-1f3fe.png","sheet_x":38,"sheet_y":2,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91B-1F3FF","non_qualified":null,"image":"1f91b-1f3ff.png","sheet_x":38,"sheet_y":3,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Left-Facing Fist","b":"1F91B","k":[37,50],"o":9},"right-facing_fist":{"skin_variations":{"1F3FB":{"unified":"1F91C-1F3FB","non_qualified":null,"image":"1f91c-1f3fb.png","sheet_x":38,"sheet_y":5,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91C-1F3FC","non_qualified":null,"image":"1f91c-1f3fc.png","sheet_x":38,"sheet_y":6,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91C-1F3FD","non_qualified":null,"image":"1f91c-1f3fd.png","sheet_x":38,"sheet_y":7,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91C-1F3FE","non_qualified":null,"image":"1f91c-1f3fe.png","sheet_x":38,"sheet_y":8,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91C-1F3FF","non_qualified":null,"image":"1f91c-1f3ff.png","sheet_x":38,"sheet_y":9,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Right-Facing Fist","b":"1F91C","k":[38,4],"o":9},"raised_back_of_hand":{"skin_variations":{"1F3FB":{"unified":"1F91A-1F3FB","non_qualified":null,"image":"1f91a-1f3fb.png","sheet_x":37,"sheet_y":45,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91A-1F3FC","non_qualified":null,"image":"1f91a-1f3fc.png","sheet_x":37,"sheet_y":46,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91A-1F3FD","non_qualified":null,"image":"1f91a-1f3fd.png","sheet_x":37,"sheet_y":47,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91A-1F3FE","non_qualified":null,"image":"1f91a-1f3fe.png","sheet_x":37,"sheet_y":48,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91A-1F3FF","non_qualified":null,"image":"1f91a-1f3ff.png","sheet_x":37,"sheet_y":49,"added_in":"9.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Raised Back of Hand","b":"1F91A","j":["fingers","raised","backhand"],"k":[37,44],"o":9},"wave":{"skin_variations":{"1F3FB":{"unified":"1F44B-1F3FB","non_qualified":null,"image":"1f44b-1f3fb.png","sheet_x":14,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44B-1F3FC","non_qualified":null,"image":"1f44b-1f3fc.png","sheet_x":14,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44B-1F3FD","non_qualified":null,"image":"1f44b-1f3fd.png","sheet_x":14,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44B-1F3FE","non_qualified":null,"image":"1f44b-1f3fe.png","sheet_x":14,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44B-1F3FF","non_qualified":null,"image":"1f44b-1f3ff.png","sheet_x":14,"sheet_y":42,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Waving Hand Sign","b":"1F44B","j":["hands","gesture","goodbye","solong","farewell","hello","hi","palm"],"k":[14,37]},"i_love_you_hand_sign":{"skin_variations":{"1F3FB":{"unified":"1F91F-1F3FB","non_qualified":null,"image":"1f91f-1f3fb.png","sheet_x":38,"sheet_y":18,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F91F-1F3FC","non_qualified":null,"image":"1f91f-1f3fc.png","sheet_x":38,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F91F-1F3FD","non_qualified":null,"image":"1f91f-1f3fd.png","sheet_x":38,"sheet_y":20,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F91F-1F3FE","non_qualified":null,"image":"1f91f-1f3fe.png","sheet_x":38,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F91F-1F3FF","non_qualified":null,"image":"1f91f-1f3ff.png","sheet_x":38,"sheet_y":22,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"I Love You Hand Sign","b":"1F91F","k":[38,17],"o":10},"writing_hand":{"skin_variations":{"1F3FB":{"unified":"270D-1F3FB","non_qualified":null,"image":"270d-1f3fb.png","sheet_x":49,"sheet_y":37,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"270D-1F3FC","non_qualified":null,"image":"270d-1f3fc.png","sheet_x":49,"sheet_y":38,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"270D-1F3FD","non_qualified":null,"image":"270d-1f3fd.png","sheet_x":49,"sheet_y":39,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"270D-1F3FE","non_qualified":null,"image":"270d-1f3fe.png","sheet_x":49,"sheet_y":40,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"270D-1F3FF","non_qualified":null,"image":"270d-1f3ff.png","sheet_x":49,"sheet_y":41,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Writing Hand","b":"270D-FE0F","c":"270D","j":["lower_left_ballpoint_pen","stationery","write","compose"],"k":[49,36],"o":1},"clap":{"skin_variations":{"1F3FB":{"unified":"1F44F-1F3FB","non_qualified":null,"image":"1f44f-1f3fb.png","sheet_x":15,"sheet_y":10,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F44F-1F3FC","non_qualified":null,"image":"1f44f-1f3fc.png","sheet_x":15,"sheet_y":11,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F44F-1F3FD","non_qualified":null,"image":"1f44f-1f3fd.png","sheet_x":15,"sheet_y":12,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F44F-1F3FE","non_qualified":null,"image":"1f44f-1f3fe.png","sheet_x":15,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F44F-1F3FF","non_qualified":null,"image":"1f44f-1f3ff.png","sheet_x":15,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Clapping Hands Sign","b":"1F44F","j":["hands","praise","applause","congrats","yay"],"k":[15,9]},"open_hands":{"skin_variations":{"1F3FB":{"unified":"1F450-1F3FB","non_qualified":null,"image":"1f450-1f3fb.png","sheet_x":15,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F450-1F3FC","non_qualified":null,"image":"1f450-1f3fc.png","sheet_x":15,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F450-1F3FD","non_qualified":null,"image":"1f450-1f3fd.png","sheet_x":15,"sheet_y":18,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F450-1F3FE","non_qualified":null,"image":"1f450-1f3fe.png","sheet_x":15,"sheet_y":19,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F450-1F3FF","non_qualified":null,"image":"1f450-1f3ff.png","sheet_x":15,"sheet_y":20,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Open Hands Sign","b":"1F450","j":["fingers","butterfly","hands","open"],"k":[15,15]},"raised_hands":{"skin_variations":{"1F3FB":{"unified":"1F64C-1F3FB","non_qualified":null,"image":"1f64c-1f3fb.png","sheet_x":33,"sheet_y":13,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F64C-1F3FC","non_qualified":null,"image":"1f64c-1f3fc.png","sheet_x":33,"sheet_y":14,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F64C-1F3FD","non_qualified":null,"image":"1f64c-1f3fd.png","sheet_x":33,"sheet_y":15,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F64C-1F3FE","non_qualified":null,"image":"1f64c-1f3fe.png","sheet_x":33,"sheet_y":16,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F64C-1F3FF","non_qualified":null,"image":"1f64c-1f3ff.png","sheet_x":33,"sheet_y":17,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Person Raising Both Hands in Celebration","b":"1F64C","j":["gesture","hooray","yea","celebration","hands"],"k":[33,12]},"palms_up_together":{"skin_variations":{"1F3FB":{"unified":"1F932-1F3FB","non_qualified":null,"image":"1f932-1f3fb.png","sheet_x":39,"sheet_y":17,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FC":{"unified":"1F932-1F3FC","non_qualified":null,"image":"1f932-1f3fc.png","sheet_x":39,"sheet_y":18,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FD":{"unified":"1F932-1F3FD","non_qualified":null,"image":"1f932-1f3fd.png","sheet_x":39,"sheet_y":19,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FE":{"unified":"1F932-1F3FE","non_qualified":null,"image":"1f932-1f3fe.png","sheet_x":39,"sheet_y":20,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false},"1F3FF":{"unified":"1F932-1F3FF","non_qualified":null,"image":"1f932-1f3ff.png","sheet_x":39,"sheet_y":21,"added_in":"10.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":false}},"a":"Palms Up Together","b":"1F932","k":[39,16],"o":10},"pray":{"skin_variations":{"1F3FB":{"unified":"1F64F-1F3FB","non_qualified":null,"image":"1f64f-1f3fb.png","sheet_x":34,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F64F-1F3FC","non_qualified":null,"image":"1f64f-1f3fc.png","sheet_x":34,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F64F-1F3FD","non_qualified":null,"image":"1f64f-1f3fd.png","sheet_x":34,"sheet_y":5,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F64F-1F3FE","non_qualified":null,"image":"1f64f-1f3fe.png","sheet_x":34,"sheet_y":6,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F64F-1F3FF","non_qualified":null,"image":"1f64f-1f3ff.png","sheet_x":34,"sheet_y":7,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Person with Folded Hands","b":"1F64F","j":["please","hope","wish","namaste","highfive"],"k":[34,2]},"handshake":{"a":"Handshake","b":"1F91D","j":["agreement","shake"],"k":[38,10],"o":9},"nail_care":{"skin_variations":{"1F3FB":{"unified":"1F485-1F3FB","non_qualified":null,"image":"1f485-1f3fb.png","sheet_x":23,"sheet_y":45,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F485-1F3FC","non_qualified":null,"image":"1f485-1f3fc.png","sheet_x":23,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F485-1F3FD","non_qualified":null,"image":"1f485-1f3fd.png","sheet_x":23,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F485-1F3FE","non_qualified":null,"image":"1f485-1f3fe.png","sheet_x":23,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F485-1F3FF","non_qualified":null,"image":"1f485-1f3ff.png","sheet_x":23,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Nail Polish","b":"1F485","j":["beauty","manicure","finger","fashion","nail"],"k":[23,44]},"ear":{"skin_variations":{"1F3FB":{"unified":"1F442-1F3FB","non_qualified":null,"image":"1f442-1f3fb.png","sheet_x":13,"sheet_y":46,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F442-1F3FC","non_qualified":null,"image":"1f442-1f3fc.png","sheet_x":13,"sheet_y":47,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F442-1F3FD","non_qualified":null,"image":"1f442-1f3fd.png","sheet_x":13,"sheet_y":48,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F442-1F3FE","non_qualified":null,"image":"1f442-1f3fe.png","sheet_x":13,"sheet_y":49,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F442-1F3FF","non_qualified":null,"image":"1f442-1f3ff.png","sheet_x":13,"sheet_y":50,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Ear","b":"1F442","j":["face","hear","sound","listen"],"k":[13,45]},"nose":{"skin_variations":{"1F3FB":{"unified":"1F443-1F3FB","non_qualified":null,"image":"1f443-1f3fb.png","sheet_x":14,"sheet_y":0,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FC":{"unified":"1F443-1F3FC","non_qualified":null,"image":"1f443-1f3fc.png","sheet_x":14,"sheet_y":1,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FD":{"unified":"1F443-1F3FD","non_qualified":null,"image":"1f443-1f3fd.png","sheet_x":14,"sheet_y":2,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FE":{"unified":"1F443-1F3FE","non_qualified":null,"image":"1f443-1f3fe.png","sheet_x":14,"sheet_y":3,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true},"1F3FF":{"unified":"1F443-1F3FF","non_qualified":null,"image":"1f443-1f3ff.png","sheet_x":14,"sheet_y":4,"added_in":"8.0","has_img_apple":true,"has_img_google":true,"has_img_twitter":true,"has_img_emojione":true,"has_img_facebook":true,"has_img_messenger":true}},"a":"Nose","b":"1F443","j":["smell","sniff"],"k":[13,51]},"footprints":{"a":"Footprints","b":"1F463","j":["feet","tracking","walking","beach"],"k":[15,39]},"eyes":{"a":"Eyes","b":"1F440","j":["look","watch","stalk","peek","see"],"k":[13,42]},"eye":{"a":"Eye","b":"1F441-FE0F","c":"1F441","j":["face","look","see","watch","stare"],"k":[13,44],"o":7},"eye-in-speech-bubble":{"a":"Eye in Speech Bubble","b":"1F441-FE0F-200D-1F5E8-FE0F","k":[13,43],"o":7},"brain":{"a":"Brain","b":"1F9E0","k":[46,22],"o":10},"tongue":{"a":"Tongue","b":"1F445","j":["mouth","playful"],"k":[14,6]},"lips":{"a":"Mouth","b":"1F444","j":["mouth","kiss"],"k":[14,5]},"kiss":{"a":"Kiss Mark","b":"1F48B","j":["face","lips","love","like","affection","valentines"],"k":[24,37]},"cupid":{"a":"Heart with Arrow","b":"1F498","j":["love","like","heart","affection","valentines"],"k":[24,50]},"heart":{"a":"Heavy Black Heart","b":"2764-FE0F","c":"2764","j":["love","like","valentines"],"k":[50,8],"l":["<3"],"m":"<3","o":1},"heartbeat":{"a":"Beating Heart","b":"1F493","j":["love","like","affection","valentines","pink","heart"],"k":[24,45]},"broken_heart":{"a":"Broken Heart","b":"1F494","j":["sad","sorry","break","heart","heartbreak"],"k":[24,46],"l":[") -> 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) + } + } + +} diff --git a/Riot/Modules/Room/EmojiPicker/Data/EmojiServiceType.swift b/Riot/Modules/Room/EmojiPicker/Data/EmojiServiceType.swift new file mode 100644 index 000000000..80a0b3456 --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/Data/EmojiServiceType.swift @@ -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) +} diff --git a/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiItem+Decodable.swift b/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiItem+Decodable.swift new file mode 100644 index 000000000..18bb1c0ae --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiItem+Decodable.swift @@ -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) + } +} diff --git a/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONCategory.swift b/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONCategory.swift new file mode 100644 index 000000000..87a64a817 --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONCategory.swift @@ -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" + } +} diff --git a/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONStore.swift b/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONStore.swift new file mode 100644 index 000000000..2db3ac2f1 --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONStore.swift @@ -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) + } +} diff --git a/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiCategory.swift b/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiCategory.swift new file mode 100644 index 000000000..6adc37d25 --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiCategory.swift @@ -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) + } +} diff --git a/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiItem.swift b/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiItem.swift new file mode 100644 index 000000000..ad2c311c9 --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiItem.swift @@ -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 + } +} From 331d7aa80f7af825c17549ded1967c2203dfea06 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 25 Jul 2019 16:33:12 +0200 Subject: [PATCH 18/75] Emoji picker: Add Emoji parsing unit tests. --- RiotTests/EmojiServiceTests.swift | 142 ++++++++++++++++++++++++++ RiotTests/RiotTests-Bridging-Header.h | 5 + 2 files changed, 147 insertions(+) create mode 100644 RiotTests/EmojiServiceTests.swift create mode 100644 RiotTests/RiotTests-Bridging-Header.h diff --git a/RiotTests/EmojiServiceTests.swift b/RiotTests/EmojiServiceTests.swift new file mode 100644 index 000000000..d1541d44d --- /dev/null +++ b/RiotTests/EmojiServiceTests.swift @@ -0,0 +1,142 @@ +/* + 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 XCTest + +@testable import Riot + +class EmojiServiceTests: XCTestCase { + + // MARK: - Constants + + private let defaultTimeout: TimeInterval = 1.5 + + enum ExpectedEmojiCategory: Int { + case people + case nature + case foods + case activity + case places + case objects + case symbols + case flags + + var identifier: String { + let identifier: String + switch self { + case .people: + identifier = "people" + case .nature: + identifier = "nature" + case .foods: + identifier = "foods" + case .activity: + identifier = "activity" + case .places: + identifier = "places" + case .objects: + identifier = "objects" + case .symbols: + identifier = "symbols" + case .flags: + identifier = "flags" + } + return identifier + } + + var emojisCount: Int { + let emojiCount: Int + switch self { + case .people: + emojiCount = 447 + case .nature: + emojiCount = 113 + case .foods: + emojiCount = 102 + case .activity: + emojiCount = 60 + case .places: + emojiCount = 207 + case .objects: + emojiCount = 162 + case .symbols: + emojiCount = 202 + case .flags: + emojiCount = 266 + } + return emojiCount + } + + static var all: [ExpectedEmojiCategory] { + return [ + .people, .nature, .foods, .activity, .places, .objects, .symbols, .flags + ] + } + } + + // MARK: - Life cycle + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + // MARK: - Tests + + func testEmojiService() { + + let expectation = self.expectation(description: "get Emoji categories") + + let emojiService = EmojiService() + emojiService.getEmojiCategories { (response) in + switch response { + case .success(let emojiCategories): + + XCTAssertEqual(emojiCategories.count, ExpectedEmojiCategory.all.count) + + var index = 0 + for emojiCategory in emojiCategories { + guard let expectedEmojiCategory = ExpectedEmojiCategory(rawValue: index) else { + XCTFail("Fail to retrieve expected emoji category") + return + } + XCTAssertEqual(emojiCategory.identifier, expectedEmojiCategory.identifier) + XCTAssertEqual(emojiCategory.emojis.count, expectedEmojiCategory.emojisCount) + index+=1 + } + + let peopleEmojiCategory = emojiCategories[ExpectedEmojiCategory.people.rawValue] + + let grinningEmoji = peopleEmojiCategory.emojis[0] + + XCTAssertEqual(grinningEmoji.identifier, "grinning") + XCTAssertEqual(grinningEmoji.value, "😀") + XCTAssertEqual(grinningEmoji.keywords.count, 6) + + expectation.fulfill() + case .failure(let error): + XCTFail("Fail with error: \(error)") + } + } + + self.waitForExpectations(timeout: self.defaultTimeout) {error in + XCTAssertNil(error) + } + } +} diff --git a/RiotTests/RiotTests-Bridging-Header.h b/RiotTests/RiotTests-Bridging-Header.h new file mode 100644 index 000000000..d552618e7 --- /dev/null +++ b/RiotTests/RiotTests-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "Riot-Bridging-Header.h" From e4df03145139fe4936ae2a79becb062501546122 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 25 Jul 2019 16:34:01 +0200 Subject: [PATCH 19/75] Contextual menu: Fix reactions menu theme update. --- .../Room/ContextualMenu/RoomContextualMenuViewController.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuViewController.swift b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuViewController.swift index baba7f279..874ac0466 100644 --- a/Riot/Modules/Room/ContextualMenu/RoomContextualMenuViewController.swift +++ b/Riot/Modules/Room/ContextualMenu/RoomContextualMenuViewController.swift @@ -201,6 +201,7 @@ final class RoomContextualMenuViewController: UIViewController, Themable { func update(theme: Theme) { self.menuToolbarView.update(theme: theme) + self.reactionsMenuView?.update(theme: theme) } // MARK: - Private @@ -225,6 +226,7 @@ final class RoomContextualMenuViewController: UIViewController, Themable { if self.reactionsMenuContainerView.subviews.isEmpty { let reactionsMenuView = ReactionsMenuView.loadFromNib() self.reactionsMenuContainerView.vc_addSubViewMatchingParent(reactionsMenuView) + reactionsMenuView.update(theme: self.theme) self.reactionsMenuView = reactionsMenuView } From c9d4b4193a6ef6fa53df80224b813618895791ab Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 25 Jul 2019 16:37:18 +0200 Subject: [PATCH 20/75] Emoji picker: Implement Emoji store, view model and view data. --- .../EmojiPicker/Data/Store/EmojiStore.swift | 69 +++++++++ .../EmojiPickerCategoryViewData.swift | 23 +++ .../EmojiPicker/EmojiPickerItemViewData.swift | 23 +++ .../EmojiPicker/EmojiPickerViewAction.swift | 27 ++++ .../EmojiPicker/EmojiPickerViewModel.swift | 146 ++++++++++++++++++ .../EmojiPickerViewModelType.swift | 38 +++++ .../EmojiPicker/EmojiPickerViewState.swift | 26 ++++ 7 files changed, 352 insertions(+) create mode 100644 Riot/Modules/Room/EmojiPicker/Data/Store/EmojiStore.swift create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerCategoryViewData.swift create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerItemViewData.swift create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerViewAction.swift create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerViewModel.swift create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerViewModelType.swift create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerViewState.swift diff --git a/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiStore.swift b/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiStore.swift new file mode 100644 index 000000000..be52d9c1d --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiStore.swift @@ -0,0 +1,69 @@ +/* + 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 + +final class EmojiStore { + + static let shared = EmojiStore() + + // MARK: - Properties + + private var emojiCategories: [EmojiCategory] = [] + + // MARK: - Public + + func getAll() -> [EmojiCategory] { + return self.emojiCategories + } + + func set(_ emojiCategories: [EmojiCategory]) { + self.emojiCategories = emojiCategories + } + + func findEmojiItemsSortedByCategory(with searchText: String) -> [EmojiCategory] { + let initial: [EmojiCategory] = [] + + let filteredEmojiCategories = emojiCategories.reduce(into: initial) { (filteredEmojiCategories, emojiCategory) in + + let filteredEmojiItems = emojiCategory.emojis.filter({ (emojiItem) -> Bool in + + // Do not use `String.localizedCaseInsensitiveContains` here as EmojiItem data is not localized for the moment + + if emojiItem.name.vc_caseInsensitiveContains(searchText) { + return true + } + + if emojiItem.keywords.contains(where: { $0.vc_caseInsensitiveContains(searchText) }) { + return true + } + + let shortNamesMatch = emojiItem.shortNames.contains { text -> Bool in + return text.vc_caseInsensitiveContains(searchText) + } + + return shortNamesMatch + }) + + if filteredEmojiItems.isEmpty == false { + let filteredEmojiCategory = EmojiCategory(identifier: emojiCategory.identifier, emojis: filteredEmojiItems) + filteredEmojiCategories.append(filteredEmojiCategory) + } + } + + return filteredEmojiCategories + } +} diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerCategoryViewData.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerCategoryViewData.swift new file mode 100644 index 000000000..971594168 --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerCategoryViewData.swift @@ -0,0 +1,23 @@ +/* + 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 UIKit + +struct EmojiPickerCategoryViewData { + let identifier: String + let name: String + let emojiViewDataList: [EmojiPickerItemViewData] +} diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerItemViewData.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerItemViewData.swift new file mode 100644 index 000000000..03b343789 --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerItemViewData.swift @@ -0,0 +1,23 @@ +/* + 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 UIKit + +struct EmojiPickerItemViewData { + let identifier: String + let emoji: String + let isSelected: Bool +} diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerViewAction.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewAction.swift new file mode 100644 index 000000000..68e7f813e --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewAction.swift @@ -0,0 +1,27 @@ +// File created from ScreenTemplate +// $ createScreen.sh toto EmojiPicker +/* + 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 + +/// EmojiPickerViewController view actions exposed to view model +enum EmojiPickerViewAction { + case loadData + case cancel + case tap(emojiItemViewData: EmojiPickerItemViewData) + case search(text: String?) +} diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerViewModel.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewModel.swift new file mode 100644 index 000000000..6f83eb7ee --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewModel.swift @@ -0,0 +1,146 @@ +// File created from ScreenTemplate +// $ createScreen.sh toto EmojiPicker +/* + 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 + +final class EmojiPickerViewModel: EmojiPickerViewModelType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let roomId: String + private let eventId: String + private let emojiService: EmojiService + private let emojiStore: EmojiStore + + private lazy var aggregatedReactionsByEmoji: [String: MXReactionCount] = { + return self.buildAggregatedReactionsByEmoji() + }() + + // MARK: Public + + weak var viewDelegate: EmojiPickerViewModelViewDelegate? + weak var coordinatorDelegate: EmojiPickerViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, roomId: String, eventId: String) { + self.session = session + self.roomId = roomId + self.eventId = eventId + self.emojiService = EmojiService() + self.emojiStore = EmojiStore.shared + } + + // MARK: - Public + + func process(viewAction: EmojiPickerViewAction) { + switch viewAction { + case .loadData: + self.loadData() + case .tap(emojiItemViewData: let emojiItemViewData): + let emoji = emojiItemViewData.emoji + + if emojiItemViewData.isSelected { + self.coordinatorDelegate?.emojiPickerViewModel(self, didRemoveEmoji: emoji, forEventId: self.eventId) + } else { + self.coordinatorDelegate?.emojiPickerViewModel(self, didAddEmoji: emoji, forEventId: self.eventId) + } + case .search(text: let searchText): + self.searchEmojis(with: searchText) + case .cancel: + self.coordinatorDelegate?.emojiPickerViewModelDidCancel(self) + } + } + + // MARK: - Private + + private func loadData() { + if self.emojiStore.getAll().isEmpty == false { + let emojiCategories = self.emojiStore.getAll() + let emojiCatagoryViewDataList = self.emojiCatagoryViewDataList(from: emojiCategories) + self.update(viewState: .loaded(emojiCategories: emojiCatagoryViewDataList)) + } else { + self.update(viewState: .loading) + self.emojiService.getEmojiCategories { response in + switch response { + case .success(let emojiCategories): + + self.emojiStore.set(emojiCategories) + + let emojiCatagoryViewDataList = self.emojiCatagoryViewDataList(from: emojiCategories) + DispatchQueue.main.async { + self.update(viewState: .loaded(emojiCategories: emojiCatagoryViewDataList)) + } + case .failure(let error): + DispatchQueue.main.async { + self.update(viewState: .error(error)) + } + } + } + } + } + + 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() + } + + let emojiCatagoryViewDataList = self.emojiCatagoryViewDataList(from: filteredEmojiCategories) + self.update(viewState: .loaded(emojiCategories: emojiCatagoryViewDataList)) + } + + private func update(viewState: EmojiPickerViewState) { + self.viewDelegate?.emojiPickerViewModel(self, didUpdateViewState: viewState) + } + + private func emojiCatagoryViewDataList(from emojiCategories: [EmojiCategory]) -> [EmojiPickerCategoryViewData] { + 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 EmojiPickerCategoryViewData(identifier: emojiCategory.identifier, name: emojiCategory.name, emojiViewDataList: emojiPickerViewDataList) + } + } + + private func isUserReacted(with emoji: String) -> Bool { + guard let reactionCount = self.aggregatedReactionsByEmoji[emoji] else { + return false + } + return reactionCount.myUserHasReacted + } + + private func buildAggregatedReactionsByEmoji() -> [String: MXReactionCount] { + guard let aggregatedReactions = self.session.aggregations.aggregatedReactions(onEvent: self.eventId, inRoom: self.roomId) else { + return [:] + } + + let initial: [String: MXReactionCount] = [:] + + return aggregatedReactions.reactions.reduce(into: initial) { (aggregatedReactionsByEmoji, reactionCount) in + aggregatedReactionsByEmoji[reactionCount.reaction] = reactionCount + } + } +} diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerViewModelType.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewModelType.swift new file mode 100644 index 000000000..3f15484f1 --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewModelType.swift @@ -0,0 +1,38 @@ +// File created from ScreenTemplate +// $ createScreen.sh toto EmojiPicker +/* + 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 EmojiPickerViewModelViewDelegate: class { + func emojiPickerViewModel(_ viewModel: EmojiPickerViewModelType, didUpdateViewState viewSate: EmojiPickerViewState) +} + +protocol EmojiPickerViewModelCoordinatorDelegate: class { + func emojiPickerViewModel(_ viewModel: EmojiPickerViewModelType, didAddEmoji emoji: String, forEventId eventId: String) + func emojiPickerViewModel(_ viewModel: EmojiPickerViewModelType, didRemoveEmoji emoji: String, forEventId eventId: String) + func emojiPickerViewModelDidCancel(_ viewModel: EmojiPickerViewModelType) +} + +/// Protocol describing the view model used by `EmojiPickerViewController` +protocol EmojiPickerViewModelType { + + var viewDelegate: EmojiPickerViewModelViewDelegate? { get set } + var coordinatorDelegate: EmojiPickerViewModelCoordinatorDelegate? { get set } + + func process(viewAction: EmojiPickerViewAction) +} diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerViewState.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewState.swift new file mode 100644 index 000000000..8b0673a21 --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewState.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh toto EmojiPicker +/* + 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 + +/// EmojiPickerViewController view state +enum EmojiPickerViewState { + case loading + case loaded(emojiCategories: [EmojiPickerCategoryViewData]) + case error(Error) +} From 04d142ab65f21bc96bba9207d8158a6d4c1f10ca Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 25 Jul 2019 16:39:29 +0200 Subject: [PATCH 21/75] Emoji picker: Implement cell and header view. --- .../EmojiPicker/EmojiPickerHeaderView.swift | 36 +++++++ .../EmojiPicker/EmojiPickerHeaderView.xib | 39 +++++++ .../EmojiPicker/EmojiPickerViewCell.swift | 101 ++++++++++++++++++ .../Room/EmojiPicker/EmojiPickerViewCell.xib | 55 ++++++++++ 4 files changed, 231 insertions(+) create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerHeaderView.swift create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerHeaderView.xib create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerViewCell.swift create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerViewCell.xib diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerHeaderView.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerHeaderView.swift new file mode 100644 index 000000000..66a31781f --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerHeaderView.swift @@ -0,0 +1,36 @@ +/* + 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 UIKit +import Reusable + +final class EmojiPickerHeaderView: UICollectionReusableView, NibReusable { + + // MARK: - Properties + + @IBOutlet private weak var titleLabel: UILabel! + + // MARK: - Public + + func update(theme: Theme) { + self.backgroundColor = theme.backgroundColor + self.titleLabel.textColor = theme.headerTextPrimaryColor + } + + func fill(with title: String) { + titleLabel.text = title + } +} diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerHeaderView.xib b/Riot/Modules/Room/EmojiPicker/EmojiPickerHeaderView.xib new file mode 100644 index 000000000..1aebfa9d4 --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerHeaderView.xib @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerViewCell.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewCell.swift new file mode 100644 index 000000000..419b3b557 --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewCell.swift @@ -0,0 +1,101 @@ +/* + 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 UIKit +import Reusable + +final class EmojiPickerViewCell: UICollectionViewCell, NibReusable, Themable { + + // MARK: - Constants + + private enum Constants { + static let selectedBorderWidth: CGFloat = 1.0 + static let selectedBackgroundRadius: CGFloat = 5.0 + static let emojiHighlightedAlpha: CGFloat = 0.3 + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var reactionBackgroundView: UIView! + @IBOutlet private weak var emojiLabel: UILabel! + + // MARK: Private + + private var theme: Theme? + + // MARK: Public + + private var isReactionSelected: Bool = false + + override var isHighlighted: Bool { + didSet { + self.emojiLabel.alpha = isHighlighted ? Constants.emojiHighlightedAlpha : 1.0 + } + } + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + + self.reactionBackgroundView.layer.masksToBounds = true + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.reactionBackgroundView.layer.cornerRadius = self.reactionBackgroundView.frame.width/4.0 + } + + // MARK: - Public + + func fill(viewData: EmojiPickerItemViewData) { + self.emojiLabel.text = viewData.emoji + self.isReactionSelected = viewData.isSelected + + self.updateViews() + } + + func update(theme: Theme) { + self.theme = theme + self.reactionBackgroundView.layer.borderColor = theme.tintColor.cgColor + self.emojiLabel.textColor = theme.textPrimaryColor + self.updateViews() + } + + // MARK: - Private + + private func updateViews() { + + let reactionBackgroundColor: UIColor? + let reactionBackgroundBorderWidth: CGFloat + + if self.isReactionSelected { + reactionBackgroundColor = self.theme?.tintBackgroundColor + reactionBackgroundBorderWidth = Constants.selectedBorderWidth + } else { + reactionBackgroundColor = self.theme?.headerBackgroundColor + reactionBackgroundBorderWidth = 0.0 + } + + self.reactionBackgroundView.layer.borderWidth = reactionBackgroundBorderWidth + self.reactionBackgroundView.backgroundColor = reactionBackgroundColor + } + +} diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerViewCell.xib b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewCell.xib new file mode 100644 index 000000000..be89364ce --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewCell.xib @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a769f6290ebdeb4dcbb04f83661c44b8750c9535 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 25 Jul 2019 16:44:06 +0200 Subject: [PATCH 22/75] Emoji picker: Implement Emoji picker screen. --- Riot/Categories/String.swift | 9 + Riot/Generated/Images.swift | 1 + Riot/Generated/Storyboards.swift | 5 + .../EmojiPicker/EmojiPickerCoordinator.swift | 76 +++++ .../EmojiPickerCoordinatorType.swift | 30 ++ .../EmojiPickerViewController.storyboard | 56 +++ .../EmojiPickerViewController.swift | 319 ++++++++++++++++++ 7 files changed, 496 insertions(+) create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinator.swift create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorType.swift create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerViewController.storyboard create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerViewController.swift diff --git a/Riot/Categories/String.swift b/Riot/Categories/String.swift index c492e71dd..f6bbd04bc 100644 --- a/Riot/Categories/String.swift +++ b/Riot/Categories/String.swift @@ -29,4 +29,13 @@ extension String { } return abs(hash) } + + /// Locale-independent case-insensitive contains + /// Note: Prefer use `localizedCaseInsensitiveContains` when locale matters + /// + /// - Parameter other: The other string. + /// - Returns: true if current string contains other string. + func vc_caseInsensitiveContains(_ other: String) -> Bool { + return self.range(of: other, options: .caseInsensitive) != nil + } } diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 5007a2609..6953898f6 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -89,6 +89,7 @@ internal enum Asset { internal static let mainAliasIcon = ImageAsset(name: "main_alias_icon") internal static let membersListIcon = ImageAsset(name: "members_list_icon") internal static let modIcon = ImageAsset(name: "mod_icon") + internal static let moreReactions = ImageAsset(name: "more_reactions") internal static let fileDocIcon = ImageAsset(name: "file_doc_icon") internal static let fileMusicIcon = ImageAsset(name: "file_music_icon") internal static let filePhotoIcon = ImageAsset(name: "file_photo_icon") diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index b8647542a..56dc93c8f 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -42,6 +42,11 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: EditHistoryViewController.self) } + internal enum EmojiPickerViewController: StoryboardType { + internal static let storyboardName = "EmojiPickerViewController" + + internal static let initialScene = InitialSceneType(storyboard: EmojiPickerViewController.self) + } internal enum KeyBackupRecoverFromPassphraseViewController: StoryboardType { internal static let storyboardName = "KeyBackupRecoverFromPassphraseViewController" diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinator.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinator.swift new file mode 100644 index 000000000..e7380c041 --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinator.swift @@ -0,0 +1,76 @@ +// File created from ScreenTemplate +// $ createScreen.sh toto EmojiPicker +/* + 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 +import UIKit + +final class EmojiPickerCoordinator: EmojiPickerCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let roomId: String + private let eventId: String + private let router: NavigationRouter + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: EmojiPickerCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, roomId: String, eventId: String) { + self.session = session + self.roomId = roomId + self.eventId = eventId + self.router = NavigationRouter(navigationController: RiotNavigationController()) + } + + // MARK: - Public methods + + func start() { + let emojiPickerViewModel = EmojiPickerViewModel(session: self.session, roomId: self.roomId, eventId: self.eventId) + let emojiPickerViewController = EmojiPickerViewController.instantiate(with: emojiPickerViewModel) + emojiPickerViewModel.coordinatorDelegate = self + self.router.setRootModule(emojiPickerViewController) + } + + func toPresentable() -> UIViewController { + return self.router.toPresentable() + } +} + +// MARK: - EmojiPickerViewModelCoordinatorDelegate +extension EmojiPickerCoordinator: EmojiPickerViewModelCoordinatorDelegate { + func emojiPickerViewModel(_ viewModel: EmojiPickerViewModelType, didAddEmoji emoji: String, forEventId eventId: String) { + self.delegate?.emojiPickerCoordinator(self, didAddEmoji: emoji, forEventId: eventId) + } + + func emojiPickerViewModel(_ viewModel: EmojiPickerViewModelType, didRemoveEmoji emoji: String, forEventId eventId: String) { + self.delegate?.emojiPickerCoordinator(self, didRemoveEmoji: emoji, forEventId: eventId) + } + + func emojiPickerViewModelDidCancel(_ viewModel: EmojiPickerViewModelType) { + self.delegate?.emojiPickerCoordinatorDidCancel(self) + } +} diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorType.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorType.swift new file mode 100644 index 000000000..3ae5fd900 --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorType.swift @@ -0,0 +1,30 @@ +// File created from ScreenTemplate +// $ createScreen.sh toto EmojiPicker +/* + 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 EmojiPickerCoordinatorDelegate: class { + func emojiPickerCoordinator(_ coordinator: EmojiPickerCoordinatorType, didAddEmoji emoji: String, forEventId eventId: String) + func emojiPickerCoordinator(_ coordinator: EmojiPickerCoordinatorType, didRemoveEmoji emoji: String, forEventId eventId: String) + func emojiPickerCoordinatorDidCancel(_ coordinator: EmojiPickerCoordinatorType) +} + +/// `EmojiPickerCoordinatorType` is a protocol describing a Coordinator that handle key backup setup passphrase navigation flow. +protocol EmojiPickerCoordinatorType: Coordinator, Presentable { + var delegate: EmojiPickerCoordinatorDelegate? { get } +} diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerViewController.storyboard b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewController.storyboard new file mode 100644 index 000000000..104e40a45 --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewController.storyboard @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerViewController.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewController.swift new file mode 100644 index 000000000..c26d1e3fb --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewController.swift @@ -0,0 +1,319 @@ +// File created from ScreenTemplate +// $ createScreen.sh toto EmojiPicker +/* + 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 UIKit +import Reusable + +final class EmojiPickerViewController: UIViewController { + + // MARK: - Constants + + private enum CollectionViewLayout { + static let sectionInsets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0) + static let minimumInteritemSpacing: CGFloat = 6.0 + static let minimumLineSpacing: CGFloat = 2.0 + static let itemSize = CGSize(width: 50, height: 50) + } + + private static let sizingHeaderView = EmojiPickerHeaderView.loadFromNib() + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var collectionView: UICollectionView! + + // MARK: Private + + private var viewModel: EmojiPickerViewModelType! + private var theme: Theme! + private var keyboardAvoider: KeyboardAvoider? + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + private var searchController: UISearchController? + private var emojiCategories: [EmojiPickerCategoryViewData] = [] + + // MARK: - Setup + + class func instantiate(with viewModel: EmojiPickerViewModelType) -> EmojiPickerViewController { + let viewController = StoryboardScene.EmojiPickerViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.title = VectorL10n.emojiPickerTitle + + self.setupViews() + self.keyboardAvoider = KeyboardAvoider(scrollViewContainerView: self.view, scrollView: self.collectionView) + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.viewDelegate = self + self.viewModel.process(viewAction: .loadData) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.keyboardAvoider?.startAvoiding() + + // Update theme here otherwise the UISearchBar search text color is not updated + self.update(theme: self.theme) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + // Enable to hide search bar on scrolling after first time view appear + if #available(iOS 11.0, *) { + self.navigationItem.hidesSearchBarWhenScrolling = true + } + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + self.keyboardAvoider?.stopAvoiding() + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + + if let searchController = self.searchController { + theme.applyStyle(onSearchBar: searchController.searchBar) + } + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func setupViews() { + let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in + self?.cancelButtonAction() + } + + self.navigationItem.rightBarButtonItem = cancelBarButtonItem + + self.setupCollectionView() + + if #available(iOS 11.0, *) { + self.setupSearchController() + } + } + + private func setupCollectionView() { + self.collectionView.delegate = self + self.collectionView.dataSource = self + + self.collectionView.keyboardDismissMode = .interactive + + if let collectionViewFlowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout { + collectionViewFlowLayout.minimumInteritemSpacing = CollectionViewLayout.minimumInteritemSpacing + collectionViewFlowLayout.minimumLineSpacing = CollectionViewLayout.minimumLineSpacing + collectionViewFlowLayout.itemSize = CollectionViewLayout.itemSize + collectionViewFlowLayout.sectionInset = CollectionViewLayout.sectionInsets + collectionViewFlowLayout.sectionHeadersPinToVisibleBounds = true // Enable sticky headers + + // Avoid device notch in landascape (e.g. iPhone X) + if #available(iOS 11.0, *) { + collectionViewFlowLayout.sectionInsetReference = .fromSafeArea + } + } + + self.collectionView.register(supplementaryViewType: EmojiPickerHeaderView.self, ofKind: UICollectionView.elementKindSectionHeader) + self.collectionView.register(cellType: EmojiPickerViewCell.self) + } + + private func setupSearchController() { + let searchController = UISearchController(searchResultsController: nil) + searchController.dimsBackgroundDuringPresentation = false + searchController.searchResultsUpdater = self + searchController.searchBar.placeholder = VectorL10n.searchDefaultPlaceholder + searchController.hidesNavigationBarDuringPresentation = false + + if #available(iOS 11.0, *) { + self.navigationItem.searchController = searchController + // Make the search bar visible on first view appearance + self.navigationItem.hidesSearchBarWhenScrolling = false + } + + self.definesPresentationContext = true + + self.searchController = searchController + } + + private func render(viewState: EmojiPickerViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(emojiCategories: let emojiCategories): + self.renderLoaded(emojiCategories: emojiCategories) + case .error(let error): + self.render(error: error) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(emojiCategories: [EmojiPickerCategoryViewData]) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.update(emojiCategories: emojiCategories) + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + private func update(emojiCategories: [EmojiPickerCategoryViewData]) { + self.emojiCategories = emojiCategories + self.collectionView.reloadData() + } + + private func emojiItemViewData(at indexPath: IndexPath) -> EmojiPickerItemViewData { + return self.emojiCategories[indexPath.section].emojiViewDataList[indexPath.row] + } + + private func emojiCategoryViewData(at section: Int) -> EmojiPickerCategoryViewData? { + return self.emojiCategories[section] + } + + private func headerViewSize(for title: String) -> CGSize { + + let sizingHeaderView = EmojiPickerViewController.sizingHeaderView + + sizingHeaderView.fill(with: title) + sizingHeaderView.setNeedsLayout() + sizingHeaderView.layoutIfNeeded() + + var fittingSize = UIView.layoutFittingCompressedSize + fittingSize.width = self.collectionView.bounds.size.width + + return sizingHeaderView.systemLayoutSizeFitting(fittingSize) + } + + // MARK: - Actions + + private func cancelButtonAction() { + self.viewModel.process(viewAction: .cancel) + } +} + +// MARK: - UICollectionViewDataSource +extension EmojiPickerViewController: UICollectionViewDataSource { + func numberOfSections(in collectionView: UICollectionView) -> Int { + return self.emojiCategories.count + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.emojiCategories[section].emojiViewDataList.count + } + + func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { + let emojiPickerCategory = self.emojiCategories[indexPath.section] + + let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, for: indexPath) as EmojiPickerHeaderView + headerView.update(theme: self.theme) + headerView.fill(with: emojiPickerCategory.name) + + return headerView + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell: EmojiPickerViewCell = collectionView.dequeueReusableCell(for: indexPath) + + if let theme = self.theme { + cell.update(theme: theme) + } + + let viewData = self.emojiItemViewData(at: indexPath) + cell.fill(viewData: viewData) + + return cell + } +} + +// MARK: - UICollectionViewDelegate +extension EmojiPickerViewController: UICollectionViewDelegate { + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let emojiItemViewData = self.emojiItemViewData(at: indexPath) + self.viewModel.process(viewAction: .tap(emojiItemViewData: emojiItemViewData)) + } + + func collectionView(_ collectionView: UICollectionView, willDisplaySupplementaryView view: UICollectionReusableView, forElementKind elementKind: String, at indexPath: IndexPath) { + // Fix UICollectionView scroll bar appears underneath header view + view.layer.zPosition = 0.0 + } +} + +// MARK: - UICollectionViewDelegateFlowLayout +extension EmojiPickerViewController: UICollectionViewDelegateFlowLayout { + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { + let emojiCategory = self.emojiCategories[section] + let headerSize = self.headerViewSize(for: emojiCategory.name) + return headerSize + } +} + +// MARK: - EmojiPickerViewModelViewDelegate +extension EmojiPickerViewController: EmojiPickerViewModelViewDelegate { + + func emojiPickerViewModel(_ viewModel: EmojiPickerViewModelType, didUpdateViewState viewSate: EmojiPickerViewState) { + self.render(viewState: viewSate) + } +} + +// MARK: - UISearchResultsUpdating +extension EmojiPickerViewController: UISearchResultsUpdating { + func updateSearchResults(for searchController: UISearchController) { + let searchText = searchController.searchBar.text + self.viewModel.process(viewAction: .search(text: searchText)) + } +} From 7066c58a0b74959843221a3bd7d4b35645dfcb8b Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 25 Jul 2019 16:45:20 +0200 Subject: [PATCH 23/75] Emoji picker: Implement coordinator bridge presenter. --- ...mojiPickerCoordinatorBridgePresenter.swift | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorBridgePresenter.swift diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorBridgePresenter.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorBridgePresenter.swift new file mode 100644 index 000000000..cd1a0653c --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorBridgePresenter.swift @@ -0,0 +1,117 @@ +/* + 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 UIKit + +@objc protocol EmojiPickerCoordinatorBridgePresenterDelegate { + func emojiPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: EmojiPickerCoordinatorBridgePresenter, didAddEmoji emoji: String, forEventId eventId: String) + func emojiPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: EmojiPickerCoordinatorBridgePresenter, didRemoveEmoji emoji: String, forEventId eventId: String) + func emojiPickerCoordinatorBridgePresenterDidCancel(_ coordinatorBridgePresenter: EmojiPickerCoordinatorBridgePresenter) +} + +/// EmojiPickerCoordinatorBridgePresenter enables to start EmojiPickerCoordinator from a view controller. +/// This bridge is used while waiting for global usage of coordinator pattern. +@objcMembers +final class EmojiPickerCoordinatorBridgePresenter: NSObject { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let roomId: String + private let eventId: String + private var coordinator: EmojiPickerCoordinator? + + // MARK: Public + + weak var delegate: EmojiPickerCoordinatorBridgePresenterDelegate? + + // MARK: - Setup + + init(session: MXSession, roomId: String, eventId: String) { + self.session = session + self.roomId = roomId + self.eventId = eventId + super.init() + } + + // MARK: - Public + + func present(from viewController: UIViewController, + sourceView: UIView?, + sourceRect: CGRect, + animated: Bool) { + let emojiPickerCoordinator = EmojiPickerCoordinator(session: self.session, roomId: self.roomId, eventId: self.eventId) + emojiPickerCoordinator.delegate = self + + let emojiPickerPresentable = emojiPickerCoordinator.toPresentable() + + if let sourceView = sourceView { + + emojiPickerPresentable.modalPresentationStyle = .popover + + if let popoverPresentationController = emojiPickerPresentable.popoverPresentationController { + popoverPresentationController.sourceView = sourceView + + let finalSourceRect: CGRect + + if sourceRect != CGRect.null { + finalSourceRect = sourceRect + } else { + finalSourceRect = sourceView.bounds + } + + popoverPresentationController.sourceRect = finalSourceRect + } + } + + viewController.present(emojiPickerPresentable, animated: animated, completion: nil) + + emojiPickerCoordinator.start() + + self.coordinator = emojiPickerCoordinator + } + + func dismiss(animated: Bool, completion: (() -> Void)?) { + guard let coordinator = self.coordinator else { + return + } + coordinator.toPresentable().dismiss(animated: animated) { + self.coordinator = nil + + if let completion = completion { + completion() + } + } + } +} + +// MARK: - EmojiPickerCoordinatorDelegate +extension EmojiPickerCoordinatorBridgePresenter: EmojiPickerCoordinatorDelegate { + + func emojiPickerCoordinator(_ coordinator: EmojiPickerCoordinatorType, didAddEmoji emoji: String, forEventId eventId: String) { + self.delegate?.emojiPickerCoordinatorBridgePresenter(self, didAddEmoji: emoji, forEventId: eventId) + } + + func emojiPickerCoordinator(_ coordinator: EmojiPickerCoordinatorType, didRemoveEmoji emoji: String, forEventId eventId: String) { + self.delegate?.emojiPickerCoordinatorBridgePresenter(self, didRemoveEmoji: emoji, forEventId: eventId) + } + + func emojiPickerCoordinatorDidCancel(_ coordinator: EmojiPickerCoordinatorType) { + self.delegate?.emojiPickerCoordinatorBridgePresenterDidCancel(self) + } +} From 39629a6638baa2b2de5091bd6410e26944ab5b3a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 25 Jul 2019 16:46:35 +0200 Subject: [PATCH 24/75] RoomVC: Handle Emoji picker. --- Riot/Modules/Room/RoomViewController.m | 99 ++++++++++++++++++++------ 1 file changed, 77 insertions(+), 22 deletions(-) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 5b574c56f..4518abd77 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -124,7 +124,7 @@ #import "Riot-Swift.h" @interface RoomViewController () + ReactionsMenuViewModelCoordinatorDelegate, EditHistoryCoordinatorBridgePresenterDelegate, MXKDocumentPickerPresenterDelegate, EmojiPickerCoordinatorBridgePresenterDelegate> { // The expanded header ExpandedRoomTitleView *expandedHeader; @@ -223,6 +223,7 @@ @property (nonatomic, strong) NSString *textMessageBeforeEditing; @property (nonatomic, strong) EditHistoryCoordinatorBridgePresenter *editHistoryPresenter; @property (nonatomic, strong) MXKDocumentPickerPresenter *documentPickerPresenter; +@property (nonatomic, strong) EmojiPickerCoordinatorBridgePresenter *emojiPickerCoordinatorBridgePresenter; @end @@ -2710,19 +2711,9 @@ // Do not display empty action sheet if (currentAlert.actions.count > 1) { - NSArray *components = roomBubbleTableViewCell.bubbleData.bubbleComponents; + NSInteger bubbleComponentIndex = [roomBubbleTableViewCell.bubbleData bubbleComponentIndexForEventId:selectedEvent.eventId]; - NSInteger index = 0; - for (MXKRoomBubbleComponent *component in components) - { - if ([component.event.eventId isEqualToString:selectedEvent.eventId]) - { - break; - } - index++; - } - - CGRect sourceRect = [roomBubbleTableViewCell componentFrameInContentViewForIndex:index]; + CGRect sourceRect = [roomBubbleTableViewCell componentFrameInContentViewForIndex:bubbleComponentIndex]; [currentAlert mxk_setAccessibilityIdentifier:@"RoomVCEventMenuAlert"]; [currentAlert popoverPresentationController].sourceView = roomBubbleTableViewCell; @@ -5200,15 +5191,7 @@ MXKRoomBubbleCellData *bubbleCellData = roomBubbleTableViewCell.bubbleData; NSArray *bubbleComponents = bubbleCellData.bubbleComponents; - NSInteger foundComponentIndex = [bubbleComponents indexOfObjectPassingTest:^BOOL(MXKRoomBubbleComponent * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - if (obj.event.eventId == selectedEventId) - { - *stop = YES; - return YES; - } - return NO; - }]; - + NSInteger foundComponentIndex = [bubbleCellData bubbleComponentIndexForEventId:event.eventId]; CGRect bubbleComponentFrame; if (bubbleComponents.count > 0) @@ -5334,6 +5317,37 @@ }]; } +- (void)reactionsMenuViewModelDidTapMoreReactions:(ReactionsMenuViewModel *)viewModel forEventId:(NSString *)eventId +{ + [self hideContextualMenuAnimated:YES]; + + EmojiPickerCoordinatorBridgePresenter *emojiPickerCoordinatorBridgePresenter = [[EmojiPickerCoordinatorBridgePresenter alloc] initWithSession:self.mainSession roomId:self.roomDataSource.roomId eventId:eventId]; + emojiPickerCoordinatorBridgePresenter.delegate = self; + + NSInteger cellRow = [self.roomDataSource indexOfCellDataWithEventId:eventId]; + + UIView *sourceView; + CGRect sourceRect = CGRectNull; + + if (cellRow > 0) + { + NSIndexPath *cellIndexPath = [NSIndexPath indexPathForRow:cellRow inSection:0]; + UITableViewCell *cell = [self.bubblesTableView cellForRowAtIndexPath:cellIndexPath]; + sourceView = cell; + + if ([cell isKindOfClass:[MXKRoomBubbleTableViewCell class]]) + { + MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell*)cell; + NSInteger bubbleComponentIndex = [roomBubbleTableViewCell.bubbleData bubbleComponentIndexForEventId:eventId]; + sourceRect = [roomBubbleTableViewCell componentFrameInContentViewForIndex:bubbleComponentIndex]; + } + + } + + [emojiPickerCoordinatorBridgePresenter presentFrom:self sourceView:sourceView sourceRect:sourceRect animated:YES]; + self.emojiPickerCoordinatorBridgePresenter = emojiPickerCoordinatorBridgePresenter; +} + #pragma mark - - (void)showEditHistoryForEventId:(NSString*)eventId animated:(BOOL)animated @@ -5401,5 +5415,46 @@ } } +#pragma mark - EmojiPickerCoordinatorBridgePresenterDelegate + +- (void)emojiPickerCoordinatorBridgePresenter:(EmojiPickerCoordinatorBridgePresenter *)coordinatorBridgePresenter didAddEmoji:(NSString *)emoji forEventId:(NSString *)eventId +{ + MXWeakify(self); + + [coordinatorBridgePresenter dismissWithAnimated:YES completion:^{ + [self.roomDataSource addReaction:emoji forEventId:eventId success:^{ + + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + + [self.errorPresenter presentErrorFromViewController:self forError:error animated:YES handler:nil]; + }]; + }]; + self.emojiPickerCoordinatorBridgePresenter = nil; +} + +- (void)emojiPickerCoordinatorBridgePresenter:(EmojiPickerCoordinatorBridgePresenter *)coordinatorBridgePresenter didRemoveEmoji:(NSString *)emoji forEventId:(NSString *)eventId +{ + MXWeakify(self); + + [coordinatorBridgePresenter dismissWithAnimated:YES completion:^{ + + [self.roomDataSource removeReaction:emoji forEventId:eventId success:^{ + + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + + [self.errorPresenter presentErrorFromViewController:self forError:error animated:YES handler:nil]; + }]; + }]; + self.emojiPickerCoordinatorBridgePresenter = nil; +} + +- (void)emojiPickerCoordinatorBridgePresenterDidCancel:(EmojiPickerCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + [coordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + self.emojiPickerCoordinatorBridgePresenter = nil; +} + @end From 3614a192055f0e227b23f6d5b8483c2513506534 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 25 Jul 2019 16:47:21 +0200 Subject: [PATCH 25/75] Podfile: Handle RiotTests target. --- Podfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Podfile b/Podfile index c75eeeea7..5b8cc5b54 100644 --- a/Podfile +++ b/Podfile @@ -79,6 +79,10 @@ abstract_target 'RiotPods' do target "Riot" do import_MatrixKit pod 'DGCollectionViewLeftAlignFlowLayout', '~> 1.0.4' + + target 'RiotTests' do + inherit! :search_paths + end end target "RiotShareExtension" do From 24a19a112d0dbc6baabafd6663a57197b0fc1ac2 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 25 Jul 2019 16:47:39 +0200 Subject: [PATCH 26/75] Update pbxproj --- Riot.xcodeproj/project.pbxproj | 197 +++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 13306ca78..143557997 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -101,6 +101,7 @@ 32F6B96C2270623100BBA352 /* DeviceVerificationDataLoadingCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9662270623100BBA352 /* DeviceVerificationDataLoadingCoordinatorType.swift */; }; 32F6B96D2270623100BBA352 /* DeviceVerificationDataLoadingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9672270623100BBA352 /* DeviceVerificationDataLoadingViewModel.swift */; }; 32F6B96E2270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F6B9682270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift */; }; + 3AF393339D2D566CE14AC200 /* Pods_RiotTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 129EB7E27E7E4AC3F5F098F5 /* Pods_RiotTests.framework */; }; 405FD41D306133A48D9B5AA1 /* Pods_RiotPods_Riot.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1ACF09217ADF1D7E7A35BC02 /* Pods_RiotPods_Riot.framework */; }; 670966FEFE120D865FD8A5B6 /* Pods_RiotPods_SiriIntents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51187E952D5CECF6D6F5A28E /* Pods_RiotPods_SiriIntents.framework */; }; 926FA53F1F4C132000F826C2 /* MXSession+Riot.m in Sources */ = {isa = PBXBuildFile; fileRef = 926FA53E1F4C132000F826C2 /* MXSession+Riot.m */; }; @@ -163,6 +164,8 @@ B14F143322144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14F142B22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModel.swift */; }; B14F143422144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14F142C22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewAction.swift */; }; B14F143522144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B14F142D22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift */; }; + B152C73122DF561E0041315A /* EmojiServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B152C73022DF561E0041315A /* EmojiServiceTests.swift */; }; + B152C73522DF599C0041315A /* apple_emojis_data.json in Resources */ = {isa = PBXBuildFile; fileRef = B152C73422DF599B0041315A /* apple_emojis_data.json */; }; B1664BC520F4E67600808783 /* FallbackViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1664BAD20F4E67500808783 /* FallbackViewController.xib */; }; B1664BC620F4E67600808783 /* FallbackViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1664BAE20F4E67500808783 /* FallbackViewController.m */; }; B1664BC720F4E67600808783 /* SharePresentingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B1664BB220F4E67500808783 /* SharePresentingViewController.m */; }; @@ -457,6 +460,9 @@ B1B5599320EFC5E400210D55 /* DecryptionFailure.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5598D20EFC5E400210D55 /* DecryptionFailure.m */; }; B1B5599420EFC5E400210D55 /* DecryptionFailureTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = B1B5599120EFC5E400210D55 /* DecryptionFailureTracker.m */; }; B1B9194C2118984300FE25B5 /* RoomPredecessorBubbleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1B9194A2118984300FE25B5 /* RoomPredecessorBubbleCell.xib */; }; + B1B9DEDA22E9B7350065E677 /* SerializationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DED922E9B7350065E677 /* SerializationService.swift */; }; + B1B9DEDC22E9B7440065E677 /* SerializationServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEDB22E9B7440065E677 /* SerializationServiceType.swift */; }; + B1B9DEDE22E9D9890065E677 /* EmojiServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEDD22E9D9890065E677 /* EmojiServiceType.swift */; }; B1C562CA2289C2690037F12A /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */; }; B1C562CC228AB3510037F12A /* UIStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562CB228AB3510037F12A /* UIStackView.swift */; }; B1C562D9228C0B760037F12A /* RoomContextualMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */; }; @@ -489,6 +495,30 @@ B1DB4F0C2231494F0065DBFA /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DB4F0A223131600065DBFA /* String.swift */; }; B1DB4F0E22316FFF0065DBFA /* UserNameColorGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DB4F0D22316FFF0065DBFA /* UserNameColorGenerator.swift */; }; B1DB4F0F223170000065DBFA /* UserNameColorGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DB4F0D22316FFF0065DBFA /* UserNameColorGenerator.swift */; }; + B1DCC61722E5E17100625807 /* EmojiPickerViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1DCC60F22E5E16900625807 /* EmojiPickerViewController.storyboard */; }; + B1DCC61822E5E17100625807 /* EmojiPickerViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC61022E5E16900625807 /* EmojiPickerViewState.swift */; }; + B1DCC61922E5E17100625807 /* EmojiPickerCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC61122E5E16A00625807 /* EmojiPickerCoordinatorType.swift */; }; + B1DCC61A22E5E17100625807 /* EmojiPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC61222E5E16C00625807 /* EmojiPickerViewController.swift */; }; + B1DCC61B22E5E17100625807 /* EmojiPickerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC61322E5E16D00625807 /* EmojiPickerCoordinator.swift */; }; + B1DCC61C22E5E17100625807 /* EmojiPickerViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC61422E5E16E00625807 /* EmojiPickerViewAction.swift */; }; + B1DCC61D22E5E17100625807 /* EmojiPickerViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC61522E5E16F00625807 /* EmojiPickerViewModelType.swift */; }; + B1DCC61E22E5E17100625807 /* EmojiPickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC61622E5E17000625807 /* EmojiPickerViewModel.swift */; }; + B1DCC62022E5EDA400625807 /* EmojiPickerCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC61F22E5EDA400625807 /* EmojiPickerCoordinatorBridgePresenter.swift */; }; + B1DCC62222E60BE000625807 /* EmojiPickerItemViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1DCC62122E60BE000625807 /* EmojiPickerItemViewData.swift */; }; + 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 */; }; + 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 */; }; + B1DCC63222E7026F00625807 /* EmojiPickerHeaderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1DCC63022E7026F00625807 /* EmojiPickerHeaderView.xib */; }; + 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 */; }; 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 */; }; @@ -558,6 +588,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 129EB7E27E7E4AC3F5F098F5 /* Pods_RiotTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RiotTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1ACF09217ADF1D7E7A35BC02 /* Pods_RiotPods_Riot.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RiotPods_Riot.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 24CBEC4E1F0EAD310093EABB /* RiotShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = RiotShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 3209451121F1C1430088CAA2 /* BlackTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlackTheme.swift; sourceTree = ""; }; @@ -679,6 +710,7 @@ 92726A501F587410004AD26F /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; 97151D7F0F892081250D50A3 /* Pods_RiotPods_RiotShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RiotPods_RiotShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9C3242E3FE95BCDA9562C75D /* Pods-RiotPods-RiotShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-RiotShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-RiotPods-RiotShareExtension/Pods-RiotPods-RiotShareExtension.debug.xcconfig"; sourceTree = ""; }; + AC34BF67FD21A9D01C16AE5D /* Pods-RiotTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotTests.release.xcconfig"; path = "Target Support Files/Pods-RiotTests/Pods-RiotTests.release.xcconfig"; sourceTree = ""; }; B104C2932203773B00D9F496 /* KeyBackupBannerPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupBannerPreferences.swift; sourceTree = ""; }; B1057788221304EB00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupSetupSuccessFromPassphraseViewController.swift; sourceTree = ""; }; B105778A221304FA00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = KeyBackupSetupSuccessFromPassphraseViewController.storyboard; sourceTree = ""; }; @@ -734,6 +766,9 @@ B14F142B22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromRecoveryKeyViewModel.swift; sourceTree = ""; }; B14F142C22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromRecoveryKeyViewAction.swift; sourceTree = ""; }; B14F142D22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromRecoveryKeyViewController.swift; sourceTree = ""; }; + B152C72D22DF55D80041315A /* RiotTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RiotTests-Bridging-Header.h"; sourceTree = ""; }; + B152C73022DF561E0041315A /* EmojiServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiServiceTests.swift; sourceTree = ""; }; + B152C73422DF599B0041315A /* apple_emojis_data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = apple_emojis_data.json; sourceTree = ""; }; B1664BAD20F4E67500808783 /* FallbackViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FallbackViewController.xib; sourceTree = ""; }; B1664BAE20F4E67500808783 /* FallbackViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FallbackViewController.m; sourceTree = ""; }; B1664BAF20F4E67500808783 /* FallbackViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FallbackViewController.h; sourceTree = ""; }; @@ -1212,6 +1247,9 @@ B1B5599020EFC5E400210D55 /* Analytics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Analytics.h; sourceTree = ""; }; B1B5599120EFC5E400210D55 /* DecryptionFailureTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DecryptionFailureTracker.m; sourceTree = ""; }; B1B9194A2118984300FE25B5 /* RoomPredecessorBubbleCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RoomPredecessorBubbleCell.xib; sourceTree = ""; }; + B1B9DED922E9B7350065E677 /* SerializationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerializationService.swift; sourceTree = ""; }; + B1B9DEDB22E9B7440065E677 /* SerializationServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerializationServiceType.swift; sourceTree = ""; }; + B1B9DEDD22E9D9890065E677 /* EmojiServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiServiceType.swift; sourceTree = ""; }; B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; }; B1C562CB228AB3510037F12A /* UIStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = ""; }; B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomContextualMenuItem.swift; sourceTree = ""; }; @@ -1242,11 +1280,35 @@ B1DB4F05223015080065DBFA /* Character.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Character.swift; sourceTree = ""; }; B1DB4F0A223131600065DBFA /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; B1DB4F0D22316FFF0065DBFA /* UserNameColorGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNameColorGenerator.swift; sourceTree = ""; }; + B1DCC60F22E5E16900625807 /* EmojiPickerViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = EmojiPickerViewController.storyboard; sourceTree = ""; }; + B1DCC61022E5E16900625807 /* EmojiPickerViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerViewState.swift; sourceTree = ""; }; + B1DCC61122E5E16A00625807 /* EmojiPickerCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerCoordinatorType.swift; sourceTree = ""; }; + B1DCC61222E5E16C00625807 /* EmojiPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerViewController.swift; sourceTree = ""; }; + B1DCC61322E5E16D00625807 /* EmojiPickerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerCoordinator.swift; sourceTree = ""; }; + B1DCC61422E5E16E00625807 /* EmojiPickerViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerViewAction.swift; sourceTree = ""; }; + B1DCC61522E5E16F00625807 /* EmojiPickerViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerViewModelType.swift; sourceTree = ""; }; + B1DCC61622E5E17000625807 /* EmojiPickerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerViewModel.swift; sourceTree = ""; }; + B1DCC61F22E5EDA400625807 /* EmojiPickerCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerCoordinatorBridgePresenter.swift; sourceTree = ""; }; + B1DCC62122E60BE000625807 /* EmojiPickerItemViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerItemViewData.swift; sourceTree = ""; }; + B1DCC62322E60CA800625807 /* EmojiPickerCategoryViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerCategoryViewData.swift; sourceTree = ""; }; + B1DCC62522E60CC600625807 /* EmojiItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItem.swift; sourceTree = ""; }; + B1DCC62722E60CE300625807 /* EmojiCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiCategory.swift; sourceTree = ""; }; + B1DCC62922E60D1000625807 /* EmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiService.swift; sourceTree = ""; }; + B1DCC62B22E61EAF00625807 /* EmojiPickerViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerViewCell.swift; sourceTree = ""; }; + B1DCC62C22E61EAF00625807 /* EmojiPickerViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EmojiPickerViewCell.xib; sourceTree = ""; }; + B1DCC62F22E7026F00625807 /* EmojiPickerHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerHeaderView.swift; sourceTree = ""; }; + B1DCC63022E7026F00625807 /* EmojiPickerHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EmojiPickerHeaderView.xib; sourceTree = ""; }; + B1DCC63322E72C1B00625807 /* UISearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISearchBar.swift; sourceTree = ""; }; + B1DCC63622E8541700625807 /* EmojiStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiStore.swift; sourceTree = ""; }; + B1DCC63822E85E9A00625807 /* EmojiJSONStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiJSONStore.swift; sourceTree = ""; }; + B1DCC63A22E85EF800625807 /* EmojiJSONCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiJSONCategory.swift; sourceTree = ""; }; + B1DCC63E22E9A3AE00625807 /* EmojiItem+Decodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EmojiItem+Decodable.swift"; sourceTree = ""; }; B1E5368821FB1E20001F3AFF /* UIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButton.swift; sourceTree = ""; }; B1E5368C21FB7245001F3AFF /* KeyBackupRecoverFromPassphraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewController.swift; sourceTree = ""; }; B1E5368E21FB7258001F3AFF /* KeyBackupRecoverFromPassphraseViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = KeyBackupRecoverFromPassphraseViewController.storyboard; sourceTree = ""; }; B1FDF55F21F5FE5500BA3834 /* KeyBackupSetupPassphraseViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupSetupPassphraseViewAction.swift; sourceTree = ""; }; B43DC75D1590BB8A4243BD4D /* Pods-RiotPods-Riot.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-Riot.release.xcconfig"; path = "Target Support Files/Pods-RiotPods-Riot/Pods-RiotPods-Riot.release.xcconfig"; sourceTree = ""; }; + BABB6681FBD79219B1213D6C /* Pods-RiotTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotTests.debug.xcconfig"; path = "Target Support Files/Pods-RiotTests/Pods-RiotTests.debug.xcconfig"; sourceTree = ""; }; E2599D0ECB8DD206624E450B /* Pods-RiotPods-SiriIntents.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-SiriIntents.release.xcconfig"; path = "Target Support Files/Pods-RiotPods-SiriIntents/Pods-RiotPods-SiriIntents.release.xcconfig"; sourceTree = ""; }; F05927C71FDED835009F2A68 /* MXGroup+Riot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MXGroup+Riot.m"; sourceTree = ""; }; F05927C81FDED835009F2A68 /* MXGroup+Riot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MXGroup+Riot.h"; sourceTree = ""; }; @@ -1317,6 +1379,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 3AF393339D2D566CE14AC200 /* Pods_RiotTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1601,6 +1664,8 @@ 4FC6A5D63FAD1B27C2F57AFA /* Pods-RiotPods-RiotShareExtension.release.xcconfig */, 3942DD65EBEB7AE647C6392A /* Pods-RiotPods-SiriIntents.debug.xcconfig */, E2599D0ECB8DD206624E450B /* Pods-RiotPods-SiriIntents.release.xcconfig */, + BABB6681FBD79219B1213D6C /* Pods-RiotTests.debug.xcconfig */, + AC34BF67FD21A9D01C16AE5D /* Pods-RiotTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -1612,6 +1677,7 @@ 1ACF09217ADF1D7E7A35BC02 /* Pods_RiotPods_Riot.framework */, 97151D7F0F892081250D50A3 /* Pods_RiotPods_RiotShareExtension.framework */, 51187E952D5CECF6D6F5A28E /* Pods_RiotPods_SiriIntents.framework */, + 129EB7E27E7E4AC3F5F098F5 /* Pods_RiotTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -1762,6 +1828,40 @@ path = RecoveryKey; sourceTree = ""; }; + B152C72922DCEA670041315A /* EmojiPicker */ = { + isa = PBXGroup; + children = ( + B1DCC61F22E5EDA400625807 /* EmojiPickerCoordinatorBridgePresenter.swift */, + B1DCC61322E5E16D00625807 /* EmojiPickerCoordinator.swift */, + B1DCC61122E5E16A00625807 /* EmojiPickerCoordinatorType.swift */, + B1DCC61422E5E16E00625807 /* EmojiPickerViewAction.swift */, + B1DCC61222E5E16C00625807 /* EmojiPickerViewController.swift */, + B1DCC60F22E5E16900625807 /* EmojiPickerViewController.storyboard */, + B1DCC61622E5E17000625807 /* EmojiPickerViewModel.swift */, + B1DCC61522E5E16F00625807 /* EmojiPickerViewModelType.swift */, + B1DCC61022E5E16900625807 /* EmojiPickerViewState.swift */, + B1DCC62322E60CA800625807 /* EmojiPickerCategoryViewData.swift */, + B1DCC62122E60BE000625807 /* EmojiPickerItemViewData.swift */, + B1DCC62F22E7026F00625807 /* EmojiPickerHeaderView.swift */, + B1DCC63022E7026F00625807 /* EmojiPickerHeaderView.xib */, + B1DCC62B22E61EAF00625807 /* EmojiPickerViewCell.swift */, + B1DCC62C22E61EAF00625807 /* EmojiPickerViewCell.xib */, + B152C72C22DF21880041315A /* Data */, + ); + path = EmojiPicker; + sourceTree = ""; + }; + B152C72C22DF21880041315A /* Data */ = { + isa = PBXGroup; + children = ( + B1B9DEDD22E9D9890065E677 /* EmojiServiceType.swift */, + B1DCC62922E60D1000625807 /* EmojiService.swift */, + B1DCC64022E9B37400625807 /* Store */, + B1B9DED722E9B56C0065E677 /* JSON */, + ); + path = Data; + sourceTree = ""; + }; B1664BAB20F4E67500808783 /* Modules */ = { isa = PBXGroup; children = ( @@ -2158,6 +2258,7 @@ B1B5569020EE6C4C00210D55 /* Settings */, B1C562D7228C0B4C0037F12A /* ContextualMenu */, B1963B24228F1C4800CBA17F /* BubbleReactions */, + B152C72922DCEA670041315A /* EmojiPicker */, ); path = Room; sourceTree = ""; @@ -3088,6 +3189,7 @@ B1B5597C20EFC3DF00210D55 /* Managers */ = { isa = PBXGroup; children = ( + B1B9DED822E9B7120065E677 /* Serialization */, B1FDF56321F68C0700BA3834 /* PasswordStrength */, B1798300211B137B001FD722 /* OnBoarding */, B1B5598B20EFC5E400210D55 /* Analytics */, @@ -3141,6 +3243,25 @@ path = Analytics; sourceTree = ""; }; + B1B9DED722E9B56C0065E677 /* JSON */ = { + isa = PBXGroup; + children = ( + B1DCC63822E85E9A00625807 /* EmojiJSONStore.swift */, + B1DCC63A22E85EF800625807 /* EmojiJSONCategory.swift */, + B1DCC63E22E9A3AE00625807 /* EmojiItem+Decodable.swift */, + ); + path = JSON; + sourceTree = ""; + }; + B1B9DED822E9B7120065E677 /* Serialization */ = { + isa = PBXGroup; + children = ( + B1B9DEDB22E9B7440065E677 /* SerializationServiceType.swift */, + B1B9DED922E9B7350065E677 /* SerializationService.swift */, + ); + path = Serialization; + sourceTree = ""; + }; B1C562D7228C0B4C0037F12A /* ContextualMenu */ = { isa = PBXGroup; children = ( @@ -3183,6 +3304,16 @@ path = KeyboardAvoiding; sourceTree = ""; }; + B1DCC64022E9B37400625807 /* Store */ = { + isa = PBXGroup; + children = ( + B1DCC63622E8541700625807 /* EmojiStore.swift */, + B1DCC62722E60CE300625807 /* EmojiCategory.swift */, + B1DCC62522E60CC600625807 /* EmojiItem.swift */, + ); + path = Store; + sourceTree = ""; + }; B1E5368A21FB6FC0001F3AFF /* Passphrase */ = { isa = PBXGroup; children = ( @@ -3224,6 +3355,7 @@ isa = PBXGroup; children = ( F083BB041E7005FD00A9B29C /* RiotTests.m */, + B152C73022DF561E0041315A /* EmojiServiceTests.swift */, F083BB071E70067700A9B29C /* Supporting Files */, ); path = RiotTests; @@ -3232,6 +3364,7 @@ F083BB071E70067700A9B29C /* Supporting Files */ = { isa = PBXGroup; children = ( + B152C72D22DF55D80041315A /* RiotTests-Bridging-Header.h */, F083BB031E7005FD00A9B29C /* Info.plist */, ); name = "Supporting Files"; @@ -3265,6 +3398,7 @@ 32935CB21F628B98006888C8 /* js */, F083BBEF1E7009EC00A9B29C /* Images.xcassets */, B169328620F3954A00746532 /* SharedImages.xcassets */, + B152C73422DF599B0041315A /* apple_emojis_data.json */, B169329320F39E6200746532 /* LaunchScreen.storyboard */, B169329520F39E6300746532 /* Main.storyboard */, F083BC0E1E7009EC00A9B29C /* third_party_licenses.html */, @@ -3316,6 +3450,7 @@ B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */, B1C562CB228AB3510037F12A /* UIStackView.swift */, B1B12B2822942315002CB419 /* UITouch.swift */, + B1DCC63322E72C1B00625807 /* UISearchBar.swift */, ); path = Categories; sourceTree = ""; @@ -3451,6 +3586,7 @@ isa = PBXNativeTarget; buildConfigurationList = F094A9CB1B78D8F000B1FBBF /* Build configuration list for PBXNativeTarget "RiotTests" */; buildPhases = ( + CB374E980E51D4490F17AD40 /* [CP] Check Pods Manifest.lock */, F094A9BA1B78D8F000B1FBBF /* Sources */, F094A9BB1B78D8F000B1FBBF /* Frameworks */, F094A9BC1B78D8F000B1FBBF /* Resources */, @@ -3515,6 +3651,7 @@ F094A9BD1B78D8F000B1FBBF = { CreatedOnToolsVersion = 6.2; DevelopmentTeam = 7J4U792NQT; + LastSwiftMigration = 1020; TestTargetID = F094A9A11B78D8F000B1FBBF; }; }; @@ -3585,6 +3722,7 @@ B1B5590220EF768F00210D55 /* RoomOutgoingAttachmentWithPaginationTitleBubbleCell.xib in Resources */, B1B558CA20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib in Resources */, 32891D702264DF7B00C82226 /* DeviceVerificationVerifiedViewController.storyboard in Resources */, + B152C73522DF599C0041315A /* apple_emojis_data.json in Resources */, F083BDE91E7009ED00A9B29C /* ring.mp3 in Resources */, B1B558F720EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib in Resources */, B1B5592B20EF7A5D00210D55 /* TableViewCellWithButton.xib in Resources */, @@ -3650,6 +3788,7 @@ 3232AB2122564D9100AD6A5C /* README.md in Resources */, B1B5593920EF7BAC00210D55 /* TableViewCellWithCheckBoxes.xib in Resources */, B1B557C120EF5B4500210D55 /* DisabledRoomInputToolbarView.xib in Resources */, + B1DCC61722E5E17100625807 /* EmojiPickerViewController.storyboard in Resources */, 32891D6C2264CBA300C82226 /* SimpleScreenTemplateViewController.storyboard in Resources */, B1963B2C228F1C4900CBA17F /* BubbleReactionViewCell.xib in Resources */, B1664DA320F4F96200808783 /* Vector.strings in Resources */, @@ -3667,6 +3806,7 @@ 3232AB4B2256558300AD6A5C /* TemplateScreenViewController.storyboard in Resources */, B1D1BDA822BBAFC900831367 /* ReactionsMenuView.xib in Resources */, 32B1FEDB21A46F2C00637127 /* TermsView.xib in Resources */, + B1DCC63222E7026F00625807 /* EmojiPickerHeaderView.xib in Resources */, B1B5578E20EF568D00210D55 /* GroupInviteTableViewCell.xib in Resources */, B1B5582020EF625800210D55 /* SimpleRoomTitleView.xib in Resources */, F083BE061E7009ED00A9B29C /* Riot-Defaults.plist in Resources */, @@ -3689,6 +3829,7 @@ B1098BF821ECFE65000DDA48 /* KeyBackupSetupPassphraseViewController.storyboard in Resources */, F083BDF31E7009ED00A9B29C /* Images.xcassets in Resources */, B1C562E4228C7C8D0037F12A /* RoomContextualMenuToolbarView.xib in Resources */, + B1DCC62E22E61EAF00625807 /* EmojiPickerViewCell.xib in Resources */, B1B5590720EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.xib in Resources */, B169329920F39E6300746532 /* LaunchScreen.storyboard in Resources */, B1B5595320EF9A8700210D55 /* RecentTableViewCell.xib in Resources */, @@ -3839,6 +3980,28 @@ shellPath = /bin/sh; shellScript = "${PODS_ROOT}/SwiftLint/swiftlint\n"; }; + CB374E980E51D4490F17AD40 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RiotTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; FF06981FDA0DB688B8C52A41 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -3886,6 +4049,7 @@ 32242F0A21E8B21300725742 /* UIColor.swift in Sources */, 32242F1921E8FBFB00725742 /* DarkTheme.swift in Sources */, B1664BC820F4E67600808783 /* ShareDataSource.m in Sources */, + B1DCC63522E72D2200625807 /* UISearchBar.swift in Sources */, B1664BCD20F4E67600808783 /* RecentRoomTableViewCell.m in Sources */, B1DB4F0C2231494F0065DBFA /* String.swift in Sources */, B169331720F3CBE000746532 /* RecentCellData.m in Sources */, @@ -3917,6 +4081,7 @@ B16932A520F3A21C00746532 /* empty.mm in Sources */, 3232AB4A2256558300AD6A5C /* FlowTemplateCoordinator.swift in Sources */, B19EFA3B21F8BB4100FC070E /* KeyBackupRecoverCoordinator.swift in Sources */, + B1B9DEDE22E9D9890065E677 /* EmojiServiceType.swift in Sources */, 3232ABA9225730E100AD6A5C /* DeviceVerificationStartViewModel.swift in Sources */, B16932FA20F3C51A00746532 /* RecentCellData.m in Sources */, B16932F220F3C49E00746532 /* GroupsDataSource.m in Sources */, @@ -3926,6 +4091,7 @@ B1B5598820EFC3E000210D55 /* WidgetManager.m in Sources */, B1DB4F0E22316FFF0065DBFA /* UserNameColorGenerator.swift in Sources */, B1057789221304EC00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.swift in Sources */, + B1DCC61922E5E17100625807 /* EmojiPickerCoordinatorType.swift in Sources */, 3232ABB72257BE6400AD6A5C /* DeviceVerificationVerifyViewModelType.swift in Sources */, 32F6B96D2270623100BBA352 /* DeviceVerificationDataLoadingViewModel.swift in Sources */, B16932B120F3AC9200746532 /* RoomSearchDataSource.m in Sources */, @@ -3957,6 +4123,8 @@ 3232ABA3225730E100AD6A5C /* DeviceVerificationStartCoordinatorType.swift in Sources */, 3232AB4D2256558300AD6A5C /* TemplateScreenCoordinatorType.swift in Sources */, B1B5581720EF625800210D55 /* PreviewRoomTitleView.m in Sources */, + B1DCC63F22E9A3AE00625807 /* EmojiItem+Decodable.swift in Sources */, + B1DCC61C22E5E17100625807 /* EmojiPickerViewAction.swift in Sources */, B1098BDF21ECE09F000DDA48 /* Strings.swift in Sources */, B1B558C420EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, 3232ABC022594C0900AD6A5C /* VerifyEmojiCollectionViewCell.swift in Sources */, @@ -3970,15 +4138,18 @@ B14F143422144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewAction.swift in Sources */, B1098BF621ECFE65000DDA48 /* KeyBackupSetupPassphraseCoordinator.swift in Sources */, B1B558C320EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.m in Sources */, + B1B9DEDC22E9B7440065E677 /* SerializationServiceType.swift in Sources */, B110872521F098F0003554A5 /* ActivityIndicatorPresenter.swift in Sources */, 32242F1521E8FBA900725742 /* DarkTheme.swift in Sources */, B1D211E222BD193C00D939BD /* ReactionsMenuViewModel.swift in Sources */, B140B4A621F89E7600E3F5FE /* KeyBackupSetupCoordinatorBridgePresenter.swift in Sources */, B1B5577420EE702900210D55 /* WidgetViewController.m in Sources */, + B1DCC63122E7026F00625807 /* EmojiPickerHeaderView.swift in Sources */, B139C21B21FE5B9200BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift in Sources */, 32A6001E22C661100042C1D9 /* EditHistoryCoordinatorBridgePresenter.swift in Sources */, B1B5574A20EE6C4D00210D55 /* MediaPickerViewController.m in Sources */, B1B5598520EFC3E000210D55 /* RageShakeManager.m in Sources */, + B1DCC62D22E61EAF00625807 /* EmojiPickerViewCell.swift in Sources */, 3232ABA8225730E100AD6A5C /* DeviceVerificationStartViewState.swift in Sources */, B1B558D420EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */, B169331420F3CAFC00746532 /* PublicRoomTableViewCell.m in Sources */, @@ -4029,6 +4200,7 @@ B1D4752821EE4E630067973F /* KeyboardNotification.swift in Sources */, B1D1BDA622BBAFB500831367 /* ReactionsMenuView.swift in Sources */, B1B5573C20EE6C4D00210D55 /* MasterTabBarController.m in Sources */, + B1DCC61B22E5E17100625807 /* EmojiPickerCoordinator.swift in Sources */, 32F6B96E2270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift in Sources */, B1B5592C20EF7A5D00210D55 /* TableViewCellWithButton.m in Sources */, 32242F1421E8FBA900725742 /* DefaultTheme.swift in Sources */, @@ -4040,16 +4212,20 @@ B1B5573620EE6C4D00210D55 /* GroupsViewController.m in Sources */, 3232ABB82257BE6500AD6A5C /* DeviceVerificationVerifyCoordinator.swift in Sources */, B142317A22CCFA2000FFA96A /* EditHistoryCell.swift in Sources */, + B1DCC62622E60CC600625807 /* EmojiItem.swift in Sources */, B1B5572A20EE6C4D00210D55 /* RoomMemberDetailsViewController.m in Sources */, B1B5590120EF768F00210D55 /* RoomMembershipExpandedWithPaginationTitleBubbleCell.m in Sources */, 32F6B96B2270623100BBA352 /* DeviceVerificationDataLoadingViewAction.swift in Sources */, B1B558C920EF768F00210D55 /* RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.m in Sources */, B1B5571B20EE6C4D00210D55 /* DeactivateAccountViewController.m in Sources */, + B1DCC63922E85E9A00625807 /* EmojiJSONStore.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 */, 3232AB4F2256558300AD6A5C /* TemplateScreenViewController.swift in Sources */, B1B558FC20EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleBubbleCell.m in Sources */, B1B5572920EE6C4D00210D55 /* RoomFilesViewController.m in Sources */, @@ -4068,7 +4244,9 @@ 3232ABAB225730E100AD6A5C /* DeviceVerificationCoordinator.swift in Sources */, B1B5583E20EF6E7F00210D55 /* GroupRoomTableViewCell.m in Sources */, B14F143522144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift in Sources */, + B1DCC61E22E5E17100625807 /* EmojiPickerViewModel.swift in Sources */, B1B5574F20EE6C4D00210D55 /* RoomsViewController.m in Sources */, + B1B9DEDA22E9B7350065E677 /* SerializationService.swift in Sources */, B1B5572520EE6C4D00210D55 /* RoomMessagesSearchViewController.m in Sources */, B139C22121FE5D9D00BB68EC /* KeyBackupRecoverFromPassphraseViewState.swift in Sources */, B1B5579120EF568D00210D55 /* GroupInviteTableViewCell.m in Sources */, @@ -4076,6 +4254,7 @@ B1B12B2922942315002CB419 /* UITouch.swift in Sources */, B1B558CC20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.m in Sources */, B1B5571D20EE6C4D00210D55 /* HomeViewController.m in Sources */, + B1DCC63722E8541700625807 /* EmojiStore.swift in Sources */, 3232ABA6225730E100AD6A5C /* DeviceVerificationStartViewController.swift in Sources */, B16932EA20F3C39000746532 /* UnifiedSearchRecentsDataSource.m in Sources */, B1B557DE20EF5FBB00210D55 /* FilesSearchTableViewCell.m in Sources */, @@ -4086,6 +4265,7 @@ B1098BFE21ECFE65000DDA48 /* KeyBackupSetupPassphraseViewModelType.swift in Sources */, B1B558BE20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.m in Sources */, F083BDED1E7009ED00A9B29C /* MXKRoomBubbleTableViewCell+Riot.m in Sources */, + B1DCC62022E5EDA400625807 /* EmojiPickerCoordinatorBridgePresenter.swift in Sources */, B1B557A820EF5A1B00210D55 /* DeviceTableViewCell.m in Sources */, B1B5572620EE6C4D00210D55 /* RoomFilesSearchViewController.m in Sources */, B1B5583120EF66BA00210D55 /* RoomIdOrAliasTableViewCell.m in Sources */, @@ -4094,10 +4274,12 @@ F083BDFA1E7009ED00A9B29C /* RoomPreviewData.m in Sources */, B1B557B420EF5AEF00210D55 /* EventDetailsView.m in Sources */, B1B5577E20EE84BF00210D55 /* IncomingCallView.m in Sources */, + B1DCC62822E60CE300625807 /* EmojiCategory.swift in Sources */, B1B5578F20EF568D00210D55 /* GroupTableViewCell.m in Sources */, B1B5573220EE6C4D00210D55 /* GroupHomeViewController.m in Sources */, B1B5595220EF9A8700210D55 /* RecentTableViewCell.m in Sources */, 32F6B96C2270623100BBA352 /* DeviceVerificationDataLoadingCoordinatorType.swift in Sources */, + B1DCC61D22E5E17100625807 /* EmojiPickerViewModelType.swift in Sources */, B1B5574120EE6C4D00210D55 /* RecentsViewController.m in Sources */, B1D250D82118AA0A000F4E93 /* RoomPredecessorBubbleCell.m in Sources */, B1B5577120EE702800210D55 /* StickerPickerViewController.m in Sources */, @@ -4116,6 +4298,7 @@ B1798302211B13B3001FD722 /* OnBoardingManager.swift in Sources */, B1B5573520EE6C4D00210D55 /* GroupDetailsViewController.m in Sources */, B10B3B5B2201DD740072C76B /* KeyBackupBannerCell.swift in Sources */, + B1DCC61A22E5E17100625807 /* EmojiPickerViewController.swift in Sources */, B1963B32228F1C6B00CBA17F /* BubbleReactionsViewModelType.swift in Sources */, 32A6001722C661100042C1D9 /* EditHistoryViewController.swift in Sources */, B1098BFA21ECFE65000DDA48 /* KeyBackupSetupPassphraseViewModel.swift in Sources */, @@ -4144,10 +4327,12 @@ B1B5575A20EE6C4D00210D55 /* UnifiedSearchViewController.m in Sources */, 3232AB492256558300AD6A5C /* FlowTemplateCoordinatorBridgePresenter.swift in Sources */, B1B5572820EE6C4D00210D55 /* RoomViewController.m in Sources */, + B1DCC62A22E60D1000625807 /* EmojiService.swift in Sources */, B1B558C720EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */, B1B558F020EF768F00210D55 /* RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m in Sources */, 926FA53F1F4C132000F826C2 /* MXSession+Riot.m in Sources */, B1B5593820EF7BAC00210D55 /* TableViewCellWithLabelAndLargeTextView.m in Sources */, + B1DCC62222E60BE000625807 /* EmojiPickerItemViewData.swift in Sources */, 3232AB502256558300AD6A5C /* TemplateScreenViewState.swift in Sources */, B1B558C820EF768F00210D55 /* RoomIncomingEncryptedAttachmentBubbleCell.m in Sources */, B1B557C620EF5CD400210D55 /* DirectoryServerDetailTableViewCell.m in Sources */, @@ -4155,6 +4340,7 @@ 324A2054225FC571004FE8B0 /* DeviceVerificationIncomingCoordinatorType.swift in Sources */, 3232ABB92257BE6500AD6A5C /* DeviceVerificationVerifyViewController.swift in Sources */, B139C21F21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift in Sources */, + B1DCC61822E5E17100625807 /* EmojiPickerViewState.swift in Sources */, B1C562DB228C0BB00037F12A /* RoomContextualMenuAction.swift in Sources */, B1B5574720EE6C4D00210D55 /* UsersDevicesViewController.m in Sources */, B1098BFF21ECFE65000DDA48 /* PasswordStrengthView.swift in Sources */, @@ -4229,6 +4415,7 @@ B1C562E8228C7CF20037F12A /* ContextualMenuItemView.swift in Sources */, B14F143022144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift in Sources */, B1E5368921FB1E20001F3AFF /* UIButton.swift in Sources */, + B1DCC63422E72C1B00625807 /* UISearchBar.swift in Sources */, 32A6001622C661100042C1D9 /* EditHistoryViewState.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4238,6 +4425,7 @@ buildActionMask = 2147483647; files = ( F083BEA51E70356E00A9B29C /* RiotTests.m in Sources */, + B152C73122DF561E0041315A /* EmojiServiceTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4664,9 +4852,11 @@ }; F094A9CC1B78D8F000B1FBBF /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = BABB6681FBD79219B1213D6C /* Pods-RiotTests.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; DEVELOPMENT_TEAM = 7J4U792NQT; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", @@ -4684,15 +4874,20 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "org.matrix.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "RiotTests/RiotTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Riot.app/Riot"; }; name = Debug; }; F094A9CD1B78D8F000B1FBBF /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = AC34BF67FD21A9D01C16AE5D /* Pods-RiotTests.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; DEVELOPMENT_TEAM = 7J4U792NQT; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", @@ -4706,6 +4901,8 @@ ); PRODUCT_BUNDLE_IDENTIFIER = "org.matrix.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "RiotTests/RiotTests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Riot.app/Riot"; }; name = Release; From b53789a601bf88626caa463ccc96f6b21ae80b85 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 25 Jul 2019 16:58:54 +0200 Subject: [PATCH 27/75] Update changes --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index a09fb1333..34b9390db 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,7 @@ Changes in 0.9.2 (2019-07-) Improvements: * Upgrade MatrixKit version ([v0.10.2](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.10.2)). * Soft logout: Support soft logout (#2540). + * Reactions: Emoji picker (#2370). Changes in 0.9.1 (2019-07-17) =============================================== From 46434d7596abd8cbaf57d98630efd0844cc0c9e9 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Thu, 25 Jul 2019 19:16:15 +0200 Subject: [PATCH 28/75] Update Riot/Modules/Room/RoomViewController.m Co-Authored-By: manuroe --- Riot/Modules/Room/RoomViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 4518abd77..e40e31cd7 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -5329,7 +5329,7 @@ UIView *sourceView; CGRect sourceRect = CGRectNull; - if (cellRow > 0) + if (cellRow >= 0) { NSIndexPath *cellIndexPath = [NSIndexPath indexPathForRow:cellRow inSection:0]; UITableViewCell *cell = [self.bubblesTableView cellForRowAtIndexPath:cellIndexPath]; From ca7779ae5882ebd8ed9ca5dec3cde170db2b3928 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 26 Jul 2019 14:06:19 +0200 Subject: [PATCH 29/75] Emoji picker: Handle Manu's comments. --- Riot.xcodeproj/project.pbxproj | 38 +++++------ Riot/Assets/third_party_licenses.html | 17 +++++ .../EmojiItem+EmojiMart.swift} | 23 +++++-- .../{JSON => EmojiMart}/EmojiJSONStore.swift | 8 ++- .../EmojiMartCategory.swift} | 15 +++-- .../EmojiMartService.swift} | 16 +++-- .../Data/EmojiMart/EmojiMartStore.swift | 67 +++++++++++++++++++ .../Data/Store/EmojiCategory.swift | 5 ++ .../EmojiPicker/Data/Store/EmojiItem.swift | 24 +++++-- .../EmojiPickerCoordinatorType.swift | 2 +- .../EmojiPicker/EmojiPickerViewModel.swift | 31 +++++---- RiotTests/EmojiServiceTests.swift | 4 +- 12 files changed, 191 insertions(+), 59 deletions(-) rename Riot/Modules/Room/EmojiPicker/Data/{JSON/EmojiItem+Decodable.swift => EmojiMart/EmojiItem+EmojiMart.swift} (68%) rename Riot/Modules/Room/EmojiPicker/Data/{JSON => EmojiMart}/EmojiJSONStore.swift (86%) rename Riot/Modules/Room/EmojiPicker/Data/{JSON/EmojiJSONCategory.swift => EmojiMart/EmojiMartCategory.swift} (65%) rename Riot/Modules/Room/EmojiPicker/Data/{EmojiService.swift => EmojiMart/EmojiMartService.swift} (76%) create mode 100644 Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiMartStore.swift diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 143557997..3b954774a 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -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 = ""; }; B1DCC62522E60CC600625807 /* EmojiItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItem.swift; sourceTree = ""; }; B1DCC62722E60CE300625807 /* EmojiCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiCategory.swift; sourceTree = ""; }; - B1DCC62922E60D1000625807 /* EmojiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiService.swift; sourceTree = ""; }; + B1DCC62922E60D1000625807 /* EmojiMartService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartService.swift; sourceTree = ""; }; B1DCC62B22E61EAF00625807 /* EmojiPickerViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerViewCell.swift; sourceTree = ""; }; B1DCC62C22E61EAF00625807 /* EmojiPickerViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EmojiPickerViewCell.xib; sourceTree = ""; }; B1DCC62F22E7026F00625807 /* EmojiPickerHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerHeaderView.swift; sourceTree = ""; }; B1DCC63022E7026F00625807 /* EmojiPickerHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EmojiPickerHeaderView.xib; sourceTree = ""; }; B1DCC63322E72C1B00625807 /* UISearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISearchBar.swift; sourceTree = ""; }; B1DCC63622E8541700625807 /* EmojiStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiStore.swift; sourceTree = ""; }; - B1DCC63822E85E9A00625807 /* EmojiJSONStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiJSONStore.swift; sourceTree = ""; }; - B1DCC63A22E85EF800625807 /* EmojiJSONCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiJSONCategory.swift; sourceTree = ""; }; - B1DCC63E22E9A3AE00625807 /* EmojiItem+Decodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EmojiItem+Decodable.swift"; sourceTree = ""; }; + B1DCC63822E85E9A00625807 /* EmojiMartStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartStore.swift; sourceTree = ""; }; + B1DCC63A22E85EF800625807 /* EmojiMartCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartCategory.swift; sourceTree = ""; }; + B1DCC63E22E9A3AE00625807 /* EmojiItem+EmojiMart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EmojiItem+EmojiMart.swift"; sourceTree = ""; }; B1E5368821FB1E20001F3AFF /* UIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButton.swift; sourceTree = ""; }; B1E5368C21FB7245001F3AFF /* KeyBackupRecoverFromPassphraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewController.swift; sourceTree = ""; }; B1E5368E21FB7258001F3AFF /* KeyBackupRecoverFromPassphraseViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = KeyBackupRecoverFromPassphraseViewController.storyboard; sourceTree = ""; }; @@ -1855,9 +1855,8 @@ isa = PBXGroup; children = ( B1B9DEDD22E9D9890065E677 /* EmojiServiceType.swift */, - B1DCC62922E60D1000625807 /* EmojiService.swift */, B1DCC64022E9B37400625807 /* Store */, - B1B9DED722E9B56C0065E677 /* JSON */, + B1B9DED722E9B56C0065E677 /* EmojiMart */, ); path = Data; sourceTree = ""; @@ -3243,14 +3242,15 @@ path = Analytics; sourceTree = ""; }; - 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 = ""; }; 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 */, diff --git a/Riot/Assets/third_party_licenses.html b/Riot/Assets/third_party_licenses.html index 5f067b63d..6499f7712 100644 --- a/Riot/Assets/third_party_licenses.html +++ b/Riot/Assets/third_party_licenses.html @@ -1359,6 +1359,23 @@ href="https://www.apache.org/licenses/LICENSE-2.0">https://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. +
  • +

    Emoji Mart (https://github.com/missive/emoji-mart)

    +
    +            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.
    +        
    +
  • diff --git a/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiItem+Decodable.swift b/Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiItem+EmojiMart.swift similarity index 68% rename from Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiItem+Decodable.swift rename to Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiItem+EmojiMart.swift index 18bb1c0ae..b04872d8b 100644 --- a/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiItem+Decodable.swift +++ b/Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiItem+EmojiMart.swift @@ -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, diff --git a/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONStore.swift b/Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiJSONStore.swift similarity index 86% rename from Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONStore.swift rename to Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiJSONStore.swift index 2db3ac2f1..73913dcbf 100644 --- a/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONStore.swift +++ b/Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiJSONStore.swift @@ -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 } diff --git a/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONCategory.swift b/Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiMartCategory.swift similarity index 65% rename from Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONCategory.swift rename to Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiMartCategory.swift index 87a64a817..306bae4cf 100644 --- a/Riot/Modules/Room/EmojiPicker/Data/JSON/EmojiJSONCategory.swift +++ b/Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiMartCategory.swift @@ -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" } } diff --git a/Riot/Modules/Room/EmojiPicker/Data/EmojiService.swift b/Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiMartService.swift similarity index 76% rename from Riot/Modules/Room/EmojiPicker/Data/EmojiService.swift rename to Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiMartService.swift index 7fbfab4f6..962114635 100644 --- a/Riot/Modules/Room/EmojiPicker/Data/EmojiService.swift +++ b/Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiMartService.swift @@ -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) } diff --git a/Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiMartStore.swift b/Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiMartStore.swift new file mode 100644 index 000000000..a83e3373d --- /dev/null +++ b/Riot/Modules/Room/EmojiPicker/Data/EmojiMart/EmojiMartStore.swift @@ -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) + } +} diff --git a/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiCategory.swift b/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiCategory.swift index 6adc37d25..a7c439fbc 100644 --- a/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiCategory.swift +++ b/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiCategory.swift @@ -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) diff --git a/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiItem.swift b/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiItem.swift index ad2c311c9..257517e82 100644 --- a/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiItem.swift +++ b/Riot/Modules/Room/EmojiPicker/Data/Store/EmojiItem.swift @@ -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 diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorType.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorType.swift index 3ae5fd900..e414ea29b 100644 --- a/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorType.swift +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinatorType.swift @@ -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 } } diff --git a/Riot/Modules/Room/EmojiPicker/EmojiPickerViewModel.swift b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewModel.swift index 6f83eb7ee..514ab365b 100644 --- a/Riot/Modules/Room/EmojiPicker/EmojiPickerViewModel.swift +++ b/Riot/Modules/Room/EmojiPicker/EmojiPickerViewModel.swift @@ -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) } diff --git a/RiotTests/EmojiServiceTests.swift b/RiotTests/EmojiServiceTests.swift index d1541d44d..b4f6971af 100644 --- a/RiotTests/EmojiServiceTests.swift +++ b/RiotTests/EmojiServiceTests.swift @@ -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) From d9271adc3c94078626e8c7b4ce2542c2c9bb8a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Thu, 25 Jul 2019 08:46:25 +0000 Subject: [PATCH 30/75] Translated using Weblate (French) Currently translated at 100.0% (727 of 727 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index ec1d923b5..c210ec443 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -789,3 +789,14 @@ // MARK: File upload "file_upload_error_title" = "Envoi de fichier"; "file_upload_error_unsupported_file_type_message" = "Type de fichier non pris en charge."; +"auth_softlogout_signed_out" = "Vous êtes déconnecté(e)"; +"auth_softlogout_sign_in" = "Se connecter"; +"auth_softlogout_reason" = "L’administrateur de votre serveur d’accueil (%1$@) vous a déconnecté de votre compte %2$@ (%3$@)."; +"auth_softlogout_recover_encryption_keys" = "Connectez-vous pour récupérer les clés de chiffrement stockées uniquement sur cet appareil. Vous en avez besoin pour lire tous les messages sécurisés sur n’importe quel appareil."; +"auth_softlogout_clear_data" = "Effacer les données personnelles"; +"auth_softlogout_clear_data_message_1" = "Attention : Vos données personnelles (y compris vos clés de chiffrement) sont toujours stockées sur cet appareil."; +"auth_softlogout_clear_data_message_2" = "Effacez-les si vous n’utilisez plus cet appareil ou si vous voulez vous connecter avec un autre compte."; +"auth_softlogout_clear_data_button" = "Effacer toutes les données"; +"auth_softlogout_clear_data_sign_out_title" = "En êtes-vous sûr(e) ?"; +"auth_softlogout_clear_data_sign_out_msg" = "Voulez vous vraiment supprimer toutes les données stockées actuellement sur cet appareil ? Reconnectez-vous pour accéder aux données et messages de votre compte."; +"auth_softlogout_clear_data_sign_out" = "Se déconnecter"; From 5ceecb013132fba254b1de75c1780d7834524e27 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Thu, 25 Jul 2019 06:29:20 +0000 Subject: [PATCH 31/75] Translated using Weblate (Hungarian) Currently translated at 100.0% (727 of 727 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 1a0be2dff..66c4b5800 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -794,3 +794,14 @@ // MARK: File upload "file_upload_error_title" = "Fájl feltöltés"; "file_upload_error_unsupported_file_type_message" = "A fájl típusa nem támogatott."; +"auth_softlogout_signed_out" = "Kijelentkeztél"; +"auth_softlogout_sign_in" = "Bejelentkezés"; +"auth_softlogout_reason" = "A matrix szerver (%1$@) adminisztrátora kiléptetett a felhasználói fiókodból %2$@ (%3$@)."; +"auth_softlogout_recover_encryption_keys" = "A csak ezen az eszközön meglévő titkosítási kulcsokhoz való hozzáféréshez be kell jelentkezned. Ahhoz hogy bármelyik eszközön elolvashasd a titkosított üzeneteidet szükséged lesz rájuk."; +"auth_softlogout_clear_data" = "Személyes adatok törlése"; +"auth_softlogout_clear_data_message_1" = "Figyelmeztetés: A személyes adataid (beleértve a titkosítási kulcsokat) továbbra is az eszközön tárolódnak."; +"auth_softlogout_clear_data_message_2" = "Ha már nem akarod használni ezt az eszközt vagy másik fiókba szeretnél bejelentkezni akkor töröld őket."; +"auth_softlogout_clear_data_button" = "Minden adat törlése"; +"auth_softlogout_clear_data_sign_out_title" = "Biztos vagy benne?"; +"auth_softlogout_clear_data_sign_out_msg" = "Biztos vagy benne, hogy minden az eszközön tárolt adatot törölni szeretnél? A fiók és az üzeneteid eléréséhez jelentkezz be."; +"auth_softlogout_clear_data_sign_out" = "Kilépés"; From 65259a81e6bf032151dcfa290ed772534e53500c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=81uczy=C5=84ski?= Date: Thu, 25 Jul 2019 11:49:27 +0000 Subject: [PATCH 32/75] Translated using Weblate (Polish) Currently translated at 67.1% (488 of 727 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/pl/ --- Riot/Assets/pl.lproj/Vector.strings | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Riot/Assets/pl.lproj/Vector.strings b/Riot/Assets/pl.lproj/Vector.strings index 4df9c322f..1125c4dff 100644 --- a/Riot/Assets/pl.lproj/Vector.strings +++ b/Riot/Assets/pl.lproj/Vector.strings @@ -510,3 +510,19 @@ "settings_on_denied_notification" = "Powiadomienia są odrzucane w %@, proszę zezwól na nie w ustawieniach urządzenia"; "settings_callkit_info" = "Odbieraj połączenia przychodzące na ekranie blokady. Zobacz swoje połęczenia Riot w historii połączeń w systemie. Jeśli usługa iCloud jest włączona, historia połączeń zostanie udostępniona Apple."; "settings_ui_theme_picker_message" = "\"Auto\" używa ustawienia \"Odwróć kolory\" urządzenia"; +"close" = "Zamknij"; +"auth_softlogout_sign_in" = "Zaloguj się"; +"auth_softlogout_clear_data_button" = "Wyczyść wszystkie dane"; +"auth_softlogout_clear_data_sign_out" = "Wyloguj się"; +"room_event_action_reaction_show_all" = "Pokaż wszystko"; +"room_event_action_reaction_show_less" = "Pokaż mniej"; +"room_action_send_file" = "Wyślij plik"; +"room_message_edits_history_title" = "Edycje wiadomości"; +"settings_enable_callkit" = "Zintegrowane połączenie"; +"settings_key_backup_info_checking" = "Sprawdzanie..."; +"settings_key_backup_info_version" = "Wersja kopii zapasowej kluczy: %@"; +"settings_key_backup_info_algorithm" = "Algorytm: %@"; +"settings_key_backup_info_progress" = "Tworzenie kopii zapasowej %@ kluczy..."; +"settings_key_backup_info_progress_done" = "Utworzono kopię zapasową wszystkich kluczy"; +"settings_key_backup_button_delete" = "Usuń kopię zapasową"; +"settings_key_backup_delete_confirmation_prompt_title" = "Usuń kopię zapasową"; From c4efa328a68458241e721761c43791735437abbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Sat, 27 Jul 2019 08:32:15 +0000 Subject: [PATCH 33/75] Translated using Weblate (French) Currently translated at 100.0% (736 of 736 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index c210ec443..964ffc4aa 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -800,3 +800,13 @@ "auth_softlogout_clear_data_sign_out_title" = "En êtes-vous sûr(e) ?"; "auth_softlogout_clear_data_sign_out_msg" = "Voulez vous vraiment supprimer toutes les données stockées actuellement sur cet appareil ? Reconnectez-vous pour accéder aux données et messages de votre compte."; "auth_softlogout_clear_data_sign_out" = "Se déconnecter"; +// MARK: Emoji picker +"emoji_picker_title" = "Réactions"; +"emoji_picker_people_category" = "Émoticônes et personnes"; +"emoji_picker_nature_category" = "Animaux et nature"; +"emoji_picker_foods_category" = "Nourriture et boisson"; +"emoji_picker_activity_category" = "Activités"; +"emoji_picker_places_category" = "Voyage et lieux"; +"emoji_picker_objects_category" = "Objets"; +"emoji_picker_symbols_category" = "Symboles"; +"emoji_picker_flags_category" = "Drapeaux"; From 0c8a931a8351d984d7e111df8e9936772f68169e Mon Sep 17 00:00:00 2001 From: Szimszon Date: Fri, 26 Jul 2019 13:49:35 +0000 Subject: [PATCH 34/75] Translated using Weblate (Hungarian) Currently translated at 100.0% (736 of 736 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 66c4b5800..a96feba6f 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -805,3 +805,13 @@ "auth_softlogout_clear_data_sign_out_title" = "Biztos vagy benne?"; "auth_softlogout_clear_data_sign_out_msg" = "Biztos vagy benne, hogy minden az eszközön tárolt adatot törölni szeretnél? A fiók és az üzeneteid eléréséhez jelentkezz be."; "auth_softlogout_clear_data_sign_out" = "Kilépés"; +// MARK: Emoji picker +"emoji_picker_title" = "Reakciók"; +"emoji_picker_people_category" = "Smiley-k és emberek"; +"emoji_picker_nature_category" = "Állatok és természet"; +"emoji_picker_foods_category" = "Étel és ital"; +"emoji_picker_activity_category" = "Mozgás"; +"emoji_picker_places_category" = "Utazás és helyek"; +"emoji_picker_objects_category" = "Tárgyak"; +"emoji_picker_symbols_category" = "Szimbólumok"; +"emoji_picker_flags_category" = "Zászlók"; From 483afeb2e7883605430bc1e09a7338c2784142b7 Mon Sep 17 00:00:00 2001 From: Osoitz Date: Sun, 28 Jul 2019 09:06:37 +0000 Subject: [PATCH 35/75] Translated using Weblate (Basque) Currently translated at 100.0% (736 of 736 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/eu/ --- Riot/Assets/eu.lproj/Vector.strings | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index 58f9f6d3f..a9c7e91a6 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -777,3 +777,24 @@ // MARK: File upload "file_upload_error_title" = "Fitxategi igoera"; "file_upload_error_unsupported_file_type_message" = "Fitxategi mota ez onartua."; +"auth_softlogout_signed_out" = "Saioa amaitu duzu"; +"auth_softlogout_sign_in" = "Hasi saioa"; +"auth_softlogout_reason" = "Zure hasiera zerbitzariaren administratzaileak (%1$@) zure %2$@ kontuaren saioa amaitu du (%3$@)."; +"auth_softlogout_recover_encryption_keys" = "hasi saioa gailu honetan besterik gorde ez diren zifratze gakoak berreskuratzeko. Zure mezu seguruak beste gailuetan irakurri ahal izateko behar dituzu."; +"auth_softlogout_clear_data" = "Garbitu datu pertsonalak"; +"auth_softlogout_clear_data_message_1" = "Abisua: Zure datu pertsonalak (zure zifratze gakoak barne) gailu honetan gordeko dira."; +"auth_softlogout_clear_data_message_2" = "Garbitu gailu honekin bukatu baduzu, edo beste saio bat hasi nahi baduzu."; +"auth_softlogout_clear_data_button" = "Garbitu datu guztiak"; +"auth_softlogout_clear_data_sign_out_title" = "Ziur al zaude?"; +"auth_softlogout_clear_data_sign_out_msg" = "Ziur gailu honetan gordetako datu guztiak ezabatu nahi dituzula? Hasi saioa berriro zure kontuaren datuak eta mezuak atzitzeko."; +"auth_softlogout_clear_data_sign_out" = "Amaitu saioa"; +// MARK: Emoji picker +"emoji_picker_title" = "Erreakzioak"; +"emoji_picker_people_category" = "Irribarretxoak eta jendea"; +"emoji_picker_nature_category" = "Animaliak eta natura"; +"emoji_picker_foods_category" = "Janaria eta edaria"; +"emoji_picker_activity_category" = "Jarduerak"; +"emoji_picker_places_category" = "Bidaiak eta tokiak"; +"emoji_picker_objects_category" = "Objektuak"; +"emoji_picker_symbols_category" = "Sinboloak"; +"emoji_picker_flags_category" = "Banderak"; From a5760bc770a184b26162318381428c240d8cb5fa Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 29 Jul 2019 17:31:07 +0200 Subject: [PATCH 36/75] BF: Crash when leaving settings due to backup section refresh animation. The exception was: *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 10. The number of rows contained in an existing section after the update (0) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).' To raise this exception: - Execute the removed code after a delay (2s) - Enter and leave the settings page -> Crash --- CHANGES.rst | 3 +++ Riot/Modules/Settings/SettingsViewController.m | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 34b9390db..0786638f6 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,9 @@ Improvements: * Soft logout: Support soft logout (#2540). * Reactions: Emoji picker (#2370). +Bug fix: +* Crash when leaving settings due to backup section refresh animation. + Changes in 0.9.1 (2019-07-17) =============================================== diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index b8e545a09..091cb5d20 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -4269,10 +4269,7 @@ SignOutAlertPresenterDelegate> - (void)settingsKeyBackupTableViewSectionDidUpdate:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection { - [self.tableView beginUpdates]; - [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:SETTINGS_SECTION_KEYBACKUP_INDEX] - withRowAnimation:UITableViewRowAnimationAutomatic]; - [self.tableView endUpdates]; + [self.tableView reloadData]; } - (MXKTableViewCellWithTextView *)settingsKeyBackupTableViewSection:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection textCellForRow:(NSInteger)textCellForRow From 8d694a7f6b8fc0af275fb096dbcc67dfe5f5a26d Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 29 Jul 2019 18:12:13 +0200 Subject: [PATCH 37/75] Widgets: Whitelist https://scalar-staging.vector.im/api #2612 --- CHANGES.rst | 1 + Riot/Assets/Riot-Defaults.plist | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 0786638f6..0aa0e746f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,7 @@ Improvements: * Upgrade MatrixKit version ([v0.10.2](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.10.2)). * Soft logout: Support soft logout (#2540). * Reactions: Emoji picker (#2370). + * Widgets: Whitelist https://scalar-staging.vector.im/api (#2612). Bug fix: * Crash when leaving settings due to backup section refresh animation. diff --git a/Riot/Assets/Riot-Defaults.plist b/Riot/Assets/Riot-Defaults.plist index 78e606c72..9cab288c2 100644 --- a/Riot/Assets/Riot-Defaults.plist +++ b/Riot/Assets/Riot-Defaults.plist @@ -33,6 +33,7 @@ integrationsWidgetsUrls https://scalar-staging.riot.im/scalar/api + https://scalar-staging.vector.im/api https://scalar.vector.im/api piwik From f0b4bf9aab3e05f7b9642bbdd8c5c4c3c78447ee Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 29 Jul 2019 18:25:28 +0200 Subject: [PATCH 38/75] Fastfile: xcversion++ --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 905b84a7c..c57503670 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -19,7 +19,7 @@ platform :ios do before_all do # Ensure used Xcode version - xcversion(version: "~> 10.2.0") + xcversion(version: "~> 10.3.0") end #### Public #### From 8b9ca6983640e8825d2a3c9f40623401bcb977b3 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 30 Jul 2019 15:44:51 +0200 Subject: [PATCH 39/75] Reactions: Add long press gesture on bubble cell reactions. --- .../MXKRoomBubbleTableViewCell+Riot.h | 14 +++++- .../MXKRoomBubbleTableViewCell+Riot.m | 2 + .../BubbleReactions/BubbleReactionsView.swift | 46 +++++++++++++------ .../BubbleReactionsViewModel.swift | 2 + .../BubbleReactionsViewModelType.swift | 2 + .../Modules/Room/DataSources/RoomDataSource.m | 7 ++- 6 files changed, 57 insertions(+), 16 deletions(-) diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h index 260d79c51..ca82723c6 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h @@ -26,10 +26,22 @@ extern NSString *const kMXKRoomBubbleCellRiotEditButtonPressed; /** Action identifier used when the user tapped on receipts area. - The 'userInfo' disctionary contains an 'MXKReceiptSendersContainer' object under the 'kMXKRoomBubbleCellReceiptsContainerKey' key, representing the receipts container which was tapped on. + The 'userInfo' dictionary contains an 'MXKReceiptSendersContainer' object under the 'kMXKRoomBubbleCellReceiptsContainerKey' key, representing the receipts container which was tapped on. */ extern NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer; +/** + Action identifier used when the user perform a long press on reactions view. + + The 'userInfo' dictionary contains a 'NSString' object under the 'kMXKRoomBubbleCellEventIdKey' key, representing the event id of the event associated with the reactions. + */ +extern NSString *const kMXKRoomBubbleCellLongPressOnReactionView; + +/** + 'userInfo' dictionary key 'kMXKRoomBubbleCellEventIdKey' is associated to a 'NSString' object representing an event id. + */ +extern NSString *const kMXKRoomBubbleCellEventIdKey; + /** Define a `MXKRoomBubbleTableViewCell` category at Riot level to handle bubble customisation. */ diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m index 82389e987..077bfe8b1 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m @@ -29,6 +29,8 @@ NSString *const kMXKRoomBubbleCellRiotEditButtonPressed = @"kMXKRoomBubbleCellRiotEditButtonPressed"; NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer = @"kMXKRoomBubbleCellTapOnReceiptsContainer"; +NSString *const kMXKRoomBubbleCellLongPressOnReactionView = @"kMXKRoomBubbleCellLongPressOnReactionView"; +NSString *const kMXKRoomBubbleCellEventIdKey = @"kMXKRoomBubbleCellEventIdKey"; @implementation MXKRoomBubbleTableViewCell (Riot) diff --git a/Riot/Modules/Room/BubbleReactions/BubbleReactionsView.swift b/Riot/Modules/Room/BubbleReactions/BubbleReactionsView.swift index 4ac8923e4..a5dcb54ac 100644 --- a/Riot/Modules/Room/BubbleReactions/BubbleReactionsView.swift +++ b/Riot/Modules/Room/BubbleReactions/BubbleReactionsView.swift @@ -54,20 +54,8 @@ final class BubbleReactionsView: UIView, NibOwnerLoadable { // MARK: - Setup private func commonInit() { - self.collectionView.isScrollEnabled = false - self.collectionView.delegate = self - self.collectionView.dataSource = self - self.collectionView.collectionViewLayout = DGCollectionViewLeftAlignFlowLayout() - - if let collectionViewFlowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout { - collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize - collectionViewFlowLayout.minimumInteritemSpacing = Constants.minimumInteritemSpacing - collectionViewFlowLayout.minimumLineSpacing = Constants.minimumLineSpacing - } - - self.collectionView.register(cellType: BubbleReactionViewCell.self) - self.collectionView.register(cellType: BubbleReactionActionViewCell.self) - self.collectionView.reloadData() + self.setupCollectionView() + self.setupLongPressGestureRecognizer() } convenience init() { @@ -95,6 +83,36 @@ final class BubbleReactionsView: UIView, NibOwnerLoadable { // MARK: - Private + private func setupCollectionView() { + self.collectionView.isScrollEnabled = false + self.collectionView.delegate = self + self.collectionView.dataSource = self + self.collectionView.collectionViewLayout = DGCollectionViewLeftAlignFlowLayout() + + if let collectionViewFlowLayout = self.collectionView.collectionViewLayout as? UICollectionViewFlowLayout { + collectionViewFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + collectionViewFlowLayout.minimumInteritemSpacing = Constants.minimumInteritemSpacing + collectionViewFlowLayout.minimumLineSpacing = Constants.minimumLineSpacing + } + + self.collectionView.register(cellType: BubbleReactionViewCell.self) + self.collectionView.register(cellType: BubbleReactionActionViewCell.self) + self.collectionView.reloadData() + } + + private func setupLongPressGestureRecognizer() { + let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:))) + gestureRecognizer.delaysTouchesBegan = true + self.collectionView.addGestureRecognizer(gestureRecognizer) + } + + @objc private func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer) { + guard gestureRecognizer.state == .began else { + return + } + self.viewModel?.process(viewAction: .longPress) + } + private func fill(reactionsViewData: [BubbleReactionViewData], showAllButtonState: BubbleReactionsViewState.ShowAllButtonState) { self.reactionsViewData = reactionsViewData self.showAllButtonState = showAllButtonState diff --git a/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModel.swift b/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModel.swift index 1a79b6edb..8e362d736 100644 --- a/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModel.swift +++ b/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModel.swift @@ -69,6 +69,8 @@ import Foundation self.viewModelDelegate?.bubbleReactionsViewModel(self, didShowAllTappedForEventId: self.eventId) case .tapShowAction(.showLess): self.viewModelDelegate?.bubbleReactionsViewModel(self, didShowLessTappedForEventId: self.eventId) + case .longPress: + self.viewModelDelegate?.bubbleReactionsViewModel(self, didLongPressForEventId: self.eventId) } } diff --git a/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModelType.swift b/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModelType.swift index 3496854df..e67dcfdbd 100644 --- a/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModelType.swift +++ b/Riot/Modules/Room/BubbleReactions/BubbleReactionsViewModelType.swift @@ -21,6 +21,7 @@ enum BubbleReactionsViewAction { case tapReaction(index: Int) case addNewReaction case tapShowAction(action: ShowAction) + case longPress enum ShowAction { case showAll @@ -43,6 +44,7 @@ enum BubbleReactionsViewState { func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didRemoveReaction reactionCount: MXReactionCount, forEventId eventId: String) func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didShowAllTappedForEventId eventId: String) func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didShowLessTappedForEventId eventId: String) + func bubbleReactionsViewModel(_ viewModel: BubbleReactionsViewModel, didLongPressForEventId eventId: String) } protocol BubbleReactionsViewModelViewDelegate: class { diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index fa7034b45..376db16c3 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -238,7 +238,7 @@ continue; } - MXAggregatedReactions* reactions = cellData.reactions[componentEventId]; + MXAggregatedReactions* reactions = cellData.reactions[componentEventId].aggregatedReactionsWithNonZeroCount; BubbleReactionsView *reactionsView; @@ -607,4 +607,9 @@ } } +- (void)bubbleReactionsViewModel:(BubbleReactionsViewModel *)viewModel didLongPressForEventId:(NSString *)eventId +{ + [self.delegate dataSource:self didRecognizeAction:kMXKRoomBubbleCellLongPressOnReactionView inCell:nil userInfo:@{ kMXKRoomBubbleCellEventIdKey: eventId }]; +} + @end From f8cdb2afd67a07f2343045ef1d1a35bdfb96de6a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 30 Jul 2019 17:14:01 +0200 Subject: [PATCH 40/75] Reaction history: Implement view model. --- .../ReactionHistoryViewAction.swift | 25 +++ .../ReactionHistoryViewData.swift | 23 +++ .../ReactionHistoryViewModel.swift | 179 ++++++++++++++++++ .../ReactionHistoryViewModelType.swift | 36 ++++ .../ReactionHistoryViewState.swift | 26 +++ 5 files changed, 289 insertions(+) create mode 100644 Riot/Modules/Room/ReactionHistory/ReactionHistoryViewAction.swift create mode 100644 Riot/Modules/Room/ReactionHistory/ReactionHistoryViewData.swift create mode 100644 Riot/Modules/Room/ReactionHistory/ReactionHistoryViewModel.swift create mode 100644 Riot/Modules/Room/ReactionHistory/ReactionHistoryViewModelType.swift create mode 100644 Riot/Modules/Room/ReactionHistory/ReactionHistoryViewState.swift diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewAction.swift b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewAction.swift new file mode 100644 index 000000000..07262b85e --- /dev/null +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewAction.swift @@ -0,0 +1,25 @@ +// File created from ScreenTemplate +// $ createScreen.sh ReactionHistory ReactionHistory +/* + 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 + +/// ReactionHistoryViewController view actions exposed to view model +enum ReactionHistoryViewAction { + case loadMore + case close +} diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewData.swift b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewData.swift new file mode 100644 index 000000000..b10fa03bc --- /dev/null +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewData.swift @@ -0,0 +1,23 @@ +/* + 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 ReactionHistoryViewData { + let reaction: String + let userDisplayName: String + let dateString: String +} diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewModel.swift b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewModel.swift new file mode 100644 index 000000000..12fa4e4aa --- /dev/null +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewModel.swift @@ -0,0 +1,179 @@ +// File created from ScreenTemplate +// $ createScreen.sh ReactionHistory ReactionHistory +/* + 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 + +final class ReactionHistoryViewModel: ReactionHistoryViewModelType { + + // MARK: - Constants + + private enum Pagination { + static let count: UInt = 30 + } + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let roomId: String + private let eventId: String + private let aggregations: MXAggregations + private let eventFormatter: MXKEventFormatter + private let reactionsFormattingQueue: DispatchQueue + + private var reactionHistoryViewDataList: [ReactionHistoryViewData] = [] + private var operation: MXHTTPOperation? + private var nextBatch: String? + private var viewState: ReactionHistoryViewState? + + private lazy var roomMembers: MXRoomMembers? = { + return buildRoomMembers() + }() + + // MARK: Public + + weak var viewDelegate: ReactionHistoryViewModelViewDelegate? + weak var coordinatorDelegate: ReactionHistoryViewModelCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, roomId: String, eventId: String) { + self.session = session + self.aggregations = session.aggregations + self.roomId = roomId + self.eventId = eventId + self.eventFormatter = EventFormatter(matrixSession: session) + self.reactionsFormattingQueue = DispatchQueue(label: "\(type(of: self)).reactionsFormattingQueue") + } + + // MARK: - Public + + func process(viewAction: ReactionHistoryViewAction) { + switch viewAction { + case .loadMore: + self.loadMoreHistory() + case .close: + self.coordinatorDelegate?.reactionHistoryViewModelDidClose(self) + } + } + + // MARK: - Private + + + private func canLoadMoreHistory() -> Bool { + guard let viewState = self.viewState else { + return true + } + + let canLoadMoreHistory: Bool + + switch viewState { + case .loading: + canLoadMoreHistory = false + case .loaded(reactionHistoryViewDataList: _, allDataLoaded: let allDataLoaded): + canLoadMoreHistory = !allDataLoaded + default: + canLoadMoreHistory = true + } + + return canLoadMoreHistory + } + + private func loadMoreHistory() { + guard self.canLoadMoreHistory() else { + print("[ReactionHistoryViewModel] loadMoreHistory: pending loading or all data loaded") + return + } + + guard self.operation == nil else { + print("[ReactionHistoryViewModel] loadMoreHistory: operation already pending") + return + } + + self.update(viewState: .loading) + + self.operation = self.aggregations.reactionsEvents(forEvent: self.eventId, inRoom: self.roomId, from: self.nextBatch, limit: Pagination.count, success: { [weak self] (response) in + guard let self = self else { + return + } + + self.nextBatch = response.nextBatch + self.operation = nil + + self.process(reactionEvents: response.chunk, nextBatch: response.nextBatch) + + }, failure: { [weak self] error in + guard let self = self else { + return + } + + self.operation = nil + self.update(viewState: .error(error)) + }) + } + + private func process(reactionEvents: [MXEvent], nextBatch: String?) { + self.reactionsFormattingQueue.async { + + let reactionHistoryList = reactionEvents.compactMap { (reactionEvent) -> ReactionHistoryViewData? in + return self.reactionHistoryViewData(from: reactionEvent) + } + + self.reactionHistoryViewDataList.append(contentsOf: reactionHistoryList) + + let allDataLoaded = nextBatch == nil + + DispatchQueue.main.async { + self.update(viewState: .loaded(reactionHistoryViewDataList: self.reactionHistoryViewDataList, allDataLoaded: allDataLoaded)) + } + } + } + + private func reactionHistoryViewData(from reactionEvent: MXEvent) -> ReactionHistoryViewData? { + guard let userId = reactionEvent.sender, + let reaction = reactionEvent.relatesTo?.key, + let reactionDateString = self.eventFormatter.dateString(fromTimestamp: reactionEvent.originServerTs, withTime: true) else { + return nil + } + + let userDisplayName = self.userDisplayName(from: userId) ?? userId + + return ReactionHistoryViewData(reaction: reaction, userDisplayName: userDisplayName, dateString: reactionDateString) + } + + private func userDisplayName(from userId: String) -> String? { + guard let roomMembers = self.roomMembers else { + return nil + } + let roomMember = roomMembers.member(withUserId: userId) + return roomMember?.displayname + } + + private func buildRoomMembers() -> MXRoomMembers? { + guard let room = self.session.room(withRoomId: self.roomId) else { + return nil + } + return room.dangerousSyncState?.members + } + + private func update(viewState: ReactionHistoryViewState) { + self.viewState = viewState + self.viewDelegate?.reactionHistoryViewModel(self, didUpdateViewState: viewState) + } +} diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewModelType.swift b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewModelType.swift new file mode 100644 index 000000000..92c300d46 --- /dev/null +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewModelType.swift @@ -0,0 +1,36 @@ +// File created from ScreenTemplate +// $ createScreen.sh ReactionHistory ReactionHistory +/* + 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 ReactionHistoryViewModelViewDelegate: class { + func reactionHistoryViewModel(_ viewModel: ReactionHistoryViewModelType, didUpdateViewState viewSate: ReactionHistoryViewState) +} + +protocol ReactionHistoryViewModelCoordinatorDelegate: class { + func reactionHistoryViewModelDidClose(_ viewModel: ReactionHistoryViewModelType) +} + +/// Protocol describing the view model used by `ReactionHistoryViewController` +protocol ReactionHistoryViewModelType { + + var viewDelegate: ReactionHistoryViewModelViewDelegate? { get set } + var coordinatorDelegate: ReactionHistoryViewModelCoordinatorDelegate? { get set } + + func process(viewAction: ReactionHistoryViewAction) +} diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewState.swift b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewState.swift new file mode 100644 index 000000000..4bf2da314 --- /dev/null +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewState.swift @@ -0,0 +1,26 @@ +// File created from ScreenTemplate +// $ createScreen.sh ReactionHistory ReactionHistory +/* + 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 + +/// ReactionHistoryViewController view state +enum ReactionHistoryViewState { + case loading + case loaded(reactionHistoryViewDataList: [ReactionHistoryViewData], allDataLoaded: Bool) + case error(Error) +} From 8c83e1d51d3d7d1866740358e6b7994f9efdcd30 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 30 Jul 2019 17:15:55 +0200 Subject: [PATCH 41/75] Reaction history: Implement screen. --- Riot/Assets/en.lproj/Vector.strings | 4 + Riot/Generated/Storyboards.swift | 5 + Riot/Generated/Strings.swift | 8 + .../ReactionHistoryViewCell.swift | 44 ++++ .../ReactionHistoryViewCell.xib | 65 ++++++ .../ReactionHistoryViewController.storyboard | 48 ++++ .../ReactionHistoryViewController.swift | 206 ++++++++++++++++++ 7 files changed, 380 insertions(+) create mode 100644 Riot/Modules/Room/ReactionHistory/ReactionHistoryViewCell.swift create mode 100644 Riot/Modules/Room/ReactionHistory/ReactionHistoryViewCell.xib create mode 100644 Riot/Modules/Room/ReactionHistory/ReactionHistoryViewController.storyboard create mode 100644 Riot/Modules/Room/ReactionHistory/ReactionHistoryViewController.swift diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 36ee48fc5..e68b29730 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -295,6 +295,7 @@ "room_event_action_edit" = "Edit"; "room_event_action_reaction_show_all" = "Show all"; "room_event_action_reaction_show_less" = "Show less"; +"room_event_action_reaction_history" = "Reaction history"; "room_warning_about_encryption" = "End-to-end encryption is in beta and may not be reliable.\n\nYou should not yet trust it to secure data.\n\nDevices will not yet be able to decrypt history from before they joined the room.\n\nEncrypted messages will not be visible on clients that do not yet implement encryption."; "room_event_failed_to_send" = "Failed to send"; "room_action_send_photo_or_video" = "Send photo or video"; @@ -928,3 +929,6 @@ "emoji_picker_objects_category" = "Objects"; "emoji_picker_symbols_category" = "Symbols"; "emoji_picker_flags_category" = "Flags"; + +// MARK: Reaction history +"reaction_history_title" = "Reactions"; diff --git a/Riot/Generated/Storyboards.swift b/Riot/Generated/Storyboards.swift index 56dc93c8f..28bbe13d8 100644 --- a/Riot/Generated/Storyboards.swift +++ b/Riot/Generated/Storyboards.swift @@ -82,6 +82,11 @@ internal enum StoryboardScene { internal static let initialScene = InitialSceneType(storyboard: KeyBackupSetupSuccessFromRecoveryKeyViewController.self) } + internal enum ReactionHistoryViewController: StoryboardType { + internal static let storyboardName = "ReactionHistoryViewController" + + internal static let initialScene = InitialSceneType(storyboard: ReactionHistoryViewController.self) + } internal enum RoomContextualMenuViewController: StoryboardType { internal static let storyboardName = "RoomContextualMenuViewController" diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index f3cfa1577..8608e0e0e 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -1406,6 +1406,10 @@ internal enum VectorL10n { internal static var rageShakePrompt: String { return VectorL10n.tr("Vector", "rage_shake_prompt") } + /// Reactions + internal static var reactionHistoryTitle: String { + return VectorL10n.tr("Vector", "reaction_history_title") + } /// Read Receipts List internal static var readReceiptsList: String { return VectorL10n.tr("Vector", "read_receipts_list") @@ -1818,6 +1822,10 @@ internal enum VectorL10n { internal static var roomEventActionQuote: String { return VectorL10n.tr("Vector", "room_event_action_quote") } + /// Reaction history + internal static var roomEventActionReactionHistory: String { + return VectorL10n.tr("Vector", "room_event_action_reaction_history") + } /// Show all internal static var roomEventActionReactionShowAll: String { return VectorL10n.tr("Vector", "room_event_action_reaction_show_all") diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewCell.swift b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewCell.swift new file mode 100644 index 000000000..3ca8edc39 --- /dev/null +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewCell.swift @@ -0,0 +1,44 @@ +/* + Copyright 2014 OpenMarket Ltd + Copyright 2017 Vector Creations Ltd + Copyright 2018 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 UIKit +import Reusable + +final class ReactionHistoryViewCell: UITableViewCell, NibReusable, Themable { + + // MARK: - Properties + + @IBOutlet private weak var reactionLabel: UILabel! + @IBOutlet private weak var userDisplayNameLabel: UILabel! + @IBOutlet private weak var timestampLabel: UILabel! + + // MARK: - Public + + func fill(with viewData: ReactionHistoryViewData) { + self.reactionLabel.text = viewData.reaction + self.userDisplayNameLabel.text = viewData.userDisplayName + self.timestampLabel.text = viewData.dateString + } + + func update(theme: Theme) { + self.backgroundColor = theme.backgroundColor + self.reactionLabel.textColor = theme.textPrimaryColor + self.userDisplayNameLabel.textColor = theme.textPrimaryColor + self.timestampLabel.textColor = theme.textSecondaryColor + } +} diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewCell.xib b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewCell.xib new file mode 100644 index 000000000..8d80df926 --- /dev/null +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewCell.xib @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewController.storyboard b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewController.storyboard new file mode 100644 index 000000000..a937db23b --- /dev/null +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewController.storyboard @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewController.swift b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewController.swift new file mode 100644 index 000000000..e7d890efb --- /dev/null +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewController.swift @@ -0,0 +1,206 @@ +// File created from ScreenTemplate +// $ createScreen.sh ReactionHistory ReactionHistory +/* + 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 UIKit + +final class ReactionHistoryViewController: UIViewController { + + // MARK: - Constants + + private enum TableView { + static let estimatedRowHeight: CGFloat = 21.0 + static let contentInset = UIEdgeInsets(top: 10.0, left: 0.0, bottom: 10.0, right: 0.0) + } + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var tableView: UITableView! + + // MARK: Private + + private var viewModel: ReactionHistoryViewModelType! + private var theme: Theme! + private var errorPresenter: MXKErrorPresentation! + private var activityPresenter: ActivityIndicatorPresenter! + private var isViewAppearedOnce: Bool = false + + private var reactionHistoryViewDataList: [ReactionHistoryViewData] = [] + + + // MARK: - Setup + + class func instantiate(with viewModel: ReactionHistoryViewModelType) -> ReactionHistoryViewController { + let viewController = StoryboardScene.ReactionHistoryViewController.initialScene.instantiate() + viewController.viewModel = viewModel + viewController.theme = ThemeService.shared().theme + return viewController + } + + // MARK: - Life cycle + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + + self.title = VectorL10n.reactionHistoryTitle + + self.viewModel.viewDelegate = self + + self.activityPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + + self.setupViews() + + self.registerThemeServiceDidChangeThemeNotification() + self.update(theme: self.theme) + + self.viewModel.process(viewAction: .loadMore) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if self.isViewAppearedOnce == false { + self.isViewAppearedOnce = true + } + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return self.theme.statusBarStyle + } + + // MARK: - Private + + private func update(theme: Theme) { + self.theme = theme + + self.view.backgroundColor = theme.headerBackgroundColor + self.tableView.backgroundColor = theme.backgroundColor + + if let navigationBar = self.navigationController?.navigationBar { + theme.applyStyle(onNavigationBar: navigationBar) + } + } + + private func registerThemeServiceDidChangeThemeNotification() { + NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) + } + + @objc private func themeDidChange() { + self.update(theme: ThemeService.shared().theme) + } + + private func setupTableView() { + self.tableView.contentInset = TableView.contentInset + + self.tableView.rowHeight = UITableView.automaticDimension + self.tableView.estimatedRowHeight = TableView.estimatedRowHeight + self.tableView.register(cellType: ReactionHistoryViewCell.self) + + self.tableView.tableFooterView = UIView() + } + + private func setupViews() { + let closeBarButtonItem = MXKBarButtonItem(title: VectorL10n.close, style: .plain) { [weak self] in + self?.closeButtonAction() + } + + self.navigationItem.rightBarButtonItem = closeBarButtonItem + + self.setupTableView() + } + + private func render(viewState: ReactionHistoryViewState) { + switch viewState { + case .loading: + self.renderLoading() + case .loaded(reactionHistoryViewDataList: let reactionHistoryViewDataList, allDataLoaded: let allDataLoaded): + self.renderLoaded(reactionHistoryViewDataList: reactionHistoryViewDataList, allDataLoaded: allDataLoaded) + case .error(let error): + self.render(error: error) + } + } + + private func renderLoading() { + self.activityPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded(reactionHistoryViewDataList: [ReactionHistoryViewData], allDataLoaded: Bool) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.reactionHistoryViewDataList = reactionHistoryViewDataList + self.tableView.reloadData() + } + + private func render(error: Error) { + self.activityPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + + // MARK: - Actions + + private func closeButtonAction() { + self.viewModel.process(viewAction: .close) + } +} + +// MARK: - UITableViewDataSource +extension ReactionHistoryViewController: UITableViewDataSource { + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.reactionHistoryViewDataList.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let reactionHistoryCell = tableView.dequeueReusableCell(for: indexPath, cellType: ReactionHistoryViewCell.self) + + let reactionHistoryViewData = self.reactionHistoryViewDataList[indexPath.row] + + reactionHistoryCell.update(theme: self.theme) + reactionHistoryCell.fill(with: reactionHistoryViewData) + + return reactionHistoryCell + } +} + +// MARK: - UITableViewDelegate +extension ReactionHistoryViewController: UITableViewDelegate { + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + + guard self.isViewAppearedOnce else { + return + } + + // Check if a scroll beyond scroll view content occurs + let distanceFromBottom = scrollView.contentSize.height - scrollView.contentOffset.y + if distanceFromBottom < scrollView.frame.size.height { + self.viewModel.process(viewAction: .loadMore) + } + } +} + +// MARK: - ReactionHistoryViewModelViewDelegate +extension ReactionHistoryViewController: ReactionHistoryViewModelViewDelegate { + + func reactionHistoryViewModel(_ viewModel: ReactionHistoryViewModelType, didUpdateViewState viewSate: ReactionHistoryViewState) { + self.render(viewState: viewSate) + } +} From 1ad0d6908e66cefdcb3c86837fdd8937db0ccf8a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 30 Jul 2019 17:16:23 +0200 Subject: [PATCH 42/75] Reaction history: Implement coordinator. --- .../ReactionHistoryCoordinator.swift | 68 +++++++++++++++++++ .../ReactionHistoryCoordinatorType.swift | 28 ++++++++ 2 files changed, 96 insertions(+) create mode 100644 Riot/Modules/Room/ReactionHistory/ReactionHistoryCoordinator.swift create mode 100644 Riot/Modules/Room/ReactionHistory/ReactionHistoryCoordinatorType.swift diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryCoordinator.swift b/Riot/Modules/Room/ReactionHistory/ReactionHistoryCoordinator.swift new file mode 100644 index 000000000..4bd540823 --- /dev/null +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryCoordinator.swift @@ -0,0 +1,68 @@ +// File created from ScreenTemplate +// $ createScreen.sh ReactionHistory ReactionHistory +/* + 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 +import UIKit + +final class ReactionHistoryCoordinator: ReactionHistoryCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let roomId: String + private let eventId: String + private let router: NavigationRouter + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: ReactionHistoryCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, roomId: String, eventId: String) { + self.session = session + self.roomId = roomId + self.eventId = eventId + self.router = NavigationRouter(navigationController: RiotNavigationController()) + } + + // MARK: - Public methods + + func start() { + let reactionHistoryViewModel = ReactionHistoryViewModel(session: session, roomId: roomId, eventId: eventId) + let reactionHistoryViewController = ReactionHistoryViewController.instantiate(with: reactionHistoryViewModel) + reactionHistoryViewModel.coordinatorDelegate = self + self.router.setRootModule(reactionHistoryViewController) + } + + func toPresentable() -> UIViewController { + return self.router.toPresentable() + } +} + +// MARK: - ReactionHistoryViewModelCoordinatorDelegate +extension ReactionHistoryCoordinator: ReactionHistoryViewModelCoordinatorDelegate { + func reactionHistoryViewModelDidClose(_ viewModel: ReactionHistoryViewModelType) { + self.delegate?.reactionHistoryCoordinatorDidClose(self) + } +} diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryCoordinatorType.swift b/Riot/Modules/Room/ReactionHistory/ReactionHistoryCoordinatorType.swift new file mode 100644 index 000000000..420dc1fe2 --- /dev/null +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryCoordinatorType.swift @@ -0,0 +1,28 @@ +// File created from ScreenTemplate +// $ createScreen.sh ReactionHistory ReactionHistory +/* + 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 ReactionHistoryCoordinatorDelegate: class { + func reactionHistoryCoordinatorDidClose(_ coordinator: ReactionHistoryCoordinatorType) +} + +/// `ReactionHistoryCoordinatorType` is a protocol describing a Coordinator that handle reaction history navigation flow. +protocol ReactionHistoryCoordinatorType: Coordinator, Presentable { + var delegate: ReactionHistoryCoordinatorDelegate? { get } +} From 06feb8e28be7ad23957236bb71c7f7f40f22ad5b Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 30 Jul 2019 17:18:39 +0200 Subject: [PATCH 43/75] Reaction history: Handle presentation from room VC by long press on reactions or from contextual menu. --- ...ionHistoryBridgeCoordinatorPresenter.swift | 89 +++++++++++++++++++ Riot/Modules/Room/RoomViewController.m | 50 ++++++++++- 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 Riot/Modules/Room/ReactionHistory/ReactionHistoryBridgeCoordinatorPresenter.swift diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryBridgeCoordinatorPresenter.swift b/Riot/Modules/Room/ReactionHistory/ReactionHistoryBridgeCoordinatorPresenter.swift new file mode 100644 index 000000000..9804af4e8 --- /dev/null +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryBridgeCoordinatorPresenter.swift @@ -0,0 +1,89 @@ +/* + 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 + +@objc protocol ReactionHistoryCoordinatorBridgePresenterDelegate { + func reactionHistoryCoordinatorBridgePresenterDelegateDidClose(_ coordinatorBridgePresenter: ReactionHistoryCoordinatorBridgePresenter) +} + +/// ReactionHistoryCoordinatorBridgePresenter enables to start ReactionHistoryCoordinator from a view controller. +/// This bridge is used while waiting for global usage of coordinator pattern. +@objcMembers +final class ReactionHistoryCoordinatorBridgePresenter: NSObject { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let roomId: String + private let eventId: String + private var coordinator: ReactionHistoryCoordinator? + + // MARK: Public + + var isPresenting: Bool { + return self.coordinator != nil + } + + weak var delegate: ReactionHistoryCoordinatorBridgePresenterDelegate? + + // MARK: - Setup + + init(session: MXSession, roomId: String, eventId: String) { + self.session = session + self.roomId = roomId + self.eventId = eventId + super.init() + } + + // MARK: - Public + + func present(from viewController: UIViewController, animated: Bool) { + + let reactionHistoryCoordinator = ReactionHistoryCoordinator(session: self.session, roomId: self.roomId, eventId: self.eventId) + reactionHistoryCoordinator.delegate = self + + let coordinatorPresentable = reactionHistoryCoordinator.toPresentable() + coordinatorPresentable.modalPresentationStyle = .formSheet + viewController.present(coordinatorPresentable, animated: animated, completion: nil) + + reactionHistoryCoordinator.start() + + self.coordinator = reactionHistoryCoordinator + } + + func dismiss(animated: Bool, completion: (() -> Void)?) { + guard let coordinator = self.coordinator else { + return + } + coordinator.toPresentable().dismiss(animated: animated) { + self.coordinator = nil + + if let completion = completion { + completion() + } + } + } +} + +// MARK: - ReactionHistoryCoordinatorDelegate +extension ReactionHistoryCoordinatorBridgePresenter: ReactionHistoryCoordinatorDelegate { + func reactionHistoryCoordinatorDidClose(_ coordinator: ReactionHistoryCoordinatorType) { + self.delegate?.reactionHistoryCoordinatorBridgePresenterDelegateDidClose(self) + } +} diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index e40e31cd7..50eba1739 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -124,7 +124,8 @@ #import "Riot-Swift.h" @interface RoomViewController () + ReactionsMenuViewModelCoordinatorDelegate, EditHistoryCoordinatorBridgePresenterDelegate, MXKDocumentPickerPresenterDelegate, EmojiPickerCoordinatorBridgePresenterDelegate, + ReactionHistoryCoordinatorBridgePresenterDelegate> { // The expanded header ExpandedRoomTitleView *expandedHeader; @@ -224,6 +225,7 @@ @property (nonatomic, strong) EditHistoryCoordinatorBridgePresenter *editHistoryPresenter; @property (nonatomic, strong) MXKDocumentPickerPresenter *documentPickerPresenter; @property (nonatomic, strong) EmojiPickerCoordinatorBridgePresenter *emojiPickerCoordinatorBridgePresenter; +@property (nonatomic, strong) ReactionHistoryCoordinatorBridgePresenter *reactionHistoryCoordinatorBridgePresenter; @end @@ -1531,6 +1533,21 @@ } } +- (void)showReactionHistoryForEventId:(NSString*)eventId animated:(BOOL)animated +{ + if (self.reactionHistoryCoordinatorBridgePresenter.isPresenting) + { + return; + } + + ReactionHistoryCoordinatorBridgePresenter *presenter = [[ReactionHistoryCoordinatorBridgePresenter alloc] initWithSession:self.mainSession roomId:self.roomDataSource.roomId eventId:eventId]; + presenter.delegate = self; + + [presenter presentFrom:self animated:animated]; + + self.reactionHistoryCoordinatorBridgePresenter = presenter; +} + #pragma mark - Hide/Show expanded header - (void)showExpandedHeader:(BOOL)isVisible @@ -2173,6 +2190,14 @@ [self handleLongPressFromCell:cell withTappedEvent:tappedEvent]; } } + else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellLongPressOnReactionView]) + { + NSString *tappedEventId = userInfo[kMXKRoomBubbleCellEventIdKey]; + if (tappedEventId) + { + [self showReactionHistoryForEventId:tappedEventId animated:YES]; + } + } else { // Keep default implementation for other actions @@ -2533,6 +2558,20 @@ }]]; + // Add reaction history if event contains reactions + if (roomBubbleTableViewCell.bubbleData.reactions[selectedEvent.eventId].aggregatedReactionsWithNonZeroCount) + { + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_reaction_history", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + [self cancelEventSelection]; + + // Show reaction history + [self showReactionHistoryForEventId:selectedEvent.eventId animated:YES]; + }]]; + } + [currentAlert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_event_action_view_source", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { @@ -5456,5 +5495,14 @@ self.emojiPickerCoordinatorBridgePresenter = nil; } +#pragma mark - ReactionHistoryCoordinatorBridgePresenterDelegate + +- (void)reactionHistoryCoordinatorBridgePresenterDelegateDidClose:(ReactionHistoryCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + [coordinatorBridgePresenter dismissWithAnimated:YES completion:^{ + self.reactionHistoryCoordinatorBridgePresenter = nil; + }]; +} + @end From 58f485c56ea0dd0758da13f1fb43241adcdd4826 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 30 Jul 2019 17:19:10 +0200 Subject: [PATCH 44/75] Edit history: Fix some issues. --- .../EditHistory/EditHistoryCoordinatorBridgePresenter.swift | 3 +-- Riot/Modules/Room/EditHistory/EditHistoryViewModel.swift | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorBridgePresenter.swift b/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorBridgePresenter.swift index f6b20b5e5..d6a4bb7ad 100644 --- a/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorBridgePresenter.swift +++ b/Riot/Modules/Room/EditHistory/EditHistoryCoordinatorBridgePresenter.swift @@ -65,9 +65,8 @@ final class EditHistoryCoordinatorBridgePresenter: NSObject { let editHistoryCoordinator = EditHistoryCoordinator(session: self.session, formatter: formatter, event: self.event) editHistoryCoordinator.delegate = self - let navigationController = RiotNavigationController() + let navigationController = RiotNavigationController(rootViewController: editHistoryCoordinator.toPresentable()) navigationController.modalPresentationStyle = .formSheet - navigationController.addChild(editHistoryCoordinator.toPresentable()) viewController.present(navigationController, animated: animated, completion: nil) editHistoryCoordinator.start() diff --git a/Riot/Modules/Room/EditHistory/EditHistoryViewModel.swift b/Riot/Modules/Room/EditHistory/EditHistoryViewModel.swift index ddf34a462..7885393dc 100644 --- a/Riot/Modules/Room/EditHistory/EditHistoryViewModel.swift +++ b/Riot/Modules/Room/EditHistory/EditHistoryViewModel.swift @@ -113,7 +113,7 @@ final class EditHistoryViewModel: EditHistoryViewModelType { sself.nextBatch = response.nextBatch sself.operation = nil - sself.process(editEvents: response.chunk,originalEvent: response.originalEvent, nextBatch: response.nextBatch) + sself.process(editEvents: response.chunk, originalEvent: response.originalEvent, nextBatch: response.nextBatch) }, failure: { [weak self] error in guard let sself = self else { From 206a11dc8569249f279b8894220b83b1c898c39a Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 30 Jul 2019 17:19:19 +0200 Subject: [PATCH 45/75] Update pbxproj --- Riot.xcodeproj/project.pbxproj | 56 ++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 3b954774a..88c2eb33f 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -145,6 +145,7 @@ B110872421F098F0003554A5 /* ActivityIndicatorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B110872021F098EF003554A5 /* ActivityIndicatorView.xib */; }; B110872521F098F0003554A5 /* ActivityIndicatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B110872121F098EF003554A5 /* ActivityIndicatorPresenter.swift */; }; B110872621F098F0003554A5 /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B110872221F098F0003554A5 /* ActivityIndicatorView.swift */; }; + B120863722EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B120863622EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift */; }; B139C21B21FE5B9200BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21A21FE5B9100BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift */; }; B139C21D21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21C21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift */; }; B139C21F21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B139C21E21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift */; }; @@ -463,6 +464,17 @@ B1B9DEDA22E9B7350065E677 /* SerializationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DED922E9B7350065E677 /* SerializationService.swift */; }; B1B9DEDC22E9B7440065E677 /* SerializationServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEDB22E9B7440065E677 /* SerializationServiceType.swift */; }; B1B9DEDE22E9D9890065E677 /* EmojiServiceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEDD22E9D9890065E677 /* EmojiServiceType.swift */; }; + B1B9DEE822EB34EF0065E677 /* ReactionHistoryCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEE022EB34ED0065E677 /* ReactionHistoryCoordinatorType.swift */; }; + B1B9DEE922EB34EF0065E677 /* ReactionHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEE122EB34EE0065E677 /* ReactionHistoryViewController.swift */; }; + B1B9DEEA22EB34EF0065E677 /* ReactionHistoryViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B1B9DEE222EB34EE0065E677 /* ReactionHistoryViewController.storyboard */; }; + B1B9DEEB22EB34EF0065E677 /* ReactionHistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEE322EB34EE0065E677 /* ReactionHistoryViewModel.swift */; }; + B1B9DEEC22EB34EF0065E677 /* ReactionHistoryViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEE422EB34EE0065E677 /* ReactionHistoryViewModelType.swift */; }; + B1B9DEED22EB34EF0065E677 /* ReactionHistoryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEE522EB34EE0065E677 /* ReactionHistoryCoordinator.swift */; }; + B1B9DEEE22EB34EF0065E677 /* ReactionHistoryViewAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEE622EB34EE0065E677 /* ReactionHistoryViewAction.swift */; }; + B1B9DEEF22EB34EF0065E677 /* ReactionHistoryViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEE722EB34EE0065E677 /* ReactionHistoryViewState.swift */; }; + B1B9DEF122EB396B0065E677 /* ReactionHistoryViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEF022EB396B0065E677 /* ReactionHistoryViewData.swift */; }; + B1B9DEF422EB426D0065E677 /* ReactionHistoryViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEF222EB426D0065E677 /* ReactionHistoryViewCell.swift */; }; + B1B9DEF522EB426D0065E677 /* ReactionHistoryViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1B9DEF322EB426D0065E677 /* ReactionHistoryViewCell.xib */; }; B1C562CA2289C2690037F12A /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */; }; B1C562CC228AB3510037F12A /* UIStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562CB228AB3510037F12A /* UIStackView.swift */; }; B1C562D9228C0B760037F12A /* RoomContextualMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */; }; @@ -747,6 +759,7 @@ B110872021F098EF003554A5 /* ActivityIndicatorView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ActivityIndicatorView.xib; sourceTree = ""; }; B110872121F098EF003554A5 /* ActivityIndicatorPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorPresenter.swift; sourceTree = ""; }; B110872221F098F0003554A5 /* ActivityIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorView.swift; sourceTree = ""; }; + B120863622EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionHistoryBridgeCoordinatorPresenter.swift; sourceTree = ""; }; B139C21A21FE5B9100BB68EC /* KeyBackupRecoverFromPassphraseViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewModel.swift; sourceTree = ""; }; B139C21C21FE5BF500BB68EC /* KeyBackupRecoverFromPassphraseViewModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewModelType.swift; sourceTree = ""; }; B139C21E21FE5D6600BB68EC /* KeyBackupRecoverFromPassphraseViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewAction.swift; sourceTree = ""; }; @@ -1250,6 +1263,17 @@ B1B9DED922E9B7350065E677 /* SerializationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerializationService.swift; sourceTree = ""; }; B1B9DEDB22E9B7440065E677 /* SerializationServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerializationServiceType.swift; sourceTree = ""; }; B1B9DEDD22E9D9890065E677 /* EmojiServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiServiceType.swift; sourceTree = ""; }; + B1B9DEE022EB34ED0065E677 /* ReactionHistoryCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionHistoryCoordinatorType.swift; sourceTree = ""; }; + B1B9DEE122EB34EE0065E677 /* ReactionHistoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionHistoryViewController.swift; sourceTree = ""; }; + B1B9DEE222EB34EE0065E677 /* ReactionHistoryViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = ReactionHistoryViewController.storyboard; sourceTree = ""; }; + B1B9DEE322EB34EE0065E677 /* ReactionHistoryViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionHistoryViewModel.swift; sourceTree = ""; }; + B1B9DEE422EB34EE0065E677 /* ReactionHistoryViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionHistoryViewModelType.swift; sourceTree = ""; }; + B1B9DEE522EB34EE0065E677 /* ReactionHistoryCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionHistoryCoordinator.swift; sourceTree = ""; }; + B1B9DEE622EB34EE0065E677 /* ReactionHistoryViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionHistoryViewAction.swift; sourceTree = ""; }; + B1B9DEE722EB34EE0065E677 /* ReactionHistoryViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactionHistoryViewState.swift; sourceTree = ""; }; + B1B9DEF022EB396B0065E677 /* ReactionHistoryViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionHistoryViewData.swift; sourceTree = ""; }; + B1B9DEF222EB426D0065E677 /* ReactionHistoryViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionHistoryViewCell.swift; sourceTree = ""; }; + B1B9DEF322EB426D0065E677 /* ReactionHistoryViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReactionHistoryViewCell.xib; sourceTree = ""; }; B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; }; B1C562CB228AB3510037F12A /* UIStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = ""; }; B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomContextualMenuItem.swift; sourceTree = ""; }; @@ -2258,6 +2282,7 @@ B1C562D7228C0B4C0037F12A /* ContextualMenu */, B1963B24228F1C4800CBA17F /* BubbleReactions */, B152C72922DCEA670041315A /* EmojiPicker */, + B1B9DEDF22EB34ED0065E677 /* ReactionHistory */, ); path = Room; sourceTree = ""; @@ -3262,6 +3287,25 @@ path = Serialization; sourceTree = ""; }; + B1B9DEDF22EB34ED0065E677 /* ReactionHistory */ = { + isa = PBXGroup; + children = ( + B120863622EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift */, + B1B9DEE022EB34ED0065E677 /* ReactionHistoryCoordinatorType.swift */, + B1B9DEE522EB34EE0065E677 /* ReactionHistoryCoordinator.swift */, + B1B9DEE122EB34EE0065E677 /* ReactionHistoryViewController.swift */, + B1B9DEE222EB34EE0065E677 /* ReactionHistoryViewController.storyboard */, + B1B9DEE422EB34EE0065E677 /* ReactionHistoryViewModelType.swift */, + B1B9DEE322EB34EE0065E677 /* ReactionHistoryViewModel.swift */, + B1B9DEE622EB34EE0065E677 /* ReactionHistoryViewAction.swift */, + B1B9DEE722EB34EE0065E677 /* ReactionHistoryViewState.swift */, + B1B9DEF022EB396B0065E677 /* ReactionHistoryViewData.swift */, + B1B9DEF222EB426D0065E677 /* ReactionHistoryViewCell.swift */, + B1B9DEF322EB426D0065E677 /* ReactionHistoryViewCell.xib */, + ); + path = ReactionHistory; + sourceTree = ""; + }; B1C562D7228C0B4C0037F12A /* ContextualMenu */ = { isa = PBXGroup; children = ( @@ -3748,6 +3792,7 @@ B1B558F220EF768F00210D55 /* RoomIncomingTextMsgWithoutSenderNameBubbleCell.xib in Resources */, B1B558EA20EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleBubbleCell.xib in Resources */, B1B558CD20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.xib in Resources */, + B1B9DEEA22EB34EF0065E677 /* ReactionHistoryViewController.storyboard in Resources */, B1B9194C2118984300FE25B5 /* RoomPredecessorBubbleCell.xib in Resources */, B1C562E9228C7CF20037F12A /* ContextualMenuItemView.xib in Resources */, B1B5572120EE6C4D00210D55 /* ContactsTableViewController.xib in Resources */, @@ -3779,6 +3824,7 @@ B1B5590820EF768F00210D55 /* RoomMembershipWithPaginationTitleBubbleCell.xib in Resources */, F083BDE81E7009ED00A9B29C /* message.mp3 in Resources */, B1107ECA2200B09F0038014B /* KeyBackupRecoverSuccessViewController.storyboard in Resources */, + B1B9DEF522EB426D0065E677 /* ReactionHistoryViewCell.xib in Resources */, B1B5579C20EF575B00210D55 /* ForgotPasswordInputsView.xib in Resources */, F083BE011E7009ED00A9B29C /* third_party_licenses.html in Resources */, B1098BFC21ECFE65000DDA48 /* PasswordStrengthView.xib in Resources */, @@ -4119,6 +4165,7 @@ B1B558F420EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, B1B5572320EE6C4D00210D55 /* AttachmentsViewController.m in Sources */, F083BDEE1E7009ED00A9B29C /* MXRoom+Riot.m in Sources */, + B120863722EF375F001F89E0 /* ReactionHistoryBridgeCoordinatorPresenter.swift in Sources */, B1B5598620EFC3E000210D55 /* RiotSettings.swift in Sources */, 3232ABA3225730E100AD6A5C /* DeviceVerificationStartCoordinatorType.swift in Sources */, 3232AB4D2256558300AD6A5C /* TemplateScreenCoordinatorType.swift in Sources */, @@ -4166,11 +4213,13 @@ B1B5593B20EF7BAC00210D55 /* TableViewCellWithCheckBoxAndLabel.m in Sources */, B1B5581A20EF625800210D55 /* ExpandedRoomTitleView.m in Sources */, B1107EC82200B0720038014B /* KeyBackupRecoverSuccessViewController.swift in Sources */, + B1B9DEEB22EB34EF0065E677 /* ReactionHistoryViewModel.swift in Sources */, B1963B2F228F1C4900CBA17F /* BubbleReactionViewCell.swift in Sources */, B1B558E920EF768F00210D55 /* RoomSelectedStickerBubbleCell.m in Sources */, B1B558DF20EF768F00210D55 /* RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m in Sources */, F083BE041E7009ED00A9B29C /* Tools.m in Sources */, 3275FD8C21A5A2C500B9C13D /* TermsView.swift in Sources */, + B1B9DEE822EB34EF0065E677 /* ReactionHistoryCoordinatorType.swift in Sources */, B14F143122144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewState.swift in Sources */, B1098C1121ED07E4000DDA48 /* NavigationRouterType.swift in Sources */, B1B5573D20EE6C4D00210D55 /* WebViewViewController.m in Sources */, @@ -4199,6 +4248,7 @@ B1D4752721EE4E630067973F /* KeyboardAvoider.swift in Sources */, B1D4752821EE4E630067973F /* KeyboardNotification.swift in Sources */, B1D1BDA622BBAFB500831367 /* ReactionsMenuView.swift in Sources */, + B1B9DEF422EB426D0065E677 /* ReactionHistoryViewCell.swift in Sources */, B1B5573C20EE6C4D00210D55 /* MasterTabBarController.m in Sources */, B1DCC61B22E5E17100625807 /* EmojiPickerCoordinator.swift in Sources */, 32F6B96E2270623100BBA352 /* DeviceVerificationDataLoadingViewModelType.swift in Sources */, @@ -4265,6 +4315,7 @@ B1098BFE21ECFE65000DDA48 /* KeyBackupSetupPassphraseViewModelType.swift in Sources */, B1B558BE20EF768F00210D55 /* RoomOutgoingEncryptedAttachmentWithPaginationTitleBubbleCell.m in Sources */, F083BDED1E7009ED00A9B29C /* MXKRoomBubbleTableViewCell+Riot.m in Sources */, + B1B9DEEF22EB34EF0065E677 /* ReactionHistoryViewState.swift in Sources */, B1DCC62022E5EDA400625807 /* EmojiPickerCoordinatorBridgePresenter.swift in Sources */, B1B557A820EF5A1B00210D55 /* DeviceTableViewCell.m in Sources */, B1B5572620EE6C4D00210D55 /* RoomFilesSearchViewController.m in Sources */, @@ -4312,6 +4363,7 @@ 324A2052225FC571004FE8B0 /* DeviceVerificationIncomingViewAction.swift in Sources */, B105778D2213051E00334B1E /* KeyBackupSetupSuccessFromRecoveryKeyViewController.swift in Sources */, B1B557D820EF5EA900210D55 /* RoomActivitiesView.m in Sources */, + B1B9DEE922EB34EF0065E677 /* ReactionHistoryViewController.swift in Sources */, B1B5596620EF9E9B00210D55 /* RoomTableViewCell.m in Sources */, B14F143322144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModel.swift in Sources */, 32A6001822C661100042C1D9 /* EditHistoryViewModel.swift in Sources */, @@ -4327,6 +4379,7 @@ B1B5575A20EE6C4D00210D55 /* UnifiedSearchViewController.m in Sources */, 3232AB492256558300AD6A5C /* FlowTemplateCoordinatorBridgePresenter.swift in Sources */, B1B5572820EE6C4D00210D55 /* RoomViewController.m in Sources */, + B1B9DEED22EB34EF0065E677 /* ReactionHistoryCoordinator.swift in Sources */, B1DCC62A22E60D1000625807 /* EmojiMartService.swift in Sources */, B1B558C720EF768F00210D55 /* RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m in Sources */, B1B558F020EF768F00210D55 /* RoomOutgoingAttachmentWithPaginationTitleBubbleCell.m in Sources */, @@ -4400,6 +4453,7 @@ B1098BFD21ECFE65000DDA48 /* PasswordStrengthManager.swift in Sources */, B1B558F520EF768F00210D55 /* RoomOutgoingTextMsgWithPaginationTitleBubbleCell.m in Sources */, 3232AB482256558300AD6A5C /* FlowTemplateCoordinatorType.swift in Sources */, + B1B9DEF122EB396B0065E677 /* ReactionHistoryViewData.swift in Sources */, B1B558F820EF768F00210D55 /* RoomIncomingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, 32242F0921E8B05F00725742 /* UIColor.swift in Sources */, B16932E720F3C37100746532 /* HomeMessagesSearchDataSource.m in Sources */, @@ -4411,7 +4465,9 @@ B1098C0021ECFE65000DDA48 /* KeyBackupSetupPassphraseViewController.swift in Sources */, B1B5591020EF782800210D55 /* TableViewCellWithPhoneNumberTextField.m in Sources */, B1DB4F06223015080065DBFA /* Character.swift in Sources */, + B1B9DEEE22EB34EF0065E677 /* ReactionHistoryViewAction.swift in Sources */, 32B94DFA228EC26400716A26 /* ReactionsMenuButton.swift in Sources */, + B1B9DEEC22EB34EF0065E677 /* ReactionHistoryViewModelType.swift in Sources */, B1C562E8228C7CF20037F12A /* ContextualMenuItemView.swift in Sources */, B14F143022144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinatorType.swift in Sources */, B1E5368921FB1E20001F3AFF /* UIButton.swift in Sources */, From a22db9f70c3b5555ca2be6dabd88c7c727f1a762 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 30 Jul 2019 17:26:11 +0200 Subject: [PATCH 46/75] Update changes --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index 0aa0e746f..dcc6b69d9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,7 @@ Improvements: * Soft logout: Support soft logout (#2540). * Reactions: Emoji picker (#2370). * Widgets: Whitelist https://scalar-staging.vector.im/api (#2612). + * Reactions: Show who reacted (#2591). Bug fix: * Crash when leaving settings due to backup section refresh animation. From 17775afd0e165df29164901be594e808199f6b09 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 30 Jul 2019 18:06:13 +0200 Subject: [PATCH 47/75] ReactionHistoryViewCell: Update copyright. --- .../Room/ReactionHistory/ReactionHistoryViewCell.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewCell.swift b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewCell.swift index 3ca8edc39..691b52e3f 100644 --- a/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewCell.swift +++ b/Riot/Modules/Room/ReactionHistory/ReactionHistoryViewCell.swift @@ -1,7 +1,5 @@ /* - Copyright 2014 OpenMarket Ltd - Copyright 2017 Vector Creations Ltd - Copyright 2018 New Vector Ltd + 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. From 2bdd4d782e1751feb708f5e630229c175bfa4918 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 30 Jul 2019 18:24:12 +0200 Subject: [PATCH 48/75] RoomDataSource: Do not display reactions when event is redacted. --- Riot/Modules/Room/DataSources/RoomDataSource.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m index 376db16c3..3b05bc638 100644 --- a/Riot/Modules/Room/DataSources/RoomDataSource.m +++ b/Riot/Modules/Room/DataSources/RoomDataSource.m @@ -242,8 +242,8 @@ BubbleReactionsView *reactionsView; - if (reactions && !isCollapsableCellCollapsed) - { + if (!component.event.isRedactedEvent && reactions && !isCollapsableCellCollapsed) + { BOOL showAllReactions = [cellData showAllReactionsForEvent:componentEventId]; BubbleReactionsViewModel *bubbleReactionsViewModel = [[BubbleReactionsViewModel alloc] initWithAggregatedReactions:reactions eventId:componentEventId From acb2ab7fa5deff59a86599c33caba98097864b73 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Tue, 30 Jul 2019 18:26:18 +0200 Subject: [PATCH 49/75] Update changes --- CHANGES.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index dcc6b69d9..bc692af8d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,7 +9,8 @@ Improvements: * Reactions: Show who reacted (#2591). Bug fix: -* Crash when leaving settings due to backup section refresh animation. + * Crash when leaving settings due to backup section refresh animation. + * Reactions: Do not display reactions on redacted events in timeline. Changes in 0.9.1 (2019-07-17) =============================================== From 9ec40311f95f735b9bbac266c6864b0bb15c952e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Wed, 31 Jul 2019 07:37:13 +0000 Subject: [PATCH 50/75] Translated using Weblate (French) Currently translated at 100.0% (738 of 738 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 964ffc4aa..89ab870b7 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -810,3 +810,6 @@ "emoji_picker_objects_category" = "Objets"; "emoji_picker_symbols_category" = "Symboles"; "emoji_picker_flags_category" = "Drapeaux"; +"room_event_action_reaction_history" = "Historique des réactions"; +// MARK: Reaction history +"reaction_history_title" = "Réactions"; From f7f64232f2e4010c1b196c1f65e5b726d4e351d2 Mon Sep 17 00:00:00 2001 From: dccs Date: Tue, 30 Jul 2019 18:27:27 +0000 Subject: [PATCH 51/75] Translated using Weblate (German) Currently translated at 99.5% (734 of 738 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/de/ --- Riot/Assets/de.lproj/Vector.strings | 64 ++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index c59aa01c5..504066561 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -669,7 +669,7 @@ "room_action_reply" = "Antworten"; "settings_labs_message_reaction" = "Mit einem Emoji reagieren"; "settings_key_backup_button_connect" = "Schlüssel dieses Geräts sichern"; -"event_formatter_message_edited_mention" = "(geändert)"; +"event_formatter_message_edited_mention" = "(bearbeitet)"; "key_backup_setup_intro_setup_connect_action_with_existing_backup" = "Schlüssel dieses Geräts sichern"; "key_backup_recover_connent_banner_subtitle" = "Schlüssel dieses Geräts sichern"; // MARK: - Device Verification @@ -736,3 +736,65 @@ "device_verification_emoji_bell" = "Glocke"; "device_verification_emoji_anchor" = "Anker"; "device_verification_emoji_headphones" = "Kopfhörer"; +"close" = "Schließen"; +"auth_softlogout_signed_out" = "Sie sind abgemeldet"; +"auth_softlogout_sign_in" = "Einloggen"; +"auth_softlogout_reason" = "Ihr Homeserver-Administrator (%1$@) hat Sie von Ihrem Konto %2$@ (%3$@) abgemeldet."; +"auth_softlogout_recover_encryption_keys" = "Melden Sie sich an, um Verschlüsselungsschlüssel wiederherzustellen, die ausschließlich auf diesem Gerät gespeichert sind. Sie benötigen sie, um alle Ihre sicheren Nachrichten auf jedem Gerät lesen zu können."; +"auth_softlogout_clear_data" = "Persönliche Daten löschen"; +"auth_softlogout_clear_data_message_1" = "Warnung: Ihre persönlichen Daten (einschließlich Verschlüsselungsschlüssel) sind noch auf diesem Gerät gespeichert."; +"auth_softlogout_clear_data_message_2" = "Deaktivieren Sie es, wenn Sie dieses Gerät nicht mehr verwenden oder sich bei einem anderen Konto anmelden möchten."; +"auth_softlogout_clear_data_button" = "Lösche alle Daten"; +"auth_softlogout_clear_data_sign_out_title" = "Bist du sicher?"; +"auth_softlogout_clear_data_sign_out_msg" = "Möchten Sie wirklich alle derzeit auf diesem Gerät gespeicherten Daten löschen? Melden Sie sich erneut an, um auf Ihre Kontodaten und Nachrichten zuzugreifen."; +"auth_softlogout_clear_data_sign_out" = "Ausloggen"; +"room_event_action_reaction_show_all" = "Zeige alles"; +"room_event_action_reaction_show_less" = "Zeige weniger"; +"room_event_action_reaction_history" = "Reaktionsverlauf"; +"room_action_send_file" = "Datei senden"; +"room_message_edits_history_title" = "Bearbeitungsverlauf"; +// Widget +"widget_no_integrations_server_configured" = "Kein Integrationsserver konfiguriert"; +"widget_integrations_server_failed_to_connect" = "Verbindung zum Integrationsserver fehlgeschlagen"; +"device_verification_security_advice" = "Für maximale Sicherheit empfehlen wir, dies persönlich zu tun oder ein anderes vertrauenswürdiges Kommunikationsmittel zu verwenden"; +"device_verification_incoming_description_1" = "Überprüfen Sie dieses Gerät, um es als vertrauenswürdig zu markieren. Das Vertrauen auf Geräte von Partnern gibt Ihnen zusätzliche Sicherheit, wenn Sie verschlüsselte End-to-End-Nachrichten verwenden."; +"device_verification_incoming_description_2" = "Wenn Sie dieses Gerät überprüfen, wird es als vertrauenswürdig und für den Partner als vertrauenswürdig gekennzeichnet."; +// MARK: Start +"device_verification_start_title" = "Überprüfen Sie dies, indem Sie eine kurze Textzeichenfolge vergleichen"; +"device_verification_start_wait_partner" = "Warten auf Partner zu akzeptieren ..."; +"device_verification_start_use_legacy" = "Nichts auftauchend? Nicht alle Clients unterstützen die interaktive Überprüfung. Verwenden Sie die Alte-Überprüfung."; +"device_verification_start_use_legacy_action" = "Verwenden Sie die Alte-Überprüfung"; +// MARK: Verify +"device_verification_verify_title_emoji" = "Überprüfen Sie dieses Gerät, indem Sie bestätigen, dass das folgende Emoji auf dem Bildschirm des Partners angezeigt wird"; +"device_verification_verify_title_number" = "Überprüfen Sie dieses Gerät, indem Sie bestätigen, dass die folgenden Zahlen auf dem Bildschirm des Partners angezeigt werden"; +"device_verification_verify_wait_partner" = "Warten auf die Bestätigung des Partners..."; +// MARK: Verified +"device_verification_verified_title" = "Verifiziert!"; +"device_verification_verified_description_1" = "Sie haben dieses Gerät erfolgreich überprüft."; +"device_verification_verified_description_2" = "Verschlüsselte Nachrichten mit diesem Benutzer werden durchgehend verschlüsselt und können von Dritten nicht gelesen werden."; +"device_verification_emoji_rooster" = "Hahn"; +"device_verification_emoji_globe" = "Globus"; +"device_verification_emoji_smiley" = "Lächeln"; +"device_verification_emoji_spanner" = "Spanner"; +"device_verification_emoji_thumbs up" = "Daumen hoch"; +"device_verification_emoji_hourglass" = "Sanduhr"; +"device_verification_emoji_clock" = "Uhr"; +"device_verification_emoji_pencil" = "Bleistift"; +"device_verification_emoji_lock" = "sperren"; +"device_verification_emoji_folder" = "Ordner"; +"device_verification_emoji_pin" = "Stift"; +// MARK: File upload +"file_upload_error_title" = "Datei hochladen"; +"file_upload_error_unsupported_file_type_message" = "Dateityp wird nicht unterstützt."; +// MARK: Emoji picker +"emoji_picker_title" = "Reaktionen"; +"emoji_picker_people_category" = "Smileys & Menschen"; +"emoji_picker_nature_category" = "Tiere & Natur"; +"emoji_picker_foods_category" = "Essen und Trinken"; +"emoji_picker_activity_category" = "Aktivitäten"; +"emoji_picker_places_category" = "Reisen & Orte"; +"emoji_picker_objects_category" = "Objekte"; +"emoji_picker_symbols_category" = "Symbole"; +"emoji_picker_flags_category" = "Flaggen"; +// MARK: Reaction history +"reaction_history_title" = "Reaktionen"; From a2769a878ffb1e130fa7a2b07fddf59ddc565d01 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Tue, 30 Jul 2019 18:49:41 +0000 Subject: [PATCH 52/75] Translated using Weblate (Hungarian) Currently translated at 100.0% (738 of 738 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index a96feba6f..23df1cd1a 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -815,3 +815,6 @@ "emoji_picker_objects_category" = "Tárgyak"; "emoji_picker_symbols_category" = "Szimbólumok"; "emoji_picker_flags_category" = "Zászlók"; +"room_event_action_reaction_history" = "Reakciók története"; +// MARK: Reaction history +"reaction_history_title" = "Reakciók"; From b32a08c43aba8382f4bb8f4c7bab4ae97d418d5e Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 2 Aug 2019 16:51:12 +0200 Subject: [PATCH 53/75] Add media picker and camera wordings. --- Riot/Assets/en.lproj/Vector.strings | 8 ++++++++ Riot/Generated/Strings.swift | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index e68b29730..cc51f3107 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -298,6 +298,7 @@ "room_event_action_reaction_history" = "Reaction history"; "room_warning_about_encryption" = "End-to-end encryption is in beta and may not be reliable.\n\nYou should not yet trust it to secure data.\n\nDevices will not yet be able to decrypt history from before they joined the room.\n\nEncrypted messages will not be visible on clients that do not yet implement encryption."; "room_event_failed_to_send" = "Failed to send"; +"room_action_camera" = "Take photo or video"; "room_action_send_photo_or_video" = "Send photo or video"; "room_action_send_sticker" = "Send sticker"; "room_action_send_file" = "Send file"; @@ -574,9 +575,14 @@ "receipt_status_read" = "Read: "; // Media picker +"media_picker_title" = "Media library"; "media_picker_library" = "Library"; "media_picker_select" = "Select"; +// Image picker +"image_picker_action_camera" = "Take photo"; +"image_picker_action_library" = "Choose from library"; + // Directory "directory_title" = "Directory"; "directory_server_picker_title" = "Select a directory"; @@ -607,6 +613,8 @@ "rage_shake_prompt" = "You seem to be shaking the phone in frustration. Would you like to submit a bug report?"; "do_not_ask_again" = "Do not ask again"; "camera_access_not_granted" = "%@ doesn't have permission to use Camera, please change privacy settings"; +"camera_unavailable" = "The camera is unavailable on your device"; +"photo_library_access_not_granted" = "%@ doesn't have permission to access photo library, please change privacy settings"; "large_badge_value_k_format" = "%.1fK"; "room_does_not_exist" = "%@ does not exist"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 8608e0e0e..27ddc0ec6 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -374,6 +374,10 @@ internal enum VectorL10n { internal static func cameraAccessNotGranted(_ p1: String) -> String { return VectorL10n.tr("Vector", "camera_access_not_granted", p1) } + /// The camera is unavailable on your device + internal static var cameraUnavailable: String { + return VectorL10n.tr("Vector", "camera_unavailable") + } /// Cancel internal static var cancel: String { return VectorL10n.tr("Vector", "cancel") @@ -1110,6 +1114,14 @@ internal enum VectorL10n { internal static var homeserverConnectionLost: String { return VectorL10n.tr("Vector", "homeserver_connection_lost") } + /// Take photo + internal static var imagePickerActionCamera: String { + return VectorL10n.tr("Vector", "image_picker_action_camera") + } + /// Choose from library + internal static var imagePickerActionLibrary: String { + return VectorL10n.tr("Vector", "image_picker_action_library") + } /// Invite internal static var invite: String { return VectorL10n.tr("Vector", "invite") @@ -1354,6 +1366,10 @@ internal enum VectorL10n { internal static var mediaPickerSelect: String { return VectorL10n.tr("Vector", "media_picker_select") } + /// Media library + internal static var mediaPickerTitle: String { + return VectorL10n.tr("Vector", "media_picker_title") + } /// The Internet connection appears to be offline. internal static var networkOfflinePrompt: String { return VectorL10n.tr("Vector", "network_offline_prompt") @@ -1394,6 +1410,10 @@ internal enum VectorL10n { internal static var peopleNoConversation: String { return VectorL10n.tr("Vector", "people_no_conversation") } + /// %@ doesn't have permission to access photo library, please change privacy settings + internal static func photoLibraryAccessNotGranted(_ p1: String) -> String { + return VectorL10n.tr("Vector", "photo_library_access_not_granted", p1) + } /// Preview internal static var preview: String { return VectorL10n.tr("Vector", "preview") @@ -1438,6 +1458,10 @@ internal enum VectorL10n { internal static var retry: String { return VectorL10n.tr("Vector", "retry") } + /// Take photo or video + internal static var roomActionCamera: String { + return VectorL10n.tr("Vector", "room_action_camera") + } /// Reply internal static var roomActionReply: String { return VectorL10n.tr("Vector", "room_action_reply") From a5ac57ca638547ebe73f8cf6223641983da8fc45 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 2 Aug 2019 16:54:31 +0200 Subject: [PATCH 54/75] Create CameraPresenter that enables to present native camera. --- Riot/Modules/Camera/CameraPresenter.swift | 187 ++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 Riot/Modules/Camera/CameraPresenter.swift diff --git a/Riot/Modules/Camera/CameraPresenter.swift b/Riot/Modules/Camera/CameraPresenter.swift new file mode 100644 index 000000000..6ab0a6eee --- /dev/null +++ b/Riot/Modules/Camera/CameraPresenter.swift @@ -0,0 +1,187 @@ +/* + 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 +import UIKit +import AVFoundation + +@objc protocol CameraPresenterDelegate: class { + func cameraPresenter(_ presenter: CameraPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) + func cameraPresenter(_ presenter: CameraPresenter, didSelectVideoAt url: URL) + func cameraPresenterDidCancel(_ cameraPresenter: CameraPresenter) +} + +/// CameraPresenter enables to present native camera +@objc final class CameraPresenter: NSObject { + + // MARK: - Constants + + private enum Constants { + static let jpegCompressionQuality: CGFloat = 1.0 + } + + // MARK: - Properties + + // MARK: - Private + + private weak var presentingViewController: UIViewController? + private weak var cameraViewController: UIViewController? + private var mediaUTIs: [MXKUTI] = [] + + // MARK: - Public + + @objc weak var delegate: CameraPresenterDelegate? + + // MARK: - Public + + @objc func presentCamera(from presentingViewController: UIViewController, with mediaUTIs: [MXKUTI], animated: Bool) { + self.presentingViewController = presentingViewController + self.mediaUTIs = mediaUTIs + self.checkCameraPermissionAndPresentCamera(animated: animated) + } + + @objc func dismiss(animated: Bool, completion: (() -> Void)?) { + guard let cameraViewController = self.cameraViewController else { + return + } + cameraViewController.dismiss(animated: animated, completion: completion) + } + + // MARK: - Private + + private func checkCameraPermissionAndPresentCamera(animated: Bool) { + + let authorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) + + switch authorizationStatus { + case .authorized: + self.presentCameraController(animated: animated) + case .notDetermined: + self.requestCameraAccess(completion: { (granted) in + if granted { + self.presentCameraController(animated: animated) + } else { + self.presentPermissionDeniedAlert() + } + }) + case .denied, .restricted: + self.presentPermissionDeniedAlert() + @unknown default: + break + } + } + + private func presentCameraController(animated: Bool) { + guard let presentingViewController = self.presentingViewController else { + return + } + + guard let cameraViewController = self.buildCameraViewController() else { + return + } + + presentingViewController.present(cameraViewController, animated: true, completion: nil) + self.cameraViewController = cameraViewController + } + + private func buildCameraViewController() -> UIViewController? { + guard UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) else { + return nil + } + + let mediaTypes = self.mediaUTIs.map { (uti) -> String in + return uti.rawValue + } + + let imagePickerController = UIImagePickerController() + imagePickerController.delegate = self + imagePickerController.sourceType = UIImagePickerController.SourceType.camera + imagePickerController.mediaTypes = mediaTypes + imagePickerController.allowsEditing = false + + return imagePickerController + } + + private func requestCameraAccess(completion: @escaping (_ granted: Bool) -> Void) { + AVCaptureDevice.requestAccess(for: .video) { granted in + DispatchQueue.main.async { + completion(granted) + } + } + } + + private func presentPermissionDeniedAlert() { + guard let presentingViewController = self.presentingViewController, let settingsURL = URL(string: UIApplication.openSettingsURLString) else { + return + } + + let appDisplayName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? "" + + let alert = UIAlertController(title: VectorL10n.camera, message: VectorL10n.cameraAccessNotGranted(appDisplayName), preferredStyle: .alert) + + let cancelActionTitle = Bundle.mxk_localizedString(forKey: "ok") + let cancelAction = UIAlertAction(title: cancelActionTitle, style: .cancel, handler: { _ in + }) + + let settingsActionTitle = Bundle.mxk_localizedString(forKey: "settings") + let settingsAction = UIAlertAction(title: settingsActionTitle, style: .default, handler: { _ in + UIApplication.shared.open(settingsURL, options: [:], completionHandler: { (succeed) in + if !succeed { + print("[CameraPresenter] Fails to open settings") + } + }) + }) + + alert.addAction(cancelAction) + alert.addAction(settingsAction) + + presentingViewController.present(alert, animated: true, completion: nil) + } + + private func presentCameraUnavailableAlert() { + guard let presentingViewController = self.presentingViewController else { + return + } + + let alert = UIAlertController(title: VectorL10n.camera, message: VectorL10n.cameraUnavailable, preferredStyle: .alert) + + let okAction = UIAlertAction(title: VectorL10n.accept, style: .default, handler: nil) + + alert.addAction(okAction) + + presentingViewController.present(alert, animated: true, completion: nil) + } +} + +// MARK: - UIImagePickerControllerDelegate +extension CameraPresenter: UIImagePickerControllerDelegate { + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { + if let videoURL = info[.mediaURL] as? URL { + self.delegate?.cameraPresenter(self, didSelectVideoAt: videoURL) + } else if let image = (info[.editedImage] ?? info[.originalImage]) as? UIImage, let imageData = image.jpegData(compressionQuality: Constants.jpegCompressionQuality) { + self.delegate?.cameraPresenter(self, didSelectImageData: imageData, withUTI: MXKUTI.jpeg) + } + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + self.delegate?.cameraPresenterDidCancel(self) + } +} + +// MARK: - UINavigationControllerDelegate +extension CameraPresenter: UINavigationControllerDelegate { +} From c26800937c2ccadca92e112686457c48225366f7 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 2 Aug 2019 17:04:14 +0200 Subject: [PATCH 55/75] MediaPickerViewController: Remove camera preview and display only recent captures and media albums. --- .../MediaPicker/MediaPickerViewController.h | 26 +- .../MediaPicker/MediaPickerViewController.m | 1054 ++--------------- .../MediaPicker/MediaPickerViewController.xib | 47 +- 3 files changed, 143 insertions(+), 984 deletions(-) diff --git a/Riot/Modules/MediaPicker/MediaPickerViewController.h b/Riot/Modules/MediaPicker/MediaPickerViewController.h index b6251b322..99e9365fe 100644 --- a/Riot/Modules/MediaPicker/MediaPickerViewController.h +++ b/Riot/Modules/MediaPicker/MediaPickerViewController.h @@ -43,6 +43,13 @@ */ - (void)mediaPickerController:(MediaPickerViewController *)mediaPickerController didSelectVideo:(NSURL*)videoURL; +/** + Tells the delegate that the user wants to cancel media picking. + + @param mediaPickerController the `MediaPickerViewController` instance. + */ +- (void)mediaPickerControllerDidCancel:(MediaPickerViewController *)mediaPickerController; + @optional /** Tells the delegate that the user select multiple media. @@ -55,16 +62,9 @@ @end /** + * MediaPickerViewController displays recent camera captures and photo/video albums from user library. */ -@interface MediaPickerViewController : MXKViewController - -/** - * Returns the `UINib` object initialized for a `MediaPickerViewController`. - * - * @return The initialized `UINib` object or `nil` if there were errors during initialization - * or the nib file could not be located. - */ -+ (UINib *)nib; +@interface MediaPickerViewController : MXKViewController /** * Creates and returns a new `MediaPickerViewController` object. @@ -73,7 +73,7 @@ * * @return An initialized `MediaPickerViewController` object if successful, `nil` otherwise. */ -+ (instancetype)mediaPickerViewController; ++ (instancetype)instantiate; /** The delegate for the view controller. @@ -85,5 +85,11 @@ */ @property (nonatomic) NSArray *mediaTypes; +/** + A Boolean value that determines whether users can select more than one item. + Default is NO. + */ +@property (nonatomic) BOOL allowsMultipleSelection; + @end diff --git a/Riot/Modules/MediaPicker/MediaPickerViewController.m b/Riot/Modules/MediaPicker/MediaPickerViewController.m index f8c713dbe..d33381100 100644 --- a/Riot/Modules/MediaPicker/MediaPickerViewController.m +++ b/Riot/Modules/MediaPicker/MediaPickerViewController.m @@ -32,39 +32,14 @@ #import -static void *CapturingStillImageContext = &CapturingStillImageContext; -static void *RecordingContext = &RecordingContext; +@interface MediaPickerViewController () -@interface MediaPickerViewController () { /** Observe UIApplicationWillEnterForegroundNotification to refresh captures collection when app leaves the background state. */ id UIApplicationWillEnterForegroundNotificationObserver; - BOOL isPictureCaptureEnabled; - BOOL isVideoCaptureEnabled; - - // Set up only one session at the time. - BOOL isCaptureSessionSetupInProgress; - - AVCaptureSession *captureSession; - AVCaptureDeviceInput *frontCameraInput; - AVCaptureDeviceInput *backCameraInput; - AVCaptureDeviceInput *currentCameraInput; - - AVCaptureMovieFileOutput *movieFileOutput; - AVCaptureStillImageOutput *stillImageOutput; - - AVCaptureVideoPreviewLayer *cameraPreviewLayer; - Boolean canToggleCamera; - - dispatch_queue_t cameraQueue; - - BOOL lockInterfaceRotation; - - UIAlertController *alert; - PHFetchResult *recentCaptures; /** @@ -80,9 +55,6 @@ static void *RecordingContext = &RecordingContext; BOOL isValidationInProgress; - NSTimer *updateVideoRecordingTimer; - NSDate *videoRecordStartDate; - /** Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. */ @@ -96,17 +68,6 @@ static void *RecordingContext = &RecordingContext; @property (weak, nonatomic) IBOutlet UIScrollView *mainScrollView; -@property (weak, nonatomic) IBOutlet UIView *captureViewContainer; - -@property (weak, nonatomic) IBOutlet UIView *cameraPreviewContainerView; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *cameraPreviewContainerAspectRatio; -@property (weak, nonatomic) IBOutlet UIActivityIndicatorView *cameraActivityIndicator; -@property (weak, nonatomic) IBOutlet UIButton *closeButton; -@property (weak, nonatomic) IBOutlet UIButton *cameraSwitchButton; -@property (weak, nonatomic) IBOutlet UIButton *cameraCaptureButton; -@property (weak, nonatomic) IBOutlet NSLayoutConstraint *cameraCaptureButtonWidthConstraint; -@property (weak, nonatomic) IBOutlet MXKPieChartView *cameraVideoCaptureProgressView; - @property (weak, nonatomic) IBOutlet UIView *recentCapturesCollectionContainerView; @property (weak, nonatomic) IBOutlet UICollectionView *recentCapturesCollectionView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *recentCapturesCollectionContainerViewHeightConstraint; @@ -115,21 +76,13 @@ static void *RecordingContext = &RecordingContext; @property (weak, nonatomic) IBOutlet UITableView *userAlbumsTableView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *libraryViewContainerViewHeightConstraint; -@property (nonatomic) UIBackgroundTaskIdentifier backgroundRecordingID; - @end @implementation MediaPickerViewController #pragma mark - Class methods -+ (UINib *)nib -{ - return [UINib nibWithNibName:NSStringFromClass([MediaPickerViewController class]) - bundle:[NSBundle bundleForClass:[MediaPickerViewController class]]]; -} - -+ (instancetype)mediaPickerViewController ++ (instancetype)instantiate { return [[[self class] alloc] initWithNibName:NSStringFromClass([MediaPickerViewController class]) bundle:[NSBundle bundleForClass:[MediaPickerViewController class]]]; @@ -145,13 +98,28 @@ static void *RecordingContext = &RecordingContext; self.enableBarTintColorStatusChange = NO; self.rageShakeManager = [RageShakeManager sharedManager]; - cameraQueue = dispatch_queue_create("media.picker.vc.camera", NULL); - canToggleCamera = YES; - // Keep visible the status bar by default. isStatusBarHidden = NO; +} + +- (void)dealloc +{ + if (kThemeServiceDidChangeThemeNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver]; + kThemeServiceDidChangeThemeNotificationObserver = nil; + } - isCaptureSessionSetupInProgress = NO; + if (UIApplicationWillEnterForegroundNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:UIApplicationWillEnterForegroundNotificationObserver]; + UIApplicationWillEnterForegroundNotificationObserver = nil; + } + + [self dismissImageValidationView]; + + userAlbumsQueue = nil; + userAlbums = nil; } - (void)viewDidLoad @@ -159,32 +127,29 @@ static void *RecordingContext = &RecordingContext; [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. + self.title = NSLocalizedStringFromTable(@"media_picker_title", @"Vector", nil); + + MXWeakify(self); + + UIBarButtonItem *closeBarButtonItem = [[MXKBarButtonItem alloc] initWithTitle:NSLocalizedStringFromTable(@"cancel", @"Vector", nil) style:UIBarButtonItemStylePlain action:^{ + MXStrongifyAndReturnIfNil(self); + [self.delegate mediaPickerControllerDidCancel:self]; + }]; + + self.navigationItem.rightBarButtonItem = closeBarButtonItem; + // Register collection view cell class - [self.recentCapturesCollectionView registerClass:MXKMediaCollectionViewCell.class forCellWithReuseIdentifier:[MXKMediaCollectionViewCell defaultReuseIdentifier]]; + [self.recentCapturesCollectionView registerNib:MXKMediaCollectionViewCell.nib forCellWithReuseIdentifier:[MXKMediaCollectionViewCell defaultReuseIdentifier]]; // Register album table view cell class - [self.userAlbumsTableView registerClass:MediaAlbumTableCell.class forCellReuseIdentifier:[MediaAlbumTableCell defaultReuseIdentifier]]; + [self.userAlbumsTableView registerNib:MediaAlbumTableCell.nib forCellReuseIdentifier:[MediaAlbumTableCell defaultReuseIdentifier]]; + self.userAlbumsTableView.alwaysBounceVertical = NO; // Force UI refresh according to selected media types - Set default media type if none. self.mediaTypes = _mediaTypes ? _mediaTypes : @[(NSString *)kUTTypeImage]; - // Check camera access before set up AV capture - [self checkDeviceAuthorizationStatus]; - - // Set camera preview background - self.cameraPreviewContainerView.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0]; - - // The top buttons background is circular - self.closeButton.layer.cornerRadius = self.closeButton.frame.size.width / 2; - self.closeButton.clipsToBounds = YES; - self.cameraSwitchButton.layer.cornerRadius = self.cameraSwitchButton.frame.size.width / 2; - self.cameraSwitchButton.clipsToBounds = YES; - - self.cameraVideoCaptureProgressView.progress = 0; - - [self setBackgroundRecordingID:UIBackgroundTaskInvalid]; - - MXWeakify(self); + // Check photo library access + [self checkPhotoLibraryAuthorizationStatus]; // Observe UIApplicationWillEnterForegroundNotification to refresh captures collection when app leaves the background state. UIApplicationWillEnterForegroundNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -212,9 +177,6 @@ static void *RecordingContext = &RecordingContext; self.activityIndicator.backgroundColor = ThemeService.shared.theme.overlayBackgroundColor; - self.cameraVideoCaptureProgressView.progressColor = ThemeService.shared.theme.backgroundColor; - self.cameraVideoCaptureProgressView.unprogressColor = [UIColor clearColor]; - self.userAlbumsTableView.backgroundColor = ThemeService.shared.theme.backgroundColor; self.view.backgroundColor = ThemeService.shared.theme.backgroundColor; self.userAlbumsTableView.separatorColor = ThemeService.shared.theme.lineBreakColor; @@ -234,23 +196,8 @@ static void *RecordingContext = &RecordingContext; - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; - - // Here the views frames are ready, set up the camera preview if it is not already done. - if (!captureSession) - { - // Adjust camera preview ratio - [self handleScreenOrientation]; - [self setupAVCapture]; - } -} -- (void)dealloc -{ - // Check whether destroy is required - if (captureSession) - { - [self destroy]; - } + [self updateRecentCapturesCollectionViewHeightIfNeeded]; } - (void)didReceiveMemoryWarning @@ -262,6 +209,8 @@ static void *RecordingContext = &RecordingContext; - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; + + [self userInterfaceThemeDidChange]; // Screen tracking [[Analytics sharedInstance] trackScreen:@"MediaPicker"]; @@ -273,153 +222,84 @@ static void *RecordingContext = &RecordingContext; [self reloadRecentCapturesCollection]; [self reloadUserLibraryAlbums]; - - // Hide the navigation bar, and force the preview camera to be at the top (behing the status bar) - self.navigationController.navigationBarHidden = YES; - self.mainScrollView.contentOffset = CGPointMake(0, 0); -} - -- (void)viewWillDisappear:(BOOL)animated -{ - [super viewWillDisappear:animated]; - - self.navigationController.navigationBarHidden = NO; -} - -- (void)viewDidAppear:(BOOL)animated -{ - [super viewDidAppear:animated]; -} - -- (BOOL)shouldAutorotate -{ - // Disable autorotation of the interface when recording is in progress. - return !lockInterfaceRotation; -} - -- (UIInterfaceOrientationMask)supportedInterfaceOrientations -{ - return UIInterfaceOrientationMaskAll; } - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id )coordinator { [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; - - // Hide camera preview during transition - cameraPreviewLayer.hidden = YES; - [self.cameraActivityIndicator startAnimating]; - + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(coordinator.transitionDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - - [self handleScreenOrientation]; - - // Show camera preview with delay to hide awful animation - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - - [self.cameraActivityIndicator stopAnimating]; - self->cameraPreviewLayer.hidden = NO; - - }); + + [self updateRecentCapturesCollectionViewHeightIfNeeded]; }); } -// The following methods are deprecated since iOS 8 -- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration +- (void)checkPhotoLibraryAuthorizationStatus { - [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration]; - - [[cameraPreviewLayer connection] setVideoOrientation:(AVCaptureVideoOrientation)toInterfaceOrientation]; -} - -- (void)checkDeviceAuthorizationStatus -{ - NSString *appDisplayName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; - - [MXKTools checkAccessForMediaType:AVMediaTypeVideo - manualChangeMessage:[NSString stringWithFormat:NSLocalizedStringFromTable(@"camera_access_not_granted", @"Vector", nil), appDisplayName] - showPopUpInViewController:self - completionHandler:^(BOOL granted) { - - if (granted) - { - // Load recent captures if this is not already done - if (!self->recentCaptures.count) - { + [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) { + + switch (status) { + case PHAuthorizationStatusAuthorized: + // Load recent captures if this is not already done + if (!self->recentCaptures.count) + { + dispatch_async(dispatch_get_main_queue(), ^{ + + [self reloadRecentCapturesCollection]; + [self reloadUserLibraryAlbums]; + + }); + } + break; + default: dispatch_async(dispatch_get_main_queue(), ^{ - - [self reloadRecentCapturesCollection]; - [self reloadUserLibraryAlbums]; - + [self presentPermissionDeniedAlert]; }); - } + break; } }]; } -- (void)updateVideoRecordingDuration +- (void)presentPermissionDeniedAlert { - NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:videoRecordStartDate]; + NSString *appDisplayName = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"]; - // The progress animation makes a turn in 30 sec. - NSUInteger loopNb = (int)(duration/30); + NSString *message = [NSString stringWithFormat:NSLocalizedStringFromTable(@"photo_library_access_not_granted", @"Vector", nil), appDisplayName]; - // Switch progress color at each turn - if (loopNb & 0x01) - { - if (self.cameraVideoCaptureProgressView.progressColor != [UIColor lightGrayColor]) - { - self.cameraVideoCaptureProgressView.progressColor = [UIColor lightGrayColor]; - self.cameraVideoCaptureProgressView.unprogressColor = ThemeService.shared.theme.backgroundColor; - } - } - else if (self.cameraVideoCaptureProgressView.progressColor != ThemeService.shared.theme.backgroundColor) - { - self.cameraVideoCaptureProgressView.progressColor = ThemeService.shared.theme.backgroundColor; - self.cameraVideoCaptureProgressView.unprogressColor = [UIColor lightGrayColor]; - } + UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"media_picker_title", @"Vector", nil) + message:message + preferredStyle:UIAlertControllerStyleAlert]; - duration -= loopNb * 30; + [alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] + style:UIAlertActionStyleCancel + handler:^(UIAlertAction * action) { + [self.delegate mediaPickerControllerDidCancel:self]; + }]]; - self.cameraVideoCaptureProgressView.progress = duration / 30; + NSURL *settingsURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString]; + + [alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"settings"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + [UIApplication.sharedApplication openURL:settingsURL options:@{} completionHandler:^(BOOL success) { + if (success) + { + [self.delegate mediaPickerControllerDidCancel:self]; + } + else + { + NSLog(@"[MediaPickerVC] Fails to open settings"); + } + }]; + }]]; + + [self presentViewController:alert animated:YES completion:nil]; } #pragma mark - - (void)setMediaTypes:(NSArray *)mediaTypes { - if ([mediaTypes indexOfObject:(NSString *)kUTTypeImage] != NSNotFound) - { - isPictureCaptureEnabled = YES; - [self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_capture"] forState:UIControlStateNormal]; - [self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_capture"] forState:UIControlStateHighlighted]; - - // Check whether video capture should be enabled too - if ([mediaTypes indexOfObject:(NSString *)kUTTypeMovie] != NSNotFound) - { - isVideoCaptureEnabled = YES; - } - else - { - isVideoCaptureEnabled = NO; - } - } - else if ([mediaTypes indexOfObject:(NSString *)kUTTypeMovie] != NSNotFound) - { - isPictureCaptureEnabled = NO; - isVideoCaptureEnabled = YES; - - [self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_video_capture"] forState:UIControlStateNormal]; - [self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_video_capture"] forState:UIControlStateHighlighted]; - } - - if (isVideoCaptureEnabled) - { - // Add a long gesture recognizer on cameraCaptureButton (in order to handle video recording) - UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(onLongPressGesture:)]; - [self.cameraCaptureButton addGestureRecognizer:longPress]; - } - if (_mediaTypes != mediaTypes) { _mediaTypes = mediaTypes; @@ -429,96 +309,27 @@ static void *RecordingContext = &RecordingContext; } } -#pragma mark - Camera preview layout - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView -{ - if (scrollView == _mainScrollView) - { - // Force camera preview at the top (behind the status bar) - if (scrollView.contentOffset.y < 0) - { - scrollView.contentOffset = CGPointMake(0, 0); - } - } -} - #pragma mark - UI Refresh/Update -- (void)handleScreenOrientation +- (void)updateRecentCapturesCollectionViewHeightIfNeeded { - UIInterfaceOrientation screenOrientation = [[UIApplication sharedApplication] statusBarOrientation]; - - // Check whether the preview ratio must be inverted - CGFloat ratio = 0.0; - switch (screenOrientation) - { - case UIInterfaceOrientationPortrait: - case UIInterfaceOrientationPortraitUpsideDown: - { - if (self.cameraPreviewContainerAspectRatio.multiplier > 1) - { - ratio = 15.0 / 22.0; - } - break; - } - case UIInterfaceOrientationLandscapeRight: - case UIInterfaceOrientationLandscapeLeft: - { - if (self.cameraPreviewContainerAspectRatio.multiplier < 1) - { - CGSize screenSize = [[UIScreen mainScreen] bounds].size; - ratio = screenSize.width / screenSize.height; - } - break; - } - default: - break; - } - - if (ratio) - { - // Replace the current ratio constraint by a new one - [NSLayoutConstraint deactivateConstraints:@[self.cameraPreviewContainerAspectRatio]]; - - self.cameraPreviewContainerAspectRatio = [NSLayoutConstraint constraintWithItem:self.cameraPreviewContainerView - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:self.cameraPreviewContainerView - attribute:NSLayoutAttributeHeight - multiplier:ratio - constant:0.0f]; - self.cameraPreviewContainerAspectRatio.priority = 750; - - [NSLayoutConstraint activateConstraints:@[self.cameraPreviewContainerAspectRatio]]; - - // Force layout refresh - [self.view layoutIfNeeded]; - - if (self.navigationController.navigationBarHidden) - { - // Force the main scroller at the top - _mainScrollView.contentOffset = CGPointMake(0, 0); - } - } - - // Refresh camera preview layer - if (cameraPreviewLayer) - { - [[cameraPreviewLayer connection] setVideoOrientation:(AVCaptureVideoOrientation)screenOrientation]; - cameraPreviewLayer.frame = self.cameraPreviewContainerView.bounds; - } - // Update Captures collection display if (recentCaptures.count) { // recents Collection is limited to the first 12 assets NSInteger recentsCount = ((recentCaptures.count > 12) ? 12 : recentCaptures.count); + + CGFloat collectionViewHeight = (ceil(recentsCount / 4.0) * ((self.view.frame.size.width - 6) / 4)) + 10; - self.recentCapturesCollectionContainerViewHeightConstraint.constant = (ceil(recentsCount / 4.0) * ((self.view.frame.size.width - 6) / 4)) + 10; - [self.recentCapturesCollectionContainerView needsUpdateConstraints]; - - [self.recentCapturesCollectionView reloadData]; + if (self.recentCapturesCollectionContainerViewHeightConstraint.constant != collectionViewHeight) + { + self.recentCapturesCollectionContainerViewHeightConstraint.constant = collectionViewHeight; + [self.recentCapturesCollectionView reloadData]; + } + } + else + { + self.recentCapturesCollectionContainerViewHeightConstraint.constant = 0; } } @@ -568,19 +379,16 @@ static void *RecordingContext = &RecordingContext; if (recentCaptures.count) { self.recentCapturesCollectionView.hidden = NO; - - // recents Collection is limited to the first 12 assets - NSInteger recentsCount = ((recentCaptures.count > 12) ? 12 : recentCaptures.count); - self.recentCapturesCollectionContainerViewHeightConstraint.constant = (ceil(recentsCount / 4.0) * ((self.view.frame.size.width - 6) / 4)) + 10; - [self.recentCapturesCollectionContainerView needsUpdateConstraints]; - [self.recentCapturesCollectionView reloadData]; } else { self.recentCapturesCollectionView.hidden = YES; - self.recentCapturesCollectionContainerViewHeightConstraint.constant = 0; } + + // Force call updateRecentCapturesCollectionViewHeightIfNeeded + [self.recentCapturesCollectionContainerView setNeedsLayout]; + [self.recentCapturesCollectionContainerView layoutIfNeeded]; } - (void)reloadUserLibraryAlbums @@ -676,41 +484,6 @@ static void *RecordingContext = &RecordingContext; }); } -- (void)launchPreview -{ - [self.cameraActivityIndicator stopAnimating]; - - self.cameraSwitchButton.enabled = YES; - - if (isPictureCaptureEnabled) - { - self.cameraCaptureButtonWidthConstraint.constant = 83; - - // Switch back to picture mode by default - [self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_capture"] forState:UIControlStateNormal]; - [self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_capture"] forState:UIControlStateHighlighted]; - - if (captureSession) - { - [captureSession setSessionPreset:AVCaptureSessionPresetPhoto]; - } - } - else - { - self.cameraCaptureButtonWidthConstraint.constant = 93; - - [self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_video_capture"] forState:UIControlStateNormal]; - [self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_video_capture"] forState:UIControlStateHighlighted]; - - if (captureSession) - { - [captureSession setSessionPreset:AVCaptureSessionPresetHigh]; - } - } - - self.cameraCaptureButton.enabled = YES; -} - #pragma mark - Validation step - (void)didSelectAsset:(PHAsset *)asset @@ -1030,619 +803,8 @@ static void *RecordingContext = &RecordingContext; } } -#pragma mark - Override MXKViewController - -- (void)destroy -{ - [self stopAVCapture]; - - if (kThemeServiceDidChangeThemeNotificationObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:kThemeServiceDidChangeThemeNotificationObserver]; - kThemeServiceDidChangeThemeNotificationObserver = nil; - } - - if (UIApplicationWillEnterForegroundNotificationObserver) - { - [[NSNotificationCenter defaultCenter] removeObserver:UIApplicationWillEnterForegroundNotificationObserver]; - UIApplicationWillEnterForegroundNotificationObserver = nil; - } - - [self dismissImageValidationView]; - - if (updateVideoRecordingTimer) - { - [updateVideoRecordingTimer invalidate]; - updateVideoRecordingTimer = nil; - } - - cameraQueue = nil; - userAlbumsQueue = nil; - userAlbums = nil; - - [super destroy]; -} - #pragma mark - Action -- (IBAction)onLongPressGesture:(UILongPressGestureRecognizer*)longPressGestureRecognizer -{ - if (longPressGestureRecognizer.state == UIGestureRecognizerStateBegan) - { - UIView* view = longPressGestureRecognizer.view; - - // Check the view on which long press has been detected - if (view == self.cameraCaptureButton) - { - self.cameraCaptureButtonWidthConstraint.constant = 93; - - [self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_video_capture"] forState:UIControlStateNormal]; - [self.cameraCaptureButton setImage:[UIImage imageNamed:@"camera_video_capture"] forState:UIControlStateHighlighted]; - - if (captureSession) - { - [captureSession setSessionPreset:AVCaptureSessionPresetHigh]; - } - - // Record a new video - [self startMovieRecording]; - } - } - else if (longPressGestureRecognizer.state == UIGestureRecognizerStateEnded || longPressGestureRecognizer.state == UIGestureRecognizerStateCancelled || longPressGestureRecognizer.state == UIGestureRecognizerStateFailed) - { - [self stopMovieRecording]; - } -} - -- (IBAction)onButtonPressed:(id)sender -{ - if (sender == self.closeButton) - { - // Close has been pressed - [self withdrawViewControllerAnimated:YES completion:nil]; - } - else if (sender == self.cameraSwitchButton) - { - [self toggleCamera]; - } - else if (sender == self.cameraCaptureButton) - { - if (isPictureCaptureEnabled) - { - [self snapStillImage]; - } - } -} - -#pragma mark - Capture handling methods - -- (void)setupAVCapture -{ - if (captureSession || isCaptureSessionSetupInProgress) - { - NSLog(@"[MediaPickerVC] Attemping to setup AVCapture when it is already started!"); - return; - } - if (!cameraQueue) - { - NSLog(@"[MediaPickerVC] Attemping to setup AVCapture when it is being destroyed!"); - return; - } - - isCaptureSessionSetupInProgress = YES; - - [self.cameraActivityIndicator startAnimating]; - - MXWeakify(self); - - dispatch_async(cameraQueue, ^{ - - MXStrongifyAndReturnIfNil(self); - - // Get the Camera Device - AVCaptureDevice *frontCamera = nil; - AVCaptureDevice *backCamera = nil; - NSArray *cameras = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; - for (AVCaptureDevice *thisCamera in cameras) - { - if (thisCamera.position == AVCaptureDevicePositionFront) - { - frontCamera = thisCamera; - } - else if (thisCamera.position == AVCaptureDevicePositionBack) - { - backCamera = thisCamera; - } - - NSError *lockError = nil; - [thisCamera lockForConfiguration:&lockError]; - if (!lockError) - { - if ([thisCamera isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) - { - [thisCamera setFocusMode:AVCaptureFocusModeContinuousAutoFocus]; - } - else if ([thisCamera isFocusModeSupported:AVCaptureFocusModeAutoFocus]) - { - [thisCamera setFocusMode:AVCaptureFocusModeAutoFocus]; - } - - if ([thisCamera isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance]) - { - [thisCamera setWhiteBalanceMode:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance]; - } - if ([thisCamera isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) - { - [thisCamera setExposureMode:AVCaptureExposureModeContinuousAutoExposure]; - } - thisCamera.videoZoomFactor = 1.0; - - [thisCamera unlockForConfiguration]; - } - else - { - NSLog(@"[MediaPickerVC] Failed to take out lock on camera. Device not setup properly."); - } - } - - self->currentCameraInput = nil; - NSError *error = nil; - if (frontCamera) - { - self->frontCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:frontCamera error:&error]; - if (error) - { - NSLog(@"[MediaPickerVC] Error: %@", error); - } - - if (self->frontCameraInput == nil) - { - NSLog(@"[MediaPickerVC] Error creating front camera capture input"); - } - else - { - self->currentCameraInput = self->frontCameraInput; - } - } - - if (backCamera) - { - error = nil; - self->backCameraInput = [[AVCaptureDeviceInput alloc] initWithDevice:backCamera error:&error]; - if (error) - { - NSLog(@"[MediaPickerVC] Error: %@", error); - } - - if (self->backCameraInput == nil) - { - NSLog(@"[MediaPickerVC] Error creating back camera capture input"); - } - else - { - self->currentCameraInput = self->backCameraInput; - } - } - - dispatch_async(dispatch_get_main_queue(), ^{ - self.cameraSwitchButton.hidden = (!frontCamera || !backCamera); - }); - - if (self->currentCameraInput) - { - // Create the AVCapture Session - self->captureSession = [[AVCaptureSession alloc] init]; - - if (self->isPictureCaptureEnabled) - { - [self->captureSession setSessionPreset:AVCaptureSessionPresetPhoto]; - } - else if (self->isVideoCaptureEnabled) - { - [self->captureSession setSessionPreset:AVCaptureSessionPresetHigh]; - } - - self->cameraPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self->captureSession]; - self->cameraPreviewLayer.masksToBounds = NO; - self->cameraPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;//AVLayerVideoGravityResizeAspect; - self->cameraPreviewLayer.backgroundColor = [[UIColor blackColor] CGColor]; -// cameraPreviewLayer.borderWidth = 2; - - dispatch_async(dispatch_get_main_queue(), ^{ - - [[self->cameraPreviewLayer connection] setVideoOrientation:(AVCaptureVideoOrientation)[[UIApplication sharedApplication] statusBarOrientation]]; - [self->cameraPreviewLayer connection].videoScaleAndCropFactor = 1.0; - self->cameraPreviewLayer.frame = self.cameraPreviewContainerView.bounds; - self->cameraPreviewLayer.hidden = YES; - - [self.cameraPreviewContainerView.layer addSublayer:self->cameraPreviewLayer]; - - }); - - [self->captureSession addInput:self->currentCameraInput]; - - AVCaptureDevice *audioDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject]; - AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error]; - - if (error) - { - NSLog(@"[MediaPickerVC] Error: %@", error); - } - - if ([self->captureSession canAddInput:audioDeviceInput]) - { - [self->captureSession addInput:audioDeviceInput]; - } - - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(caughtAVRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(AVCaptureSessionDidStartRunning:) name:AVCaptureSessionDidStartRunningNotification object:nil]; - - [self->captureSession startRunning]; - - self->movieFileOutput = [[AVCaptureMovieFileOutput alloc] init]; - if ([self->captureSession canAddOutput:self->movieFileOutput]) - { - [self->captureSession addOutput:self->movieFileOutput]; - AVCaptureConnection *connection = [self->movieFileOutput connectionWithMediaType:AVMediaTypeVideo]; - if ([connection isVideoStabilizationSupported]) - { - // Available on iOS 8 and later - [connection setPreferredVideoStabilizationMode:YES]; - } - } - [self->movieFileOutput addObserver:self forKeyPath:@"recording" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:RecordingContext]; - - self->stillImageOutput = [[AVCaptureStillImageOutput alloc] init]; - if ([self->captureSession canAddOutput:self->stillImageOutput]) - { - [self->stillImageOutput setOutputSettings:@{AVVideoCodecKey : AVVideoCodecJPEG}]; - [self->captureSession addOutput:self->stillImageOutput]; - } - [self->stillImageOutput addObserver:self forKeyPath:@"capturingStillImage" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:CapturingStillImageContext]; - } - else - { - dispatch_async(dispatch_get_main_queue(), ^{ - [self.cameraActivityIndicator stopAnimating]; - }); - } - - self->isCaptureSessionSetupInProgress = NO; - - }); -} - - -- (void)stopAVCapture -{ - [NSObject cancelPreviousPerformRequestsWithTarget:self]; - - if (captureSession) - { - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(AVCaptureSessionDidStopRunning:) name:AVCaptureSessionDidStopRunningNotification object:nil]; - - [captureSession stopRunning]; - } -} - -- (void)tearDownAVCapture -{ - if (!cameraQueue) - { - NSLog(@"[MediaPickerVC] Attemping to tear down AVCapture when it is being destroyed!"); - return; - } - - dispatch_sync(cameraQueue, ^{ - - self->frontCameraInput = nil; - self->backCameraInput = nil; - self->captureSession = nil; - - if (self->movieFileOutput) - { - [self->movieFileOutput removeObserver:self forKeyPath:@"recording" context:RecordingContext]; - self->movieFileOutput = nil; - } - - if (self->stillImageOutput) - { - [self->stillImageOutput removeObserver:self forKeyPath:@"capturingStillImage" context:CapturingStillImageContext]; - self->stillImageOutput = nil; - } - - self->currentCameraInput = nil; - - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureSessionRuntimeErrorNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureSessionDidStartRunningNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureSessionDidStopRunningNotification object:nil]; - }); -} - -- (void)caughtAVRuntimeError:(NSNotification*)note -{ - NSError *error = [note userInfo][AVCaptureSessionErrorKey]; - NSLog(@"[MediaPickerVC] AV Session Error: %@", error); - - dispatch_async(dispatch_get_main_queue(), ^{ - [self tearDownAVCapture]; - // Retry - [self performSelector:@selector(setupAVCapture) withObject:nil afterDelay:1.0]; - }); -} - -- (void)AVCaptureSessionDidStartRunning:(NSNotification*)note -{ - // Show camera preview with delay to hide camera settlement - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - - [self.cameraActivityIndicator stopAnimating]; - self->cameraPreviewLayer.hidden = NO; - - }); -} - -- (void)AVCaptureSessionDidStopRunning:(NSNotification*)note -{ - [self tearDownAVCapture]; -} - -- (void)toggleCamera -{ - if (frontCameraInput && backCameraInput) - { - dispatch_async(dispatch_get_main_queue(), ^{ - if (!self->canToggleCamera) - { - return; - } - self->canToggleCamera = NO; - - AVCaptureDeviceInput *newInput = nil; - AVCaptureDeviceInput *oldInput = nil; - if (self->currentCameraInput == self->frontCameraInput) - { - newInput = self->backCameraInput; - oldInput = self->frontCameraInput; - } - else - { - newInput = self->frontCameraInput; - oldInput = self->backCameraInput; - } - - dispatch_async(self->cameraQueue, ^{ - - [self->captureSession beginConfiguration]; - [self->captureSession removeInput:oldInput]; - if ([self->captureSession canAddInput:newInput]) { - [self->captureSession addInput:newInput]; - self->currentCameraInput = newInput; - } - [self->captureSession commitConfiguration]; - - dispatch_async(dispatch_get_main_queue(), ^{ - - [self.cameraActivityIndicator stopAnimating]; - self->cameraPreviewLayer.hidden = NO; - self->canToggleCamera = YES; - }); - }); - - [self.cameraActivityIndicator startAnimating]; - self->cameraPreviewLayer.hidden = YES; - }); - } -} - -- (void)startMovieRecording -{ - self.cameraCaptureButton.enabled = NO; - - MXWeakify(self); - - dispatch_async(cameraQueue, ^{ - - MXStrongifyAndReturnIfNil(self); - - if (![self->movieFileOutput isRecording]) - { - self->lockInterfaceRotation = YES; - - if ([[UIDevice currentDevice] isMultitaskingSupported]) - { - // Setup background task. This is needed because the captureOutput:didFinishRecordingToOutputFileAtURL: callback is not received until the App returns to the foreground unless you request background execution time. - [self setBackgroundRecordingID:[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ - - [[UIApplication sharedApplication] endBackgroundTask:self.backgroundRecordingID]; - self.backgroundRecordingID = UIBackgroundTaskInvalid; - - NSLog(@"[MediaPickerVC] pauseInBackgroundTask : %08lX expired", (unsigned long)self.backgroundRecordingID); - - }]]; - - NSLog(@"[MediaPickerVC] pauseInBackgroundTask : %08lX starts", (unsigned long)self.backgroundRecordingID); - } - - // Update the orientation on the movie file output video connection before starting recording. - [[self->movieFileOutput connectionWithMediaType:AVMediaTypeVideo] setVideoOrientation:[[self->cameraPreviewLayer connection] videoOrientation]]; - - // Turning OFF flash for video recording - [MediaPickerViewController setFlashMode:AVCaptureFlashModeOff forDevice:[self->currentCameraInput device]]; - - // Start recording to a temporary file. - NSString *outputFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[@"movie" stringByAppendingPathExtension:@"mov"]]; - [self->movieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:outputFilePath] recordingDelegate:self]; - } - }); -} - -- (void)stopMovieRecording -{ - dispatch_async(cameraQueue, ^{ - - if ([self->movieFileOutput isRecording]) - { - [self->movieFileOutput stopRecording]; - } - }); -} - -- (void)snapStillImage -{ - self.cameraCaptureButton.enabled = NO; - - MXWeakify(self); - - dispatch_async(cameraQueue, ^{ - - MXStrongifyAndReturnIfNil(self); - - // Update the orientation on the still image output video connection before capturing. - [[self->stillImageOutput connectionWithMediaType:AVMediaTypeVideo] setVideoOrientation:[[self->cameraPreviewLayer connection] videoOrientation]]; - - // Flash set to Auto for Still Capture - [MediaPickerViewController setFlashMode:AVCaptureFlashModeAuto forDevice:[self->currentCameraInput device]]; - - // Capture a still image. - [self->stillImageOutput captureStillImageAsynchronouslyFromConnection:[self->stillImageOutput connectionWithMediaType:AVMediaTypeVideo] completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) { - - if (imageDataSampleBuffer) - { - NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer]; - UIImage *image = [[UIImage alloc] initWithData:imageData]; - - // Open image validation view - [self validateSelectedImage:image responseHandler:^(BOOL isValidated) { - - if (isValidated) - { - // Send the original image - [self.delegate mediaPickerController:self didSelectImage:imageData withMimeType:@"image/jpeg" isPhotoLibraryAsset:NO]; - } - - }]; - - // Relaunch preview - [self launchPreview]; - } - }]; - }); -} - -+ (void)setFlashMode:(AVCaptureFlashMode)flashMode forDevice:(AVCaptureDevice *)device -{ - if ([device hasFlash] && [device isFlashModeSupported:flashMode]) - { - NSError *error = nil; - if ([device lockForConfiguration:&error]) - { - [device setFlashMode:flashMode]; - [device unlockForConfiguration]; - } - else - { - NSLog(@"[MediaPickerVC] %@", error); - } - } -} - -- (void)runStillImageCaptureAnimation -{ - dispatch_async(dispatch_get_main_queue(), ^{ - [self->cameraPreviewLayer setOpacity:0.0]; - - [UIView animateWithDuration:.25 animations:^{ - [self->cameraPreviewLayer setOpacity:1.0]; - }]; - }); -} - -#pragma mark - KVO - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context -{ - if (context == CapturingStillImageContext) - { - BOOL isCapturingStillImage = [change[NSKeyValueChangeNewKey] boolValue]; - - if (isCapturingStillImage) - { - [self runStillImageCaptureAnimation]; - } - } - else if (context == RecordingContext) - { - BOOL isRecording = [change[NSKeyValueChangeNewKey] boolValue]; - - dispatch_async(dispatch_get_main_queue(), ^{ - - if (isRecording) - { - self.cameraSwitchButton.enabled = NO; - - self->videoRecordStartDate = [NSDate date]; - - self.cameraVideoCaptureProgressView.hidden = NO; - self->updateVideoRecordingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateVideoRecordingDuration) userInfo:nil repeats:YES]; - - self.cameraCaptureButton.enabled = YES; - } - else - { - self.cameraVideoCaptureProgressView.hidden = YES; - [self->updateVideoRecordingTimer invalidate]; - self->updateVideoRecordingTimer = nil; - self.cameraVideoCaptureProgressView.progress = 0; - - // The preview will be restored during captureOutput:didFinishRecordingToOutputFileAtURL: callback. - } - }); - } - else - { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; - } -} - -#pragma mark - File Output Delegate - -- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error -{ - if (error) - { - NSLog(@"[MediaPickerVC] %@", error); - } - - self.cameraCaptureButton.enabled = NO; - - lockInterfaceRotation = NO; - - // Validate the new captured video - [self validateSelectedVideo:outputFileURL responseHandler:^(BOOL isValidated) { - - if (isValidated) - { - // Send the captured video - [self.delegate mediaPickerController:self didSelectVideo:outputFileURL]; - } - - // Remove the temporary file - [[NSFileManager defaultManager] removeItemAtURL:outputFileURL error:nil]; - - }]; - - // Relaunch preview - [self launchPreview]; - - UIBackgroundTaskIdentifier backgroundRecordingID = [self backgroundRecordingID]; - if (backgroundRecordingID != UIBackgroundTaskInvalid) - { - [[UIApplication sharedApplication] endBackgroundTask:backgroundRecordingID]; - [self setBackgroundRecordingID:UIBackgroundTaskInvalid]; - NSLog(@"[MediaPickerVC] >>>>> background pause task finished"); - } -} #pragma mark - UICollectionViewDataSource @@ -1845,7 +1007,7 @@ static void *RecordingContext = &RecordingContext; // Enable multiselection only if the delegate is configured to receive them if ([_delegate respondsToSelector:@selector(mediaPickerController:didSelectAssets:)]) { - albumContentViewController.allowsMultipleSelection = YES; + albumContentViewController.allowsMultipleSelection = self.allowsMultipleSelection; } // Hide back button title diff --git a/Riot/Modules/MediaPicker/MediaPickerViewController.xib b/Riot/Modules/MediaPicker/MediaPickerViewController.xib index c0dea7aa8..fe465561d 100644 --- a/Riot/Modules/MediaPicker/MediaPickerViewController.xib +++ b/Riot/Modules/MediaPicker/MediaPickerViewController.xib @@ -1,26 +1,17 @@ - + - - + + - - - - - - - - - @@ -37,7 +28,7 @@ - + @@ -58,9 +49,6 @@ - - - @@ -80,9 +68,6 @@ - - - @@ -92,10 +77,10 @@
    - + @@ -173,7 +155,7 @@ - + @@ -199,6 +181,7 @@ + @@ -208,12 +191,18 @@ + + + + + + @@ -222,16 +211,18 @@ - + + +
    - +
    From 7602c5721380269f6c1084b3a1941db7bd3f3fea Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 2 Aug 2019 17:06:52 +0200 Subject: [PATCH 56/75] MediaAlbumContentViewController: Fix some retain cycle issues. --- .../MediaPicker/Library/MediaAlbumContentViewController.h | 2 +- .../MediaPicker/Library/MediaAlbumContentViewController.m | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Riot/Modules/MediaPicker/Library/MediaAlbumContentViewController.h b/Riot/Modules/MediaPicker/Library/MediaAlbumContentViewController.h index 987ad6717..32424d8eb 100644 --- a/Riot/Modules/MediaPicker/Library/MediaAlbumContentViewController.h +++ b/Riot/Modules/MediaPicker/Library/MediaAlbumContentViewController.h @@ -69,7 +69,7 @@ /** The delegate for the view controller. */ -@property (nonatomic) id delegate; +@property (nonatomic, weak) id delegate; /** The array of the media types listed by the view controller (default value is an array containing kUTTypeImage). diff --git a/Riot/Modules/MediaPicker/Library/MediaAlbumContentViewController.m b/Riot/Modules/MediaPicker/Library/MediaAlbumContentViewController.m index f74637cde..03bc728c9 100644 --- a/Riot/Modules/MediaPicker/Library/MediaAlbumContentViewController.m +++ b/Riot/Modules/MediaPicker/Library/MediaAlbumContentViewController.m @@ -89,11 +89,15 @@ self.mediaTypes = @[(NSString *)kUTTypeImage]; } + MXWeakify(self); + // Observe UIApplicationWillEnterForegroundNotification to refresh captures collection when app leaves the background state. UIApplicationWillEnterForegroundNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillEnterForegroundNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + // Force a full refresh of the displayed collection - self.assetsCollection = _assetsCollection; + self.assetsCollection = self->_assetsCollection; }]; @@ -105,6 +109,8 @@ // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + [self userInterfaceThemeDidChange]; }]; From edc03eeb48d64530c196c8c31eba201df93ce778 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 2 Aug 2019 17:09:32 +0200 Subject: [PATCH 57/75] Media picker: Create a coordinator. --- .../MediaPicker/MediaPickerCoordinator.swift | 97 ++++++++++++++ ...ediaPickerCoordinatorBridgePresenter.swift | 124 ++++++++++++++++++ .../MediaPickerCoordinatorType.swift | 31 +++++ Riot/SupportingFiles/Riot-Bridging-Header.h | 1 + 4 files changed, 253 insertions(+) create mode 100644 Riot/Modules/MediaPicker/MediaPickerCoordinator.swift create mode 100644 Riot/Modules/MediaPicker/MediaPickerCoordinatorBridgePresenter.swift create mode 100644 Riot/Modules/MediaPicker/MediaPickerCoordinatorType.swift diff --git a/Riot/Modules/MediaPicker/MediaPickerCoordinator.swift b/Riot/Modules/MediaPicker/MediaPickerCoordinator.swift new file mode 100644 index 000000000..d3b9c2c0d --- /dev/null +++ b/Riot/Modules/MediaPicker/MediaPickerCoordinator.swift @@ -0,0 +1,97 @@ +// File created from FlowTemplate +// $ createRootCoordinator.sh Test MediaPicker +/* + 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 UIKit + +final class MediaPickerCoordinator: NSObject, MediaPickerCoordinatorType { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let mediaUTIs: [MXKUTI] + private let allowsMultipleSelection: Bool + + private let navigationRouter: NavigationRouterType + + // MARK: Public + + // Must be used only internally + var childCoordinators: [Coordinator] = [] + + weak var delegate: MediaPickerCoordinatorDelegate? + + // MARK: - Setup + + init(session: MXSession, mediaUTIs: [MXKUTI], allowsMultipleSelection: Bool) { + self.session = session + self.mediaUTIs = mediaUTIs + self.allowsMultipleSelection = allowsMultipleSelection + + self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController()) + + super.init() + } + + // MARK: - Public methods + + func start() { + let mediaTypes = self.mediaUTIs.map { (uti) -> String in + return uti.rawValue + } + + let mediaPickerViewController: MediaPickerViewController = MediaPickerViewController.instantiate() + mediaPickerViewController.mediaTypes = mediaTypes + mediaPickerViewController.allowsMultipleSelection = self.allowsMultipleSelection + self.navigationRouter.setRootModule(mediaPickerViewController) + mediaPickerViewController.delegate = self + } + + func toPresentable() -> UIViewController { + return self.navigationRouter.toPresentable() + } +} + +// MARK: - MediaPickerViewControllerDelegate +extension MediaPickerCoordinator: MediaPickerViewControllerDelegate { + + func mediaPickerController(_ mediaPickerController: MediaPickerViewController!, didSelectImage imageData: Data!, withMimeType mimetype: String!, isPhotoLibraryAsset: Bool) { + + let uti: MXKUTI? + if let mimetype = mimetype { + uti = MXKUTI(mimeType: mimetype) + } else { + uti = nil + } + + self.delegate?.mediaPickerCoordinator(self, didSelectImageData: imageData, withUTI: uti) + } + + func mediaPickerController(_ mediaPickerController: MediaPickerViewController!, didSelectVideo videoURL: URL!) { + self.delegate?.mediaPickerCoordinator(self, didSelectVideoAt: videoURL) + } + + func mediaPickerController(_ mediaPickerController: MediaPickerViewController!, didSelect assets: [PHAsset]!) { + self.delegate?.mediaPickerCoordinator(self, didSelectAssets: assets) + } + + func mediaPickerControllerDidCancel(_ mediaPickerController: MediaPickerViewController!) { + self.delegate?.mediaPickerCoordinatorDidCancel(self) + } +} diff --git a/Riot/Modules/MediaPicker/MediaPickerCoordinatorBridgePresenter.swift b/Riot/Modules/MediaPicker/MediaPickerCoordinatorBridgePresenter.swift new file mode 100644 index 000000000..0ba21d893 --- /dev/null +++ b/Riot/Modules/MediaPicker/MediaPickerCoordinatorBridgePresenter.swift @@ -0,0 +1,124 @@ +// File created from FlowTemplate +// $ createRootCoordinator.sh Test MediaPicker +/* + 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 + +@objc protocol MediaPickerCoordinatorBridgePresenterDelegate { + func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) + func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectVideoAt url: URL) + func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectAssets assets: [PHAsset]) + func mediaPickerCoordinatorBridgePresenterDidCancel(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter) +} + +/// MediaPickerCoordinatorBridgePresenter enables to start MediaPickerCoordinator from a view controller. +/// This bridge is used while waiting for global usage of coordinator pattern. +@objcMembers +final class MediaPickerCoordinatorBridgePresenter: NSObject { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let mediaUTIs: [MXKUTI] + private let allowsMultipleSelection: Bool + private var coordinator: MediaPickerCoordinator? + + // MARK: Public + + weak var delegate: MediaPickerCoordinatorBridgePresenterDelegate? + + // MARK: - Setup + + init(session: MXSession, mediaUTIs: [MXKUTI], allowsMultipleSelection: Bool) { + self.session = session + self.mediaUTIs = mediaUTIs + self.allowsMultipleSelection = allowsMultipleSelection + super.init() + } + + // MARK: - Public + + func present(from viewController: UIViewController, + sourceView: UIView?, + sourceRect: CGRect, + animated: Bool) { + let mediaPickerCoordinator = MediaPickerCoordinator(session: self.session, mediaUTIs: mediaUTIs, allowsMultipleSelection: self.allowsMultipleSelection) + mediaPickerCoordinator.delegate = self + + let mediaPickerPresentable = mediaPickerCoordinator.toPresentable() + + if let sourceView = sourceView { + + mediaPickerPresentable.modalPresentationStyle = .popover + + if let popoverPresentationController = mediaPickerPresentable.popoverPresentationController { + popoverPresentationController.sourceView = sourceView + + let finalSourceRect: CGRect + + if sourceRect != CGRect.null { + finalSourceRect = sourceRect + } else { + finalSourceRect = sourceView.bounds + } + + popoverPresentationController.sourceRect = finalSourceRect + } + } + + viewController.present(mediaPickerPresentable, animated: animated, completion: nil) + + mediaPickerCoordinator.start() + + self.coordinator = mediaPickerCoordinator + } + + func dismiss(animated: Bool, completion: (() -> Void)?) { + guard let coordinator = self.coordinator else { + return + } + coordinator.toPresentable().dismiss(animated: animated) { + self.coordinator = nil + + if let completion = completion { + completion() + } + } + } +} + +// MARK: - MediaPickerCoordinatorDelegate +extension MediaPickerCoordinatorBridgePresenter: MediaPickerCoordinatorDelegate { + + func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) { + self.delegate?.mediaPickerCoordinatorBridgePresenter(self, didSelectImageData: imageData, withUTI: uti) + } + + func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectVideoAt url: URL) { + self.delegate?.mediaPickerCoordinatorBridgePresenter(self, didSelectVideoAt: url) + } + + func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectAssets assets: [PHAsset]) { + self.delegate?.mediaPickerCoordinatorBridgePresenter(self, didSelectAssets: assets) + } + + func mediaPickerCoordinatorDidCancel(_ coordinator: MediaPickerCoordinatorType) { + self.delegate?.mediaPickerCoordinatorBridgePresenterDidCancel(self) + } +} diff --git a/Riot/Modules/MediaPicker/MediaPickerCoordinatorType.swift b/Riot/Modules/MediaPicker/MediaPickerCoordinatorType.swift new file mode 100644 index 000000000..53f154536 --- /dev/null +++ b/Riot/Modules/MediaPicker/MediaPickerCoordinatorType.swift @@ -0,0 +1,31 @@ +// File created from FlowTemplate +// $ createRootCoordinator.sh Test MediaPicker +/* + 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 MediaPickerCoordinatorDelegate: class { + func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) + func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectVideoAt url: URL) + func mediaPickerCoordinator(_ coordinator: MediaPickerCoordinatorType, didSelectAssets assets: [PHAsset]) + func mediaPickerCoordinatorDidCancel(_ coordinator: MediaPickerCoordinatorType) +} + +/// `MediaPickerCoordinatorType` is a protocol describing a Coordinator that handle keybackup setup navigation flow. +protocol MediaPickerCoordinatorType: Coordinator, Presentable { + var delegate: MediaPickerCoordinatorDelegate? { get } +} diff --git a/Riot/SupportingFiles/Riot-Bridging-Header.h b/Riot/SupportingFiles/Riot-Bridging-Header.h index 68348718c..92e6e483c 100644 --- a/Riot/SupportingFiles/Riot-Bridging-Header.h +++ b/Riot/SupportingFiles/Riot-Bridging-Header.h @@ -13,3 +13,4 @@ #import "AvatarGenerator.h" #import "EncryptionInfoView.h" #import "EventFormatter.h" +#import "MediaPickerViewController.h" From b31087ec12966d3d68ec5784b13701b41a95b817 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 2 Aug 2019 17:16:06 +0200 Subject: [PATCH 58/75] RoomInputToolbarView: Add separate actions for camera and media library. --- .../Views/InputToolbar/RoomInputToolbarView.h | 14 +++ .../Views/InputToolbar/RoomInputToolbarView.m | 89 ++++--------------- 2 files changed, 32 insertions(+), 71 deletions(-) diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h index fdff609e1..9cc1110f3 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.h @@ -45,6 +45,20 @@ typedef enum : NSUInteger */ - (void)roomInputToolbarViewDidTapFileUpload:(MXKRoomInputToolbarView*)toolbarView; +/** + Tells the delegate that the user wants to take photo or video with camera. + + @param toolbarView the room input toolbar view + */ +- (void)roomInputToolbarViewDidTapCamera:(MXKRoomInputToolbarView*)toolbarView; + +/** + Tells the delegate that the user wants to show media library. + + @param toolbarView the room input toolbar view + */ +- (void)roomInputToolbarViewDidTapMediaLibrary:(MXKRoomInputToolbarView*)toolbarView; + @end /** diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index f09f0ba8f..1542ed6a0 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -24,12 +24,6 @@ #import "UINavigationController+Riot.h" -#import - -#import - -#import - #import "WidgetManager.h" #import "IntegrationManagerViewController.h" @@ -39,8 +33,6 @@ UIAlertController *actionSheet; } -@property(nonatomic, weak) MediaPickerViewController *mediaPicker; - @end @implementation RoomInputToolbarView @@ -322,6 +314,21 @@ actionSheet = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; __weak typeof(self) weakSelf = self; + + [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"Take photo or video", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + self->actionSheet = nil; + + [self.delegate roomInputToolbarViewDidTapCamera:self]; + } + }]]; + + [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_action_send_photo_or_video", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { @@ -331,7 +338,7 @@ typeof(self) self = weakSelf; self->actionSheet = nil; - [self showMediaPicker]; + [self.delegate roomInputToolbarViewDidTapMediaLibrary:self]; } }]]; @@ -375,8 +382,8 @@ }]]; - [actionSheet popoverPresentationController].sourceView = self.voiceCallButton; - [actionSheet popoverPresentationController].sourceRect = self.voiceCallButton.bounds; + [actionSheet popoverPresentationController].sourceView = self.attachMediaButton; + [actionSheet popoverPresentationController].sourceRect = self.attachMediaButton.bounds; [self.window.rootViewController presentViewController:actionSheet animated:YES completion:nil]; } else @@ -448,34 +455,8 @@ [super onTouchUpInside:button]; } -- (void)showMediaPicker -{ - // MediaPickerViewController is based on the Photos framework. So it is available only for iOS 8 and later. - Class PHAsset_class = NSClassFromString(@"PHAsset"); - if (PHAsset_class) - { - MediaPickerViewController * mediaPicker = [MediaPickerViewController mediaPickerViewController]; - mediaPicker.mediaTypes = @[(NSString *)kUTTypeImage, (NSString *)kUTTypeMovie]; - mediaPicker.delegate = self; - UINavigationController *navigationController = [UINavigationController new]; - [navigationController pushViewController:mediaPicker animated:NO]; - - self.mediaPicker = mediaPicker; - - [self.delegate roomInputToolbarView:self presentViewController:navigationController]; - } - else - { - // We use UIImagePickerController by default for iOS < 8 - self.leftInputToolbarButton = self.attachMediaButton; - [super onTouchUpInside:self.leftInputToolbarButton]; - } -} - - (void)destroy { - [self dismissMediaPicker]; - if (actionSheet) { [actionSheet dismissViewControllerAnimated:NO completion:nil]; @@ -485,40 +466,6 @@ [super destroy]; } -#pragma mark - MediaPickerViewController Delegate - -- (void)mediaPickerController:(MediaPickerViewController *)mediaPickerController didSelectImage:(NSData*)imageData withMimeType:(NSString *)mimetype isPhotoLibraryAsset:(BOOL)isPhotoLibraryAsset -{ - [self dismissMediaPicker]; - - [self sendSelectedImage:imageData withMimeType:mimetype andCompressionMode:MXKRoomInputToolbarCompressionModePrompt isPhotoLibraryAsset:isPhotoLibraryAsset]; -} - -- (void)mediaPickerController:(MediaPickerViewController *)mediaPickerController didSelectVideo:(NSURL*)videoURL -{ - [self dismissMediaPicker]; - - BOOL isPhotoLibraryAsset = ![videoURL.path hasPrefix:NSTemporaryDirectory()]; - [self sendSelectedVideo:videoURL isPhotoLibraryAsset:isPhotoLibraryAsset]; -} - -- (void)mediaPickerController:(MediaPickerViewController *)mediaPickerController didSelectAssets:(NSArray*)assets -{ - [self dismissMediaPicker]; - - [self sendSelectedAssets:assets withCompressionMode:MXKRoomInputToolbarCompressionModePrompt]; -} - -#pragma mark - Media picker handling - -- (void)dismissMediaPicker -{ - if (self.mediaPicker) - { - [self.mediaPicker withdrawViewControllerAnimated:YES completion:nil]; - } -} - #pragma mark - Clipboard - Handle image/data paste from general pasteboard - (void)paste:(id)sender From 119d7239a83569fab3b4a72fd8ac7287f0c90ef6 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 2 Aug 2019 17:19:29 +0200 Subject: [PATCH 59/75] RoomVC: Handle presentation for new native camera and media library actions. --- Riot/Modules/Room/RoomViewController.m | 123 ++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 1 deletion(-) diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m index 50eba1739..46327a0e8 100644 --- a/Riot/Modules/Room/RoomViewController.m +++ b/Riot/Modules/Room/RoomViewController.m @@ -125,7 +125,7 @@ @interface RoomViewController () + ReactionHistoryCoordinatorBridgePresenterDelegate, CameraPresenterDelegate, MediaPickerCoordinatorBridgePresenterDelegate> { // The expanded header ExpandedRoomTitleView *expandedHeader; @@ -226,6 +226,8 @@ @property (nonatomic, strong) MXKDocumentPickerPresenter *documentPickerPresenter; @property (nonatomic, strong) EmojiPickerCoordinatorBridgePresenter *emojiPickerCoordinatorBridgePresenter; @property (nonatomic, strong) ReactionHistoryCoordinatorBridgePresenter *reactionHistoryCoordinatorBridgePresenter; +@property (nonatomic, strong) CameraPresenter *cameraPresenter; +@property (nonatomic, strong) MediaPickerCoordinatorBridgePresenter *mediaPickerPresenter; @end @@ -1548,6 +1550,39 @@ self.reactionHistoryCoordinatorBridgePresenter = presenter; } +- (void)showCameraControllerAnimated:(BOOL)animated +{ + CameraPresenter *cameraPresenter = [CameraPresenter new]; + cameraPresenter.delegate = self; + [cameraPresenter presentCameraFrom:self with:@[MXKUTI.image, MXKUTI.movie] animated:YES]; + + self.cameraPresenter = cameraPresenter; +} + + +- (void)showMediaPickerAnimated:(BOOL)animated +{ + MediaPickerCoordinatorBridgePresenter *mediaPickerPresenter = [[MediaPickerCoordinatorBridgePresenter alloc] initWithSession:self.mainSession mediaUTIs:@[MXKUTI.image, MXKUTI.movie] allowsMultipleSelection:YES]; + mediaPickerPresenter.delegate = self; + + UIView *sourceView; + + RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView]; + + if (roomInputToolbarView) + { + sourceView = roomInputToolbarView.attachMediaButton; + } + else + { + sourceView = self.inputToolbarView; + } + + [mediaPickerPresenter presentFrom:self sourceView:sourceView sourceRect:sourceView.bounds animated:YES]; + + self.mediaPickerPresenter = mediaPickerPresenter; +} + #pragma mark - Hide/Show expanded header - (void)showExpandedHeader:(BOOL)isVisible @@ -3398,6 +3433,16 @@ self.documentPickerPresenter = documentPickerPresenter; } +- (void)roomInputToolbarViewDidTapCamera:(MXKRoomInputToolbarView*)toolbarView +{ + [self showCameraControllerAnimated:YES]; +} + +- (void)roomInputToolbarViewDidTapMediaLibrary:(MXKRoomInputToolbarView*)toolbarView +{ + [self showMediaPickerAnimated:YES]; +} + #pragma mark - RoomParticipantsViewControllerDelegate - (void)roomParticipantsViewController:(RoomParticipantsViewController *)roomParticipantsViewController mention:(MXRoomMember*)member @@ -5504,5 +5549,81 @@ }]; } +#pragma mark - CameraPresenterDelegate + +- (void)cameraPresenterDidCancel:(CameraPresenter *)cameraPresenter +{ + [cameraPresenter dismissWithAnimated:YES completion:nil]; + self.cameraPresenter = nil; +} + +- (void)cameraPresenter:(CameraPresenter *)cameraPresenter didSelectImageData:(NSData *)imageData withUTI:(MXKUTI *)uti +{ + [cameraPresenter dismissWithAnimated:YES completion:nil]; + self.cameraPresenter = nil; + + RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView]; + if (roomInputToolbarView) + { + [roomInputToolbarView sendSelectedImage:imageData withMimeType:uti.mimeType andCompressionMode:MXKRoomInputToolbarCompressionModePrompt isPhotoLibraryAsset:NO]; + } +} + +- (void)cameraPresenter:(CameraPresenter *)cameraPresenter didSelectVideoAt:(NSURL *)url +{ + [cameraPresenter dismissWithAnimated:YES completion:nil]; + self.cameraPresenter = nil; + + RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView]; + if (roomInputToolbarView) + { + [roomInputToolbarView sendSelectedVideo:url isPhotoLibraryAsset:NO]; + } +} + +#pragma mark - MediaPickerCoordinatorBridgePresenterDelegate + +- (void)mediaPickerCoordinatorBridgePresenterDidCancel:(MediaPickerCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + [coordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + self.mediaPickerPresenter = nil; +} + +- (void)mediaPickerCoordinatorBridgePresenter:(MediaPickerCoordinatorBridgePresenter *)coordinatorBridgePresenter didSelectImageData:(NSData *)imageData withUTI:(MXKUTI *)uti +{ + [coordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + self.mediaPickerPresenter = nil; + + RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView]; + if (roomInputToolbarView) + { + [roomInputToolbarView sendSelectedImage:imageData withMimeType:uti.mimeType andCompressionMode:MXKRoomInputToolbarCompressionModePrompt isPhotoLibraryAsset:YES]; + } +} + +- (void)mediaPickerCoordinatorBridgePresenter:(MediaPickerCoordinatorBridgePresenter *)coordinatorBridgePresenter didSelectVideoAt:(NSURL *)url +{ + [coordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + self.mediaPickerPresenter = nil; + + RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView]; + if (roomInputToolbarView) + { + [roomInputToolbarView sendSelectedVideo:url isPhotoLibraryAsset:YES]; + } +} + +- (void)mediaPickerCoordinatorBridgePresenter:(MediaPickerCoordinatorBridgePresenter *)coordinatorBridgePresenter didSelectAssets:(NSArray *)assets +{ + [coordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + self.mediaPickerPresenter = nil; + + RoomInputToolbarView *roomInputToolbarView = [self inputToolbarViewAsRoomInputToolbarView]; + if (roomInputToolbarView) + { + [roomInputToolbarView sendSelectedAssets:assets withCompressionMode:MXKRoomInputToolbarCompressionModePrompt]; + } +} + @end From 7c81eb685ecbf148f46bc3a5ed9db4abafd3130e Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 2 Aug 2019 17:21:17 +0200 Subject: [PATCH 60/75] Create SingleImagePickerPresenter that enables to present an image picker with single selection. --- .../SingleImagePickerPresenter.swift | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 Riot/Modules/MediaPicker/SingleImagePickerPresenter.swift diff --git a/Riot/Modules/MediaPicker/SingleImagePickerPresenter.swift b/Riot/Modules/MediaPicker/SingleImagePickerPresenter.swift new file mode 100644 index 000000000..9876aa901 --- /dev/null +++ b/Riot/Modules/MediaPicker/SingleImagePickerPresenter.swift @@ -0,0 +1,150 @@ +/* + 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 +import UIKit +import AVFoundation + +@objc protocol SingleImagePickerPresenterDelegate: class { + func singleImagePickerPresenter(_ presenter: SingleImagePickerPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) + func singleImagePickerPresenterDidCancel(_ presenter: SingleImagePickerPresenter) +} + +/// SingleImagePickerPresenter enables to present an image picker with single selection +@objcMembers +final class SingleImagePickerPresenter: NSObject { + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + + private weak var presentingViewController: UIViewController? + private var cameraPresenter: CameraPresenter? + private var mediaPickerPresenter: MediaPickerCoordinatorBridgePresenter? + + // MARK: Public + + weak var delegate: SingleImagePickerPresenterDelegate? + + // MARK: - Setup + + init(session: MXSession) { + self.session = session + } + + // MARK: - Public + + func present(from presentingViewController: UIViewController, + sourceView: UIView?, + sourceRect: CGRect, + animated: Bool) { + let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) + + + let cameraAction = UIAlertAction(title: VectorL10n.imagePickerActionCamera, style: .default, handler: { _ in + self.presentCamera(animated: animated) + }) + + let photoLibraryAction = UIAlertAction(title: VectorL10n.imagePickerActionLibrary, style: .default, handler: { _ in + self.presentPhotoLibray(sourceView: sourceView, sourceRect: sourceRect, animated: animated) + }) + + let cancelAction = UIAlertAction(title: VectorL10n.cancel, style: .cancel, handler: { _ in + }) + + alert.addAction(cameraAction) + alert.addAction(photoLibraryAction) + alert.addAction(cancelAction) + + if let popoverPresentationController = alert.popoverPresentationController { + popoverPresentationController.sourceView = sourceView + popoverPresentationController.sourceRect = sourceRect + } + + presentingViewController.present(alert, animated: animated, completion: nil) + self.presentingViewController = presentingViewController + } + + func dismiss(animated: Bool, completion: (() -> Void)?) { + if let cameraPresenter = self.cameraPresenter { + cameraPresenter.dismiss(animated: animated, completion: completion) + } else if let mediaPickerPresenter = self.mediaPickerPresenter { + mediaPickerPresenter.dismiss(animated: animated, completion: completion) + } + } + + // MARK: - Private + + private func presentCamera(animated: Bool) { + guard let presentingViewController = self.presentingViewController else { + return + } + + let cameraPresenter = CameraPresenter() + cameraPresenter.delegate = self + cameraPresenter.presentCamera(from: presentingViewController, with: [.image], animated: animated) + self.cameraPresenter = cameraPresenter + } + + private func presentPhotoLibray(sourceView: UIView?, sourceRect: CGRect, animated: Bool) { + guard let presentingViewController = self.presentingViewController else { + return + } + + let mediaPickerPresenter = MediaPickerCoordinatorBridgePresenter(session: self.session, mediaUTIs: [.image], allowsMultipleSelection: false) + mediaPickerPresenter.delegate = self + + mediaPickerPresenter.present(from: presentingViewController, sourceView: sourceView, sourceRect: sourceRect, animated: animated) + self.mediaPickerPresenter = mediaPickerPresenter + } + +} + +// MARK: - CameraPresenterDelegate +extension SingleImagePickerPresenter: CameraPresenterDelegate { + + func cameraPresenter(_ cameraPresenter: CameraPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) { + self.delegate?.singleImagePickerPresenter(self, didSelectImageData: imageData, withUTI: uti) + } + + func cameraPresenterDidCancel(_ cameraPresenter: CameraPresenter) { + self.delegate?.singleImagePickerPresenterDidCancel(self) + } + + func cameraPresenter(_ cameraPresenter: CameraPresenter, didSelectVideoAt url: URL) { + self.delegate?.singleImagePickerPresenterDidCancel(self) + } +} +// MARK: - MediaPickerCoordinatorBridgePresenterDelegate +extension SingleImagePickerPresenter: MediaPickerCoordinatorBridgePresenterDelegate { + func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) { + self.delegate?.singleImagePickerPresenter(self, didSelectImageData: imageData, withUTI: uti) + } + + func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectVideoAt url: URL) { + self.delegate?.singleImagePickerPresenterDidCancel(self) + } + + func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectAssets assets: [PHAsset]) { + self.delegate?.singleImagePickerPresenterDidCancel(self) + } + + func mediaPickerCoordinatorBridgePresenterDidCancel(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter) { + self.delegate?.singleImagePickerPresenterDidCancel(self) + } +} From ccc930ccc765fff096a651e3b7ce7d85525d850d Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 2 Aug 2019 17:26:34 +0200 Subject: [PATCH 61/75] RoomSettingsViewController: Use SingleImagePickerPresenter for room avatar picture. --- .../Settings/RoomSettingsViewController.h | 2 +- .../Settings/RoomSettingsViewController.m | 83 +++++++++---------- 2 files changed, 39 insertions(+), 46 deletions(-) diff --git a/Riot/Modules/Room/Settings/RoomSettingsViewController.h b/Riot/Modules/Room/Settings/RoomSettingsViewController.h index 3318d8f97..950f9c6d7 100644 --- a/Riot/Modules/Room/Settings/RoomSettingsViewController.h +++ b/Riot/Modules/Room/Settings/RoomSettingsViewController.h @@ -45,7 +45,7 @@ typedef enum : NSUInteger { } RoomSettingsViewControllerField; -@interface RoomSettingsViewController : MXKRoomSettingsViewController +@interface RoomSettingsViewController : MXKRoomSettingsViewController /** Select a settings field in order to edit it ('RoomSettingsViewControllerFieldNone' by default). diff --git a/Riot/Modules/Room/Settings/RoomSettingsViewController.m b/Riot/Modules/Room/Settings/RoomSettingsViewController.m index 516cd9cde..77815e52a 100644 --- a/Riot/Modules/Room/Settings/RoomSettingsViewController.m +++ b/Riot/Modules/Room/Settings/RoomSettingsViewController.m @@ -109,7 +109,7 @@ NSString *const kRoomSettingsAdvancedCellViewIdentifier = @"kRoomSettingsAdvance NSString *const kRoomSettingsAdvancedEnableE2eCellViewIdentifier = @"kRoomSettingsAdvancedEnableE2eCellViewIdentifier"; NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSettingsAdvancedE2eEnabledCellViewIdentifier"; -@interface RoomSettingsViewController () +@interface RoomSettingsViewController () { // The updated user data NSMutableDictionary *updatedItemsDict; @@ -159,9 +159,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti // listen to more events than the mother class id extraEventsListener; - // picker - MediaPickerViewController* mediaPicker; - // Observe kAppDelegateDidTapStatusBarNotification to handle tap on clock status bar. id appDelegateDidTapStatusBarNotificationObserver; @@ -171,6 +168,9 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti // Observe kThemeServiceDidChangeThemeNotification to handle user interface theme change. id kThemeServiceDidChangeThemeNotificationObserver; } + +@property (nonatomic, strong) SingleImagePickerPresenter *imagePickerPresenter; + @end @implementation RoomSettingsViewController @@ -3279,41 +3279,6 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti [self presentViewController:currentAlert animated:YES completion:nil]; } -#pragma mark - MediaPickerViewController Delegate - -- (void)dismissMediaPicker -{ - if (mediaPicker) - { - [mediaPicker withdrawViewControllerAnimated:YES completion:nil]; - mediaPicker = nil; - } -} - -- (void)mediaPickerController:(MediaPickerViewController *)mediaPickerController didSelectImage:(NSData*)imageData withMimeType:(NSString *)mimetype isPhotoLibraryAsset:(BOOL)isPhotoLibraryAsset -{ - [self dismissMediaPicker]; - - if (imageData) - { - UIImage *image = [UIImage imageWithData:imageData]; - if (image) - { - [self getNavigationItem].rightBarButtonItem.enabled = YES; - - updatedItemsDict[kRoomSettingsAvatarKey] = image; - - [self refreshRoomSettings]; - } - } -} - -- (void)mediaPickerController:(MediaPickerViewController *)mediaPickerController didSelectVideo:(NSURL*)videoURL -{ - // this method should not be called - [self dismissMediaPicker]; -} - #pragma mark - MXKRoomMemberDetailsViewControllerDelegate - (void)roomMemberDetailsViewController:(MXKRoomMemberDetailsViewController *)roomMemberDetailsViewController startChatWithMemberId:(NSString *)matrixId completion:(void (^)(void))completion @@ -3380,13 +3345,17 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti - (void)onRoomAvatarTap:(UITapGestureRecognizer *)recognizer { - mediaPicker = [MediaPickerViewController mediaPickerViewController]; - mediaPicker.mediaTypes = @[(NSString *)kUTTypeImage]; - mediaPicker.delegate = self; - UINavigationController *navigationController = [UINavigationController new]; - [navigationController pushViewController:mediaPicker animated:NO]; + SingleImagePickerPresenter *singleImagePickerPresenter = [[SingleImagePickerPresenter alloc] initWithSession:self.mainSession]; + singleImagePickerPresenter.delegate = self; - [self presentViewController:navigationController animated:YES completion:nil]; + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:ROOM_SETTINGS_MAIN_SECTION_INDEX inSection:ROOM_SETTINGS_MAIN_SECTION_ROW_PHOTO]; + UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; + + UIView *sourceView = cell; + + [singleImagePickerPresenter presentFrom:self sourceView:sourceView sourceRect:sourceView.bounds animated:YES]; + + self.imagePickerPresenter = singleImagePickerPresenter; } - (void)toggleRoomNotification:(UISwitch*)theSwitch @@ -3840,6 +3809,30 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti } } +#pragma mark - SingleImagePickerPresenterDelegate + +- (void)singleImagePickerPresenterDidCancel:(SingleImagePickerPresenter *)presenter +{ + [presenter dismissWithAnimated:YES completion:nil]; + self.imagePickerPresenter = nil; +} + +- (void)singleImagePickerPresenter:(SingleImagePickerPresenter *)presenter didSelectImageData:(NSData *)imageData withUTI:(MXKUTI *)uti +{ + [presenter dismissWithAnimated:YES completion:nil]; + self.imagePickerPresenter = nil; + + UIImage *image = [UIImage imageWithData:imageData]; + if (image) + { + [self getNavigationItem].rightBarButtonItem.enabled = YES; + + updatedItemsDict[kRoomSettingsAvatarKey] = image; + + [self refreshRoomSettings]; + } +} + @end From 35d3ff1e6ac7a94d5437fa45e188eba012179aae Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 2 Aug 2019 17:27:49 +0200 Subject: [PATCH 62/75] SettingsViewController: Use SingleImagePickerPresenter for profile avatar picture. --- .../Modules/Settings/SettingsViewController.h | 2 +- .../Modules/Settings/SettingsViewController.m | 71 +++++++++---------- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/Riot/Modules/Settings/SettingsViewController.h b/Riot/Modules/Settings/SettingsViewController.h index 2bca07caf..4bce2072b 100644 --- a/Riot/Modules/Settings/SettingsViewController.h +++ b/Riot/Modules/Settings/SettingsViewController.h @@ -20,7 +20,7 @@ #import "MediaPickerViewController.h" -@interface SettingsViewController : MXKTableViewController +@interface SettingsViewController : MXKTableViewController @end diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 091cb5d20..e25f74020 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -20,10 +20,7 @@ #import -#import -#import #import -#import #import "AppDelegate.h" #import "AvatarGenerator.h" @@ -141,7 +138,8 @@ SettingsKeyBackupTableViewSectionDelegate, MXKEncryptionInfoViewDelegate, KeyBackupSetupCoordinatorBridgePresenterDelegate, KeyBackupRecoverCoordinatorBridgePresenterDelegate, -SignOutAlertPresenterDelegate> +SignOutAlertPresenterDelegate, +SingleImagePickerPresenterDelegate> { // Current alert (if any). UIAlertController *currentAlert; @@ -155,9 +153,6 @@ SignOutAlertPresenterDelegate> id notificationCenterDidUpdateObserver; id notificationCenterDidFailObserver; - // picker - MediaPickerViewController* mediaPicker; - // profile updates // avatar UIImage* newAvatarImage; @@ -252,6 +247,7 @@ SignOutAlertPresenterDelegate> @property (nonatomic, weak) DeactivateAccountViewController *deactivateAccountViewController; @property (nonatomic, strong) SignOutAlertPresenter *signOutAlertPresenter; @property (nonatomic, weak) UIButton *signOutButton; +@property (nonatomic, strong) SingleImagePickerPresenter *imagePickerPresenter; @end @@ -3711,13 +3707,18 @@ SignOutAlertPresenterDelegate> - (void)onProfileAvatarTap:(UITapGestureRecognizer *)recognizer { - mediaPicker = [MediaPickerViewController mediaPickerViewController]; - mediaPicker.mediaTypes = @[(NSString *)kUTTypeImage]; - mediaPicker.delegate = self; - UINavigationController *navigationController = [UINavigationController new]; - [navigationController pushViewController:mediaPicker animated:NO]; + SingleImagePickerPresenter *singleImagePickerPresenter = [[SingleImagePickerPresenter alloc] initWithSession:self.mainSession]; + singleImagePickerPresenter.delegate = self; - [self presentViewController:navigationController animated:YES completion:nil]; + + NSIndexPath *indexPath = [NSIndexPath indexPathForRow:userSettingsProfilePictureIndex inSection:SETTINGS_SECTION_USER_SETTINGS_INDEX]; + UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; + + UIView *sourceView = cell; + + [singleImagePickerPresenter presentFrom:self sourceView:sourceView sourceRect:sourceView.bounds animated:YES]; + + self.imagePickerPresenter = singleImagePickerPresenter; } - (void)exportEncryptionKeys:(UITapGestureRecognizer *)recognizer @@ -3890,32 +3891,6 @@ SignOutAlertPresenterDelegate> self.deactivateAccountViewController = deactivateAccountViewController; } -#pragma mark - MediaPickerViewController Delegate - -- (void)dismissMediaPicker -{ - if (mediaPicker) - { - [mediaPicker withdrawViewControllerAnimated:YES completion:nil]; - mediaPicker = nil; - } -} - -- (void)mediaPickerController:(MediaPickerViewController *)mediaPickerController didSelectImage:(NSData*)imageData withMimeType:(NSString *)mimetype isPhotoLibraryAsset:(BOOL)isPhotoLibraryAsset -{ - [self dismissMediaPicker]; - - newAvatarImage = [UIImage imageWithData:imageData]; - - [self.tableView reloadData]; -} - -- (void)mediaPickerController:(MediaPickerViewController *)mediaPickerController didSelectVideo:(NSURL*)videoURL -{ - // this method should not be called - [self dismissMediaPicker]; -} - #pragma mark - TextField listener - (IBAction)textFieldDidChange:(id)sender @@ -4427,4 +4402,22 @@ SignOutAlertPresenterDelegate> }]; } +#pragma mark - SingleImagePickerPresenterDelegate + +- (void)singleImagePickerPresenterDidCancel:(SingleImagePickerPresenter *)presenter +{ + [presenter dismissWithAnimated:YES completion:nil]; + self.imagePickerPresenter = nil; +} + +- (void)singleImagePickerPresenter:(SingleImagePickerPresenter *)presenter didSelectImageData:(NSData *)imageData withUTI:(MXKUTI *)uti +{ + [presenter dismissWithAnimated:YES completion:nil]; + self.imagePickerPresenter = nil; + + newAvatarImage = [UIImage imageWithData:imageData]; + + [self.tableView reloadData]; +} + @end From 973d75d059c155183b54b8b52031845b456475e0 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 2 Aug 2019 17:28:20 +0200 Subject: [PATCH 63/75] Update pbxproj --- Riot.xcodeproj/project.pbxproj | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 88c2eb33f..814e0b268 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -475,6 +475,11 @@ B1B9DEF122EB396B0065E677 /* ReactionHistoryViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEF022EB396B0065E677 /* ReactionHistoryViewData.swift */; }; B1B9DEF422EB426D0065E677 /* ReactionHistoryViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B9DEF222EB426D0065E677 /* ReactionHistoryViewCell.swift */; }; B1B9DEF522EB426D0065E677 /* ReactionHistoryViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = B1B9DEF322EB426D0065E677 /* ReactionHistoryViewCell.xib */; }; + B1C335CD22F1C1320021BA8D /* CameraPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335CC22F1C1320021BA8D /* CameraPresenter.swift */; }; + B1C3360122F1ED600021BA8D /* MediaPickerCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335FE22F1ED5F0021BA8D /* MediaPickerCoordinatorType.swift */; }; + B1C3360222F1ED600021BA8D /* MediaPickerCoordinatorBridgePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C335FF22F1ED5F0021BA8D /* MediaPickerCoordinatorBridgePresenter.swift */; }; + B1C3360322F1ED600021BA8D /* MediaPickerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C3360022F1ED600021BA8D /* MediaPickerCoordinator.swift */; }; + B1C3361C22F32B4A0021BA8D /* SingleImagePickerPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C3361B22F32B4A0021BA8D /* SingleImagePickerPresenter.swift */; }; B1C562CA2289C2690037F12A /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */; }; B1C562CC228AB3510037F12A /* UIStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562CB228AB3510037F12A /* UIStackView.swift */; }; B1C562D9228C0B760037F12A /* RoomContextualMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */; }; @@ -1274,6 +1279,11 @@ B1B9DEF022EB396B0065E677 /* ReactionHistoryViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionHistoryViewData.swift; sourceTree = ""; }; B1B9DEF222EB426D0065E677 /* ReactionHistoryViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionHistoryViewCell.swift; sourceTree = ""; }; B1B9DEF322EB426D0065E677 /* ReactionHistoryViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReactionHistoryViewCell.xib; sourceTree = ""; }; + B1C335CC22F1C1320021BA8D /* CameraPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraPresenter.swift; sourceTree = ""; }; + B1C335FE22F1ED5F0021BA8D /* MediaPickerCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPickerCoordinatorType.swift; sourceTree = ""; }; + B1C335FF22F1ED5F0021BA8D /* MediaPickerCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPickerCoordinatorBridgePresenter.swift; sourceTree = ""; }; + B1C3360022F1ED600021BA8D /* MediaPickerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaPickerCoordinator.swift; sourceTree = ""; }; + B1C3361B22F32B4A0021BA8D /* SingleImagePickerPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleImagePickerPresenter.swift; sourceTree = ""; }; B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; }; B1C562CB228AB3510037F12A /* UIStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = ""; }; B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomContextualMenuItem.swift; sourceTree = ""; }; @@ -2187,6 +2197,7 @@ B1B556DC20EE6C4C00210D55 /* Call */, B1B5568720EE6C4C00210D55 /* Contacts */, B1B556ED20EE6C4C00210D55 /* MediaPicker */, + B1C3361A22F328AE0021BA8D /* Camera */, B1B556E420EE6C4C00210D55 /* UserDevices */, B1B5596B20EFA85C00210D55 /* EncryptionInfo */, B1B556FD20EE6C4C00210D55 /* RoomKeyRequest */, @@ -2570,11 +2581,15 @@ B1B556ED20EE6C4C00210D55 /* MediaPicker */ = { isa = PBXGroup; children = ( + B1C335FF22F1ED5F0021BA8D /* MediaPickerCoordinatorBridgePresenter.swift */, + B1C335FE22F1ED5F0021BA8D /* MediaPickerCoordinatorType.swift */, + B1C3360022F1ED600021BA8D /* MediaPickerCoordinator.swift */, B1B556F320EE6C4C00210D55 /* MediaPickerViewController.h */, B1B556EF20EE6C4C00210D55 /* MediaPickerViewController.m */, B1B556F220EE6C4C00210D55 /* MediaPickerViewController.xib */, - B1B557CD20EF5E3500210D55 /* Views */, B1B5577720EE724200210D55 /* Library */, + B1B557CD20EF5E3500210D55 /* Views */, + B1C3361B22F32B4A0021BA8D /* SingleImagePickerPresenter.swift */, ); path = MediaPicker; sourceTree = ""; @@ -3306,6 +3321,14 @@ path = ReactionHistory; sourceTree = ""; }; + B1C3361A22F328AE0021BA8D /* Camera */ = { + isa = PBXGroup; + children = ( + B1C335CC22F1C1320021BA8D /* CameraPresenter.swift */, + ); + path = Camera; + sourceTree = ""; + }; B1C562D7228C0B4C0037F12A /* ContextualMenu */ = { isa = PBXGroup; children = ( @@ -4138,6 +4161,7 @@ B1DB4F0E22316FFF0065DBFA /* UserNameColorGenerator.swift in Sources */, B1057789221304EC00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.swift in Sources */, B1DCC61922E5E17100625807 /* EmojiPickerCoordinatorType.swift in Sources */, + B1C3360122F1ED600021BA8D /* MediaPickerCoordinatorType.swift in Sources */, 3232ABB72257BE6400AD6A5C /* DeviceVerificationVerifyViewModelType.swift in Sources */, 32F6B96D2270623100BBA352 /* DeviceVerificationDataLoadingViewModel.swift in Sources */, B16932B120F3AC9200746532 /* RoomSearchDataSource.m in Sources */, @@ -4176,6 +4200,7 @@ B1B558C420EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, 3232ABC022594C0900AD6A5C /* VerifyEmojiCollectionViewCell.swift in Sources */, B1963B2E228F1C4900CBA17F /* BubbleReactionViewData.swift in Sources */, + B1C3361C22F32B4A0021BA8D /* SingleImagePickerPresenter.swift in Sources */, B1B5572F20EE6C4D00210D55 /* ReadReceiptsViewController.m in Sources */, B1B558CB20EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m in Sources */, B169330B20F3CA3A00746532 /* Contact.m in Sources */, @@ -4216,6 +4241,7 @@ B1B9DEEB22EB34EF0065E677 /* ReactionHistoryViewModel.swift in Sources */, B1963B2F228F1C4900CBA17F /* BubbleReactionViewCell.swift in Sources */, B1B558E920EF768F00210D55 /* RoomSelectedStickerBubbleCell.m in Sources */, + B1C3360222F1ED600021BA8D /* MediaPickerCoordinatorBridgePresenter.swift in Sources */, B1B558DF20EF768F00210D55 /* RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.m in Sources */, F083BE041E7009ED00A9B29C /* Tools.m in Sources */, 3275FD8C21A5A2C500B9C13D /* TermsView.swift in Sources */, @@ -4387,6 +4413,7 @@ B1B5593820EF7BAC00210D55 /* TableViewCellWithLabelAndLargeTextView.m in Sources */, B1DCC62222E60BE000625807 /* EmojiPickerItemViewData.swift in Sources */, 3232AB502256558300AD6A5C /* TemplateScreenViewState.swift in Sources */, + B1C335CD22F1C1320021BA8D /* CameraPresenter.swift in Sources */, B1B558C820EF768F00210D55 /* RoomIncomingEncryptedAttachmentBubbleCell.m in Sources */, B1B557C620EF5CD400210D55 /* DirectoryServerDetailTableViewCell.m in Sources */, B1B5590920EF768F00210D55 /* RoomEmptyBubbleCell.m in Sources */, @@ -4413,6 +4440,7 @@ B14F143222144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyCoordinator.swift in Sources */, B110872621F098F0003554A5 /* ActivityIndicatorView.swift in Sources */, B19EFA3921F8BB2C00FC070E /* KeyBackupRecoverCoordinatorType.swift in Sources */, + B1C3360322F1ED600021BA8D /* MediaPickerCoordinator.swift in Sources */, B1E5368D21FB7245001F3AFF /* KeyBackupRecoverFromPassphraseViewController.swift in Sources */, B1963B3822933BC800CBA17F /* AutosizedCollectionView.swift in Sources */, B169330320F3C98900746532 /* RoomBubbleCellData.m in Sources */, From abd16aefb854885b930ce109c7215ac069294889 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 2 Aug 2019 17:44:56 +0200 Subject: [PATCH 64/75] Update changes --- CHANGES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.rst b/CHANGES.rst index bc692af8d..75e7fc550 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,7 @@ Improvements: * Reactions: Emoji picker (#2370). * Widgets: Whitelist https://scalar-staging.vector.im/api (#2612). * Reactions: Show who reacted (#2591). + * Media picking: Use native camera and use separate actions for camera and media picker (#638). Bug fix: * Crash when leaving settings due to backup section refresh animation. From 30734550f888afb6e9e54a39e30df99829f7d954 Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Thu, 1 Aug 2019 15:02:53 +0000 Subject: [PATCH 65/75] Translated using Weblate (Bulgarian) Currently translated at 100.0% (738 of 738 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/bg/ --- Riot/Assets/bg.lproj/Vector.strings | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Riot/Assets/bg.lproj/Vector.strings b/Riot/Assets/bg.lproj/Vector.strings index aa681d10b..73d3dbddc 100644 --- a/Riot/Assets/bg.lproj/Vector.strings +++ b/Riot/Assets/bg.lproj/Vector.strings @@ -788,3 +788,27 @@ // MARK: File upload "file_upload_error_title" = "Качване на файл"; "file_upload_error_unsupported_file_type_message" = "Типът файл не се поддържа."; +"auth_softlogout_signed_out" = "Излезли сте от профила"; +"auth_softlogout_sign_in" = "Влез"; +"auth_softlogout_reason" = "Администратора на вашия сървър (%1$@) ви е отписал от профила %2$@ (%3$@)."; +"auth_softlogout_recover_encryption_keys" = "Влезте за да възстановите ключове за шифровани съхранени само на това устройство. Ще ви трябват за да можете да четете всички защитени съобщения на кое да е устройство."; +"auth_softlogout_clear_data" = "Изчисти личните данни"; +"auth_softlogout_clear_data_message_1" = "Внимание: Личните ви данни (включително ключове за шифроване) все още са съхранени на това устройство."; +"auth_softlogout_clear_data_message_2" = "Изчистете, ако сте приключили с използването на това устройство или искате да влезете с друг профил."; +"auth_softlogout_clear_data_button" = "Изчисти всички данни"; +"auth_softlogout_clear_data_sign_out_title" = "Сигурни ли сте?"; +"auth_softlogout_clear_data_sign_out_msg" = "Сигурни ли сте, че искате да изчистите всички данни съхранени на това устройство? Влезте пак за да достъпите профила и съобщенията си."; +"auth_softlogout_clear_data_sign_out" = "Излез"; +"room_event_action_reaction_history" = "История на реакциите"; +// MARK: Emoji picker +"emoji_picker_title" = "Реакции"; +"emoji_picker_people_category" = "Усмивки и хора"; +"emoji_picker_nature_category" = "Животни и природа"; +"emoji_picker_foods_category" = "Храна и напитки"; +"emoji_picker_activity_category" = "Дейности"; +"emoji_picker_places_category" = "Пътуване и места"; +"emoji_picker_objects_category" = "Обекти"; +"emoji_picker_symbols_category" = "Символи"; +"emoji_picker_flags_category" = "Флагове"; +// MARK: Reaction history +"reaction_history_title" = "Реакции"; From d4977deee00f272be4876a9dcbc150abbb49de7d Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 2 Aug 2019 18:06:55 +0200 Subject: [PATCH 66/75] Media picker: Fix theming issue on recent captures. --- Riot/Modules/MediaPicker/MediaPickerViewController.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Riot/Modules/MediaPicker/MediaPickerViewController.m b/Riot/Modules/MediaPicker/MediaPickerViewController.m index d33381100..6c1edccb2 100644 --- a/Riot/Modules/MediaPicker/MediaPickerViewController.m +++ b/Riot/Modules/MediaPicker/MediaPickerViewController.m @@ -179,6 +179,8 @@ self.userAlbumsTableView.backgroundColor = ThemeService.shared.theme.backgroundColor; self.view.backgroundColor = ThemeService.shared.theme.backgroundColor; + self.recentCapturesCollectionContainerView.backgroundColor = ThemeService.shared.theme.backgroundColor; + self.recentCapturesCollectionView.backgroundColor = ThemeService.shared.theme.backgroundColor; self.userAlbumsTableView.separatorColor = ThemeService.shared.theme.lineBreakColor; } From 20a7b047434e1417e02bd75f6ddf90c09dd2c969 Mon Sep 17 00:00:00 2001 From: SBiOSoftWhare Date: Fri, 2 Aug 2019 18:12:41 +0200 Subject: [PATCH 67/75] Update Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m Co-Authored-By: manuroe --- Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m index 1542ed6a0..577e89544 100644 --- a/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m +++ b/Riot/Modules/Room/Views/InputToolbar/RoomInputToolbarView.m @@ -315,7 +315,7 @@ __weak typeof(self) weakSelf = self; - [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"Take photo or video", @"Vector", nil) + [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"room_action_camera", @"Vector", nil) style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { From b7d6fff86f6bf6bb43b2c0b4dc4f67e2211b4e12 Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Fri, 2 Aug 2019 20:29:09 +0000 Subject: [PATCH 68/75] Translated using Weblate (Bulgarian) Currently translated at 100.0% (744 of 744 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/bg/ --- Riot/Assets/bg.lproj/Vector.strings | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Riot/Assets/bg.lproj/Vector.strings b/Riot/Assets/bg.lproj/Vector.strings index 73d3dbddc..734db3585 100644 --- a/Riot/Assets/bg.lproj/Vector.strings +++ b/Riot/Assets/bg.lproj/Vector.strings @@ -812,3 +812,11 @@ "emoji_picker_flags_category" = "Флагове"; // MARK: Reaction history "reaction_history_title" = "Реакции"; +"room_action_camera" = "Направи снимка или видео"; +// Media picker +"media_picker_title" = "Медийна библиотека"; +// Image picker +"image_picker_action_camera" = "Направи снимка"; +"image_picker_action_library" = "Избери от библиотеката"; +"camera_unavailable" = "Не е достъпна камера на вашето устройство"; +"photo_library_access_not_granted" = "%@ няма достъп до библиотеката със снимки. Моля, променете настройките на поверителността"; From c5b37e0ff013bfc96cdbcb148cea54024c7dc291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20C?= Date: Fri, 2 Aug 2019 18:05:43 +0000 Subject: [PATCH 69/75] Translated using Weblate (French) Currently translated at 100.0% (744 of 744 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 89ab870b7..9d23275a8 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -813,3 +813,11 @@ "room_event_action_reaction_history" = "Historique des réactions"; // MARK: Reaction history "reaction_history_title" = "Réactions"; +"room_action_camera" = "Prendre une photo ou une vidéo"; +// Media picker +"media_picker_title" = "Médiathèque"; +// Image picker +"image_picker_action_camera" = "Prendre une photo"; +"image_picker_action_library" = "Choisir dans la médiathèque"; +"camera_unavailable" = "L’appareil photo n’est pas disponible sur votre appareil"; +"photo_library_access_not_granted" = "%@ n’a pas la permission pour accéder à la médiathèque, veuillez modifier les options de vie privée"; From 2a54c9e8f108f2f28c24a7c43d6c10a61dd39c6f Mon Sep 17 00:00:00 2001 From: Nathan Follens Date: Mon, 5 Aug 2019 19:10:39 +0000 Subject: [PATCH 70/75] Translated using Weblate (Dutch) Currently translated at 100.0% (744 of 744 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/nl/ --- Riot/Assets/nl.lproj/Vector.strings | 42 ++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/nl.lproj/Vector.strings b/Riot/Assets/nl.lproj/Vector.strings index 4038b6d7e..8a6aca06a 100644 --- a/Riot/Assets/nl.lproj/Vector.strings +++ b/Riot/Assets/nl.lproj/Vector.strings @@ -784,8 +784,48 @@ "device_verification_emoji_headphones" = "Koptelefoon"; "device_verification_emoji_folder" = "Map"; "device_verification_emoji_pin" = "Speld"; -"event_formatter_message_edited_mention" = "(Bewerkt)"; +"event_formatter_message_edited_mention" = "(bewerkt)"; // Widget "widget_no_integrations_server_configured" = "Geen integratieserver geconfigureerd"; "widget_integrations_server_failed_to_connect" = "Verbinden met integratieserver mislukt"; "device_verification_emoji_lock" = "Slot"; +"close" = "Sluiten"; +"auth_softlogout_signed_out" = "U bent afgemeld"; +"auth_softlogout_sign_in" = "Aanmelden"; +"auth_softlogout_reason" = "De beheerder van uw thuisserver (%1$@) heeft u van uw account %2$@ afgemeld (%3$@)."; +"auth_softlogout_recover_encryption_keys" = "Meld u aan om de versleutelingssleutels te herstellen die uitsluitend op dit apparaat worden opgeslagen. U heeft ze nodig om uw versleutelde berichten op al uw apparaten te kunnen lezen."; +"auth_softlogout_clear_data" = "Persoonlijke gegevens wissen"; +"auth_softlogout_clear_data_message_1" = "Let op: uw persoonlijke gegevens (inclusief versleutelingssleutels) worden nog altijd op dit apparaat opgeslagen."; +"auth_softlogout_clear_data_message_2" = "Wis ze indien u dit apparaat niet meer gebruikt, of indien u zich wilt aanmelden met een andere account."; +"auth_softlogout_clear_data_button" = "Alle gegevens wissen"; +"auth_softlogout_clear_data_sign_out_title" = "Weet u het zeker?"; +"auth_softlogout_clear_data_sign_out_msg" = "Weet u zeker dat u alle gegevens die op dit moment op dit apparaat worden opgeslagen wilt wissen? Meld u opnieuw aan om toegang te verkrijgen tot uw accountgegevens en berichten."; +"auth_softlogout_clear_data_sign_out" = "Afmelden"; +"room_event_action_reaction_show_all" = "Alles tonen"; +"room_event_action_reaction_show_less" = "Minder tonen"; +"room_event_action_reaction_history" = "Reactiegeschiedenis"; +"room_action_camera" = "Foto of video maken"; +"room_action_send_file" = "Bestand versturen"; +"room_message_edits_history_title" = "Berichtbewerkingen"; +// Media picker +"media_picker_title" = "Mediatheek"; +// Image picker +"image_picker_action_camera" = "Foto maken"; +"image_picker_action_library" = "Kiezen uit mediatheek"; +"camera_unavailable" = "De camera is niet beschikbaar op uw apparaat"; +"photo_library_access_not_granted" = "%@ heeft geen toegang tot de fotobibliotheek, wijzig uw privacy-instellingen"; +// MARK: File upload +"file_upload_error_title" = "Bestand uploaden"; +"file_upload_error_unsupported_file_type_message" = "Bestandstype niet ondersteund."; +// MARK: Emoji picker +"emoji_picker_title" = "Reacties"; +"emoji_picker_people_category" = "Smileys en personen"; +"emoji_picker_nature_category" = "Dieren en natuur"; +"emoji_picker_foods_category" = "Eten en drinken"; +"emoji_picker_activity_category" = "Activiteiten"; +"emoji_picker_places_category" = "Reizen en plaatsen"; +"emoji_picker_objects_category" = "Voorwerpen"; +"emoji_picker_symbols_category" = "Symbolen"; +"emoji_picker_flags_category" = "Vlaggen"; +// MARK: Reaction history +"reaction_history_title" = "Reacties"; From ccaaa6c5e0d121dabfa8783318acac1164e6f6ab Mon Sep 17 00:00:00 2001 From: Nathan Follens Date: Mon, 5 Aug 2019 20:40:57 +0000 Subject: [PATCH 71/75] Translated using Weblate (West Flemish) Currently translated at 1.2% (9 of 744 strings) Translation: Riot iOS/Riot iOS Translate-URL: https://translate.riot.im/projects/riot-ios/riot-ios/vls/ --- Riot/Assets/vls.lproj/Vector.strings | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Riot/Assets/vls.lproj/Vector.strings b/Riot/Assets/vls.lproj/Vector.strings index 8b1378917..0af4809f9 100644 --- a/Riot/Assets/vls.lproj/Vector.strings +++ b/Riot/Assets/vls.lproj/Vector.strings @@ -1 +1,12 @@ - +// String for App Store +"store_short_description" = "Veilig en gedecentraliseerd chattn en belln"; +// Titles +"title_home" = "Thuus"; +"title_favourites" = "Favorietn"; +"title_people" = "Menschn"; +"title_rooms" = "Gesprekkn"; +"title_groups" = "Gemeenschappn"; +"warning" = "Woarschuwienge"; +// Actions +"view" = "Toogn"; +"next" = "Volgende"; From ec768b1202c913fe9906cf34067af90a6f9ae74c Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 7 Aug 2019 18:35:54 +0200 Subject: [PATCH 72/75] BF: Fix crash for earch bar customisation in iOS13 #2626 --- CHANGES.rst | 1 + Riot/Categories/UISearchBar.swift | 8 ++++++-- .../Communities/Members/GroupParticipantsViewController.m | 2 +- Riot/Modules/Communities/Rooms/GroupRoomsViewController.m | 2 +- .../Modules/Room/Members/RoomParticipantsViewController.m | 2 +- Riot/Modules/StartChat/StartChatViewController.m | 2 +- 6 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 75e7fc550..d00f88af7 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,7 @@ Improvements: Bug fix: * Crash when leaving settings due to backup section refresh animation. * Reactions: Do not display reactions on redacted events in timeline. + * Fix crash for earch bar customisation in iOS13 (#2626). Changes in 0.9.1 (2019-07-17) =============================================== diff --git a/Riot/Categories/UISearchBar.swift b/Riot/Categories/UISearchBar.swift index 2158d20ba..aef8e5a9d 100644 --- a/Riot/Categories/UISearchBar.swift +++ b/Riot/Categories/UISearchBar.swift @@ -19,7 +19,11 @@ import UIKit extension UISearchBar { /// Returns internal UITextField - var vc_searchTextField: UITextField? { - return self.value(forKey: "searchField") as? UITextField + @objc var vc_searchTextField: UITextField? { + if #available(iOS 13.0, *) { + return self.searchTextField + } else { + return self.value(forKey: "searchField") as? UITextField + } } } diff --git a/Riot/Modules/Communities/Members/GroupParticipantsViewController.m b/Riot/Modules/Communities/Members/GroupParticipantsViewController.m index 33dcbc2a4..fb9e86b8b 100644 --- a/Riot/Modules/Communities/Members/GroupParticipantsViewController.m +++ b/Riot/Modules/Communities/Members/GroupParticipantsViewController.m @@ -1205,7 +1205,7 @@ // FIXME: this all seems incredibly fragile and tied to gutwrenching the current UISearchBar internals. // text color - UITextField *searchBarTextField = [searchBar valueForKey:@"_searchField"]; + UITextField *searchBarTextField = searchBar.vc_searchTextField; searchBarTextField.textColor = ThemeService.shared.theme.textSecondaryColor; // Magnifying glass icon. diff --git a/Riot/Modules/Communities/Rooms/GroupRoomsViewController.m b/Riot/Modules/Communities/Rooms/GroupRoomsViewController.m index 20cb7c448..5edeeac64 100644 --- a/Riot/Modules/Communities/Rooms/GroupRoomsViewController.m +++ b/Riot/Modules/Communities/Rooms/GroupRoomsViewController.m @@ -606,7 +606,7 @@ // FIXME: this all seems incredibly fragile and tied to gutwrenching the current UISearchBar internals. // text color - UITextField *searchBarTextField = [searchBar valueForKey:@"_searchField"]; + UITextField *searchBarTextField = searchBar.vc_searchTextField; searchBarTextField.textColor = ThemeService.shared.theme.textSecondaryColor; // Magnifying glass icon. diff --git a/Riot/Modules/Room/Members/RoomParticipantsViewController.m b/Riot/Modules/Room/Members/RoomParticipantsViewController.m index 09582da19..dfb55b6fc 100644 --- a/Riot/Modules/Room/Members/RoomParticipantsViewController.m +++ b/Riot/Modules/Room/Members/RoomParticipantsViewController.m @@ -1691,7 +1691,7 @@ // FIXME: this all seems incredibly fragile and tied to gutwrenching the current UISearchBar internals. // text color - UITextField *searchBarTextField = [searchBar valueForKey:@"_searchField"]; + UITextField *searchBarTextField = searchBar.vc_searchTextField; searchBarTextField.textColor = ThemeService.shared.theme.textSecondaryColor; // Magnifying glass icon. diff --git a/Riot/Modules/StartChat/StartChatViewController.m b/Riot/Modules/StartChat/StartChatViewController.m index d72abda68..d2c3cd3ae 100644 --- a/Riot/Modules/StartChat/StartChatViewController.m +++ b/Riot/Modules/StartChat/StartChatViewController.m @@ -628,7 +628,7 @@ // FIXME: this all seems incredibly fragile and tied to gutwrenching the current UISearchBar internals. // text color - UITextField *searchBarTextField = [searchBar valueForKey:@"_searchField"]; + UITextField *searchBarTextField = searchBar.vc_searchTextField; searchBarTextField.textColor = ThemeService.shared.theme.textSecondaryColor; // Magnifying glass icon. From ac1858a73001a536374a7e818504323b061a0111 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 8 Aug 2019 15:05:50 +0200 Subject: [PATCH 73/75] iOS13 code cannot be built from Xcode10 --- Riot/Categories/UISearchBar.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Riot/Categories/UISearchBar.swift b/Riot/Categories/UISearchBar.swift index aef8e5a9d..9c7aa2a33 100644 --- a/Riot/Categories/UISearchBar.swift +++ b/Riot/Categories/UISearchBar.swift @@ -20,10 +20,15 @@ extension UISearchBar { /// Returns internal UITextField @objc var vc_searchTextField: UITextField? { - if #available(iOS 13.0, *) { - return self.searchTextField - } else { + // TODO: To remove once on XCode11/iOS13 + #if swift(>=5.1) + if #available(iOS 13.0, *) { + return self.searchTextField + } else { + return self.value(forKey: "searchField") as? UITextField + } + #else return self.value(forKey: "searchField") as? UITextField - } + #endif } } From 13df91cc55d4cf5829d6a8189ce6ac0110beaefb Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 8 Aug 2019 17:25:23 +0200 Subject: [PATCH 74/75] BF: Build: Fix build based on git tag It was not more possible to build using tags. The reason was the fastlane git_branch method returned nil. Internally this method executes `git symbolic-ref HEAD --short` which returned `fatal: ref HEAD is not a symbolic ref` in our case. We now checks out the tag as a local branch. --- CHANGES.rst | 5 +++-- Tools/Release/buildRelease.sh | 3 ++- fastlane/Fastfile | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index d00f88af7..507513d45 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,4 +1,4 @@ -Changes in 0.9.2 (2019-07-) +Changes in 0.9.2 (2019-08-08) =============================================== Improvements: @@ -12,7 +12,8 @@ Improvements: Bug fix: * Crash when leaving settings due to backup section refresh animation. * Reactions: Do not display reactions on redacted events in timeline. - * Fix crash for earch bar customisation in iOS13 (#2626). + * Fix crash for search bar customisation in iOS13 (#2626). + * Build: Fix build based on git tag. Changes in 0.9.1 (2019-07-17) =============================================== diff --git a/Tools/Release/buildRelease.sh b/Tools/Release/buildRelease.sh index 739fb2c9b..78a4c1264 100755 --- a/Tools/Release/buildRelease.sh +++ b/Tools/Release/buildRelease.sh @@ -37,8 +37,9 @@ bundle update # Checkout the source to build mkdir -p $BUILD_DIR cd $BUILD_DIR -git clone --single-branch --branch $TAG https://github.com/vector-im/riot-ios.git +git clone https://github.com/vector-im/riot-ios.git cd riot-ios +git checkout -b $TAG $TAG # Develop branch special case diff --git a/fastlane/Fastfile b/fastlane/Fastfile index c57503670..350e21eac 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -232,7 +232,7 @@ platform :ios do build_number = options[:build_number] if !git_branch_name.to_s.empty? - preprocessor_definitions["GIT_BRANCH"] = git_branch_name.sub("origin/", "") + preprocessor_definitions["GIT_BRANCH"] = git_branch_name.sub("origin/", "").sub("heads/", "") end if !build_number.to_s.empty? From b5e38335bc67695b26f4384874299098406dbde4 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 8 Aug 2019 18:20:29 +0200 Subject: [PATCH 75/75] version++ --- Podfile | 2 +- Podfile.lock | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Podfile b/Podfile index 5b8cc5b54..c4215a7e7 100644 --- a/Podfile +++ b/Podfile @@ -7,7 +7,7 @@ use_frameworks! # Different flavours of pods to MatrixKit # The current MatrixKit pod version -$matrixKitVersion = '0.10.1' +$matrixKitVersion = '0.10.2' # The develop branch version #$matrixKitVersion = 'develop' diff --git a/Podfile.lock b/Podfile.lock index 664d1aadf..6242d69f5 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -49,41 +49,41 @@ PODS: - MatomoTracker (6.0.1): - MatomoTracker/Core (= 6.0.1) - MatomoTracker/Core (6.0.1) - - MatrixKit (0.10.1): + - MatrixKit (0.10.2): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixKit/Core (= 0.10.1) - - MatrixSDK (= 0.13.0) + - MatrixKit/Core (= 0.10.2) + - MatrixSDK (= 0.13.1) - SwiftUTI (~> 1.0.6) - - MatrixKit/AppExtension (0.10.1): + - MatrixKit/AppExtension (0.10.2): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - DTCoreText/Extension - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.13.0) + - MatrixSDK (= 0.13.1) - SwiftUTI (~> 1.0.6) - - MatrixKit/Core (0.10.1): + - MatrixKit/Core (0.10.2): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.13.0) + - MatrixSDK (= 0.13.1) - SwiftUTI (~> 1.0.6) - - MatrixSDK (0.13.0): - - MatrixSDK/Core (= 0.13.0) - - MatrixSDK/Core (0.13.0): + - MatrixSDK (0.13.1): + - MatrixSDK/Core (= 0.13.1) + - MatrixSDK/Core (0.13.1): - AFNetworking (~> 3.2.0) - GZIP (~> 1.2.2) - libbase58 (~> 0.1.4) - OLMKit (~> 3.1.0) - Realm (~> 3.13.1) - - MatrixSDK/JingleCallStack (0.13.0): + - MatrixSDK/JingleCallStack (0.13.1): - JitsiMeetSDK (~> 2.1.0) - MatrixSDK/Core - - MatrixSDK/SwiftSupport (0.13.0): + - MatrixSDK/SwiftSupport (0.13.1): - MatrixSDK/Core - OLMKit (3.1.0): - OLMKit/olmc (= 3.1.0) @@ -109,8 +109,8 @@ DEPENDENCIES: - DTCoreText - GBDeviceInfo (~> 5.2.0) - MatomoTracker (~> 6.0.1) - - MatrixKit (= 0.10.1) - - MatrixKit/AppExtension (= 0.10.1) + - MatrixKit (= 0.10.2) + - MatrixKit/AppExtension (= 0.10.2) - MatrixSDK/JingleCallStack - MatrixSDK/SwiftSupport - OLMKit @@ -166,8 +166,8 @@ SPEC CHECKSUMS: libbase58: 7c040313537b8c44b6e2d15586af8e21f7354efd libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 MatomoTracker: 3ae4f65a1f5ace8043bda7244888fee28a734de5 - MatrixKit: f8224de32ca8b6e4c54a2654369cedec7744dc6d - MatrixSDK: 6886e7234c650408db5876b44a7f7608c865ce30 + MatrixKit: 208801ba995442a6daa913eb6d639bb918c91c24 + MatrixSDK: 16c8c4b530a7f9fb264baced52b6b0948b1d0a98 OLMKit: 4ee0159d63feeb86d836fdcfefe418e163511639 Realm: 50071da38fe079e0735e47c9f2eae738c68c5996 Reusable: 82be188f29d96dc5eff0db7b2393bcc08d2cdd5b @@ -176,6 +176,6 @@ SPEC CHECKSUMS: SwiftUTI: 917993c124f8eac25e88ced0202fc58d7eb50fa8 zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c -PODFILE CHECKSUM: 6b3ff49b9c446763a5629e71bdde3fe8da7ba93f +PODFILE CHECKSUM: 131195037301d9bfb7465da86b3ea79aac686b74 COCOAPODS: 1.7.2