Merge pull request #2605 from vector-im/riot_2370

Reactions: Emoji picker
This commit is contained in:
SBiOSoftWhare 2019-07-26 14:31:46 +02:00 committed by GitHub
commit 436e5f0fa6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 2303 additions and 29 deletions

View file

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

View file

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

View file

@ -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 /* 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 */; };
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 /* 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 */; };
@ -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 = "<group>"; };
@ -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 = "<group>"; };
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 = "<group>"; };
B104C2932203773B00D9F496 /* KeyBackupBannerPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupBannerPreferences.swift; sourceTree = "<group>"; };
B1057788221304EB00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupSetupSuccessFromPassphraseViewController.swift; sourceTree = "<group>"; };
B105778A221304FA00334B1E /* KeyBackupSetupSuccessFromPassphraseViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = KeyBackupSetupSuccessFromPassphraseViewController.storyboard; sourceTree = "<group>"; };
@ -734,6 +766,9 @@
B14F142B22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromRecoveryKeyViewModel.swift; sourceTree = "<group>"; };
B14F142C22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromRecoveryKeyViewAction.swift; sourceTree = "<group>"; };
B14F142D22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromRecoveryKeyViewController.swift; sourceTree = "<group>"; };
B152C72D22DF55D80041315A /* RiotTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RiotTests-Bridging-Header.h"; sourceTree = "<group>"; };
B152C73022DF561E0041315A /* EmojiServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiServiceTests.swift; sourceTree = "<group>"; };
B152C73422DF599B0041315A /* apple_emojis_data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = apple_emojis_data.json; sourceTree = "<group>"; };
B1664BAD20F4E67500808783 /* FallbackViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = FallbackViewController.xib; sourceTree = "<group>"; };
B1664BAE20F4E67500808783 /* FallbackViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FallbackViewController.m; sourceTree = "<group>"; };
B1664BAF20F4E67500808783 /* FallbackViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FallbackViewController.h; sourceTree = "<group>"; };
@ -1212,6 +1247,9 @@
B1B5599020EFC5E400210D55 /* Analytics.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Analytics.h; sourceTree = "<group>"; };
B1B5599120EFC5E400210D55 /* DecryptionFailureTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DecryptionFailureTracker.m; sourceTree = "<group>"; };
B1B9194A2118984300FE25B5 /* RoomPredecessorBubbleCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RoomPredecessorBubbleCell.xib; sourceTree = "<group>"; };
B1B9DED922E9B7350065E677 /* SerializationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerializationService.swift; sourceTree = "<group>"; };
B1B9DEDB22E9B7440065E677 /* SerializationServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerializationServiceType.swift; sourceTree = "<group>"; };
B1B9DEDD22E9D9890065E677 /* EmojiServiceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiServiceType.swift; sourceTree = "<group>"; };
B1C562C92289C2690037F12A /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = "<group>"; };
B1C562CB228AB3510037F12A /* UIStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIStackView.swift; sourceTree = "<group>"; };
B1C562D8228C0B760037F12A /* RoomContextualMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomContextualMenuItem.swift; sourceTree = "<group>"; };
@ -1242,11 +1280,35 @@
B1DB4F05223015080065DBFA /* Character.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Character.swift; sourceTree = "<group>"; };
B1DB4F0A223131600065DBFA /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
B1DB4F0D22316FFF0065DBFA /* UserNameColorGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNameColorGenerator.swift; sourceTree = "<group>"; };
B1DCC60F22E5E16900625807 /* EmojiPickerViewController.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = EmojiPickerViewController.storyboard; sourceTree = "<group>"; };
B1DCC61022E5E16900625807 /* EmojiPickerViewState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerViewState.swift; sourceTree = "<group>"; };
B1DCC61122E5E16A00625807 /* EmojiPickerCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerCoordinatorType.swift; sourceTree = "<group>"; };
B1DCC61222E5E16C00625807 /* EmojiPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerViewController.swift; sourceTree = "<group>"; };
B1DCC61322E5E16D00625807 /* EmojiPickerCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerCoordinator.swift; sourceTree = "<group>"; };
B1DCC61422E5E16E00625807 /* EmojiPickerViewAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerViewAction.swift; sourceTree = "<group>"; };
B1DCC61522E5E16F00625807 /* EmojiPickerViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerViewModelType.swift; sourceTree = "<group>"; };
B1DCC61622E5E17000625807 /* EmojiPickerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiPickerViewModel.swift; sourceTree = "<group>"; };
B1DCC61F22E5EDA400625807 /* EmojiPickerCoordinatorBridgePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerCoordinatorBridgePresenter.swift; sourceTree = "<group>"; };
B1DCC62122E60BE000625807 /* EmojiPickerItemViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerItemViewData.swift; sourceTree = "<group>"; };
B1DCC62322E60CA800625807 /* EmojiPickerCategoryViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerCategoryViewData.swift; sourceTree = "<group>"; };
B1DCC62522E60CC600625807 /* EmojiItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItem.swift; sourceTree = "<group>"; };
B1DCC62722E60CE300625807 /* EmojiCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiCategory.swift; sourceTree = "<group>"; };
B1DCC62922E60D1000625807 /* EmojiMartService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartService.swift; sourceTree = "<group>"; };
B1DCC62B22E61EAF00625807 /* EmojiPickerViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerViewCell.swift; sourceTree = "<group>"; };
B1DCC62C22E61EAF00625807 /* EmojiPickerViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EmojiPickerViewCell.xib; sourceTree = "<group>"; };
B1DCC62F22E7026F00625807 /* EmojiPickerHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerHeaderView.swift; sourceTree = "<group>"; };
B1DCC63022E7026F00625807 /* EmojiPickerHeaderView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = EmojiPickerHeaderView.xib; sourceTree = "<group>"; };
B1DCC63322E72C1B00625807 /* UISearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISearchBar.swift; sourceTree = "<group>"; };
B1DCC63622E8541700625807 /* EmojiStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiStore.swift; sourceTree = "<group>"; };
B1DCC63822E85E9A00625807 /* EmojiMartStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartStore.swift; sourceTree = "<group>"; };
B1DCC63A22E85EF800625807 /* EmojiMartCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartCategory.swift; sourceTree = "<group>"; };
B1DCC63E22E9A3AE00625807 /* EmojiItem+EmojiMart.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EmojiItem+EmojiMart.swift"; sourceTree = "<group>"; };
B1E5368821FB1E20001F3AFF /* UIButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIButton.swift; sourceTree = "<group>"; };
B1E5368C21FB7245001F3AFF /* KeyBackupRecoverFromPassphraseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupRecoverFromPassphraseViewController.swift; sourceTree = "<group>"; };
B1E5368E21FB7258001F3AFF /* KeyBackupRecoverFromPassphraseViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = KeyBackupRecoverFromPassphraseViewController.storyboard; sourceTree = "<group>"; };
B1FDF55F21F5FE5500BA3834 /* KeyBackupSetupPassphraseViewAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyBackupSetupPassphraseViewAction.swift; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
F05927C71FDED835009F2A68 /* MXGroup+Riot.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MXGroup+Riot.m"; sourceTree = "<group>"; };
F05927C81FDED835009F2A68 /* MXGroup+Riot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MXGroup+Riot.h"; sourceTree = "<group>"; };
@ -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 = "<group>";
@ -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 = "<group>";
@ -1762,6 +1828,39 @@
path = RecoveryKey;
sourceTree = "<group>";
};
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 = "<group>";
};
B152C72C22DF21880041315A /* Data */ = {
isa = PBXGroup;
children = (
B1B9DEDD22E9D9890065E677 /* EmojiServiceType.swift */,
B1DCC64022E9B37400625807 /* Store */,
B1B9DED722E9B56C0065E677 /* EmojiMart */,
);
path = Data;
sourceTree = "<group>";
};
B1664BAB20F4E67500808783 /* Modules */ = {
isa = PBXGroup;
children = (
@ -2158,6 +2257,7 @@
B1B5569020EE6C4C00210D55 /* Settings */,
B1C562D7228C0B4C0037F12A /* ContextualMenu */,
B1963B24228F1C4800CBA17F /* BubbleReactions */,
B152C72922DCEA670041315A /* EmojiPicker */,
);
path = Room;
sourceTree = "<group>";
@ -3088,6 +3188,7 @@
B1B5597C20EFC3DF00210D55 /* Managers */ = {
isa = PBXGroup;
children = (
B1B9DED822E9B7120065E677 /* Serialization */,
B1FDF56321F68C0700BA3834 /* PasswordStrength */,
B1798300211B137B001FD722 /* OnBoarding */,
B1B5598B20EFC5E400210D55 /* Analytics */,
@ -3141,6 +3242,26 @@
path = Analytics;
sourceTree = "<group>";
};
B1B9DED722E9B56C0065E677 /* EmojiMart */ = {
isa = PBXGroup;
children = (
B1DCC62922E60D1000625807 /* EmojiMartService.swift */,
B1DCC63822E85E9A00625807 /* EmojiMartStore.swift */,
B1DCC63A22E85EF800625807 /* EmojiMartCategory.swift */,
B1DCC63E22E9A3AE00625807 /* EmojiItem+EmojiMart.swift */,
);
path = EmojiMart;
sourceTree = "<group>";
};
B1B9DED822E9B7120065E677 /* Serialization */ = {
isa = PBXGroup;
children = (
B1B9DEDB22E9B7440065E677 /* SerializationServiceType.swift */,
B1B9DED922E9B7350065E677 /* SerializationService.swift */,
);
path = Serialization;
sourceTree = "<group>";
};
B1C562D7228C0B4C0037F12A /* ContextualMenu */ = {
isa = PBXGroup;
children = (
@ -3183,6 +3304,16 @@
path = KeyboardAvoiding;
sourceTree = "<group>";
};
B1DCC64022E9B37400625807 /* Store */ = {
isa = PBXGroup;
children = (
B1DCC63622E8541700625807 /* EmojiStore.swift */,
B1DCC62722E60CE300625807 /* EmojiCategory.swift */,
B1DCC62522E60CC600625807 /* EmojiItem.swift */,
);
path = Store;
sourceTree = "<group>";
};
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 = "<group>";
@ -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+EmojiMart.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 /* 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 /* EmojiMartCategory.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 /* EmojiMartService.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;

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "more_reactions.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "more_reactions@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "more_reactions@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

File diff suppressed because one or more lines are too long

View file

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

View file

@ -1359,6 +1359,23 @@
href="https://www.apache.org/licenses/LICENSE-2.0">https://www.apache.org/licenses/LICENSE-2.0</a>
<br/><br/>Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
</li>
<li>
<p><b>Emoji Mart</b> (<a href="https://github.com/missive/emoji-mart">https://github.com/missive/emoji-mart</a>)</p>
<pre>
Copyright (c) 2016, Missive
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
</pre>
</li>
</ul>
</body>
</html>

View file

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

View file

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

View file

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

View file

@ -42,6 +42,11 @@ internal enum StoryboardScene {
internal static let initialScene = InitialSceneType<Riot.EditHistoryViewController>(storyboard: EditHistoryViewController.self)
}
internal enum EmojiPickerViewController: StoryboardType {
internal static let storyboardName = "EmojiPickerViewController"
internal static let initialScene = InitialSceneType<Riot.EmojiPickerViewController>(storyboard: EmojiPickerViewController.self)
}
internal enum KeyBackupRecoverFromPassphraseViewController: StoryboardType {
internal static let storyboardName = "KeyBackupRecoverFromPassphraseViewController"

View file

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

View file

@ -0,0 +1,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<T: Decodable>(_ data: Data) throws -> T {
return try decoder.decode(T.self, from: data)
}
func serialize<T: Encodable>(_ object: T) throws -> Data {
return try encoder.encode(object)
}
}

View file

@ -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<T: Decodable>(_ data: Data) throws -> T
func serialize<T: Encodable>(_ object: T) throws -> Data
}

View file

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

View file

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

View file

@ -30,7 +30,9 @@ final class ReactionsMenuView: UIView, Themable, NibLoadable {
// MARK: Outlets
@IBOutlet private weak var reactionsBackgroundView: UIView!
@IBOutlet private weak var reactionsStackView: UIStackView!
@IBOutlet private weak var reactionsStackView: UIStackView!
@IBOutlet private weak var moreReactionsBackgroundView: UIView!
@IBOutlet private weak var moreReactionsButton: UIButton!
// MARK: Private
@ -57,6 +59,10 @@ final class ReactionsMenuView: UIView, Themable, NibLoadable {
super.awakeFromNib()
self.reactionsBackgroundView.layer.masksToBounds = true
let moreReactionsImage = Asset.Images.moreReactions.image.withRenderingMode(.alwaysTemplate)
self.moreReactionsButton.setImage(moreReactionsImage, for: .normal)
self.update(theme: ThemeService.shared().theme)
}
@ -64,12 +70,15 @@ final class ReactionsMenuView: UIView, Themable, NibLoadable {
super.layoutSubviews()
self.reactionsBackgroundView.layer.cornerRadius = self.reactionsBackgroundView.frame.size.height/2
self.moreReactionsBackgroundView.layer.cornerRadius = self.moreReactionsBackgroundView.frame.size.height/2
}
// MARK: - Public
func update(theme: Theme) {
self.reactionsBackgroundView.backgroundColor = theme.headerBackgroundColor
self.moreReactionsBackgroundView.backgroundColor = theme.headerBackgroundColor
self.moreReactionsButton.tintColor = theme.textPrimaryColor
}
func selectionAnimationInstructionPart1() {
@ -132,6 +141,10 @@ final class ReactionsMenuView: UIView, Themable, NibLoadable {
self.tappedReactionButton = sender
self.viewModel?.process(viewAction: .tap(reaction: tappedReaction))
}
@IBAction private func moreReactionsAction(_ sender: Any) {
self.viewModel?.process(viewAction: .moreReactions)
}
}
// MARK: - ReactionsMenuViewModelViewDelegate

View file

@ -16,36 +16,66 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eXy-Kc-Ck7">
<rect key="frame" x="0.0" y="0.0" width="459" height="59"/>
<rect key="frame" x="0.0" y="0.0" width="395" height="59"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacing="2" translatesAutoresizingMaskIntoConstraints="NO" id="uzd-VB-mKT">
<rect key="frame" x="5" y="5" width="449" height="49"/>
<rect key="frame" x="5" y="5" width="385" height="49"/>
</stackView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="uzd-VB-mKT" secondAttribute="trailing" constant="5" id="fDZ-NX-EyG"/>
<constraint firstItem="uzd-VB-mKT" firstAttribute="leading" secondItem="eXy-Kc-Ck7" secondAttribute="leading" constant="5" id="iCa-Ob-e7J"/>
<constraint firstAttribute="trailing" secondItem="uzd-VB-mKT" secondAttribute="trailing" constant="5" id="lG6-Qg-UcU"/>
<constraint firstItem="uzd-VB-mKT" firstAttribute="top" secondItem="eXy-Kc-Ck7" secondAttribute="top" constant="5" id="msb-Ay-Bp6"/>
<constraint firstAttribute="bottom" secondItem="uzd-VB-mKT" secondAttribute="bottom" constant="5" id="nJE-Gm-Rf1"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="44R-pM-RoU">
<rect key="frame" x="400" y="0.0" width="59" height="59"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="VQg-zY-j1F">
<rect key="frame" x="0.0" y="0.0" width="59" height="59"/>
<state key="normal" image="more_reactions">
<color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</state>
<connections>
<action selector="moreReactionsAction:" destination="7Xq-Wy-z0M" eventType="touchUpInside" id="09O-bn-ezs"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="VQg-zY-j1F" firstAttribute="top" secondItem="44R-pM-RoU" secondAttribute="top" id="Ku5-Ra-6tc"/>
<constraint firstItem="VQg-zY-j1F" firstAttribute="leading" secondItem="44R-pM-RoU" secondAttribute="leading" id="YpY-1A-KhO"/>
<constraint firstAttribute="trailing" secondItem="VQg-zY-j1F" secondAttribute="trailing" id="aYC-Po-gf0"/>
<constraint firstAttribute="bottom" secondItem="VQg-zY-j1F" secondAttribute="bottom" id="fkS-hS-S5L"/>
<constraint firstAttribute="width" secondItem="44R-pM-RoU" secondAttribute="height" multiplier="1:1" id="vLH-qD-4s0"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="eXy-Kc-Ck7" firstAttribute="top" secondItem="7Xq-Wy-z0M" secondAttribute="top" id="43J-YM-JHg"/>
<constraint firstItem="eXy-Kc-Ck7" firstAttribute="leading" secondItem="7Xq-Wy-z0M" secondAttribute="leading" id="9dy-sK-ygm"/>
<constraint firstAttribute="trailing" secondItem="eXy-Kc-Ck7" secondAttribute="trailing" id="E4Z-bt-BRw"/>
<constraint firstAttribute="bottom" secondItem="eXy-Kc-Ck7" secondAttribute="bottom" id="Nb2-0z-IiS"/>
<constraint firstItem="44R-pM-RoU" firstAttribute="top" secondItem="7Xq-Wy-z0M" secondAttribute="top" id="aLI-UY-XKE"/>
<constraint firstAttribute="trailing" secondItem="44R-pM-RoU" secondAttribute="trailing" id="fKs-oS-uui"/>
<constraint firstAttribute="bottom" secondItem="44R-pM-RoU" secondAttribute="bottom" id="lE1-4e-whG"/>
<constraint firstItem="44R-pM-RoU" firstAttribute="leading" secondItem="eXy-Kc-Ck7" secondAttribute="trailing" constant="5" id="zFB-XG-OKN"/>
</constraints>
<nil key="simulatedTopBarMetrics"/>
<nil key="simulatedBottomBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="moreReactionsBackgroundView" destination="44R-pM-RoU" id="0Y4-Pt-1Zh"/>
<outlet property="moreReactionsButton" destination="VQg-zY-j1F" id="ZfR-3o-lPV"/>
<outlet property="reactionsBackgroundView" destination="eXy-Kc-Ck7" id="VYi-YD-mb9"/>
<outlet property="reactionsStackView" destination="uzd-VB-mKT" id="DTV-Nh-bcm"/>
</connections>
<point key="canvasLocation" x="444.20289855072468" y="-718.19196428571422"/>
<point key="canvasLocation" x="296" y="-854"/>
</view>
</objects>
<resources>
<image name="more_reactions" width="22" height="20"/>
</resources>
</document>

View file

@ -20,4 +20,5 @@ import UIKit
enum ReactionsMenuViewAction {
case loadData
case tap(reaction: String)
case moreReactions
}

View file

@ -56,6 +56,8 @@ import Foundation
self.coordinatorDelegate?.reactionsMenuViewModel(self, didAddReaction: reaction, forEventId: self.eventId)
}
}
case .moreReactions:
self.coordinatorDelegate?.reactionsMenuViewModelDidTapMoreReactions(self, forEventId: self.eventId)
}
}

View file

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

View file

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

View file

@ -0,0 +1,83 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
extension EmojiItem: Decodable {
/// JSON keys associated to EmojiItem properties.
/// See https://github.com/missive/emoji-mart/blob/master/src/utils/data.js for minified letters informations.
///
/// - shortName: The commonly-agreed short name for the emoji, as supported in GitHub and others via the :short_name: syntax.
/// - name: The offical Unicode name.
/// - codepoint: The Unicode codepoint, as 4-5 hex digits. Where an emoji needs 2 or more codepoints, they are specified like 1F1EA-1F1F8.
/// - shortNames: An array of all the other known short names.
/// - keywords: Associated emoji keywords.
enum CodingKeys: String, CodingKey {
case shortName
case name = "a"
case codepoint = "b"
case shortNames = "n"
case keywords = "j"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard let shortName = decoder.codingPath.last?.stringValue else {
throw DecodingError.dataCorruptedError(forKey: .shortName, in: container, debugDescription: "Cannot initialize short name")
}
let emojiUnicodeStringValue = try container.decode(String.self, forKey: .codepoint)
let unicodeStringComponents = emojiUnicodeStringValue.components(separatedBy: "-")
var emoji = ""
for unicodeStringComponent in unicodeStringComponents {
if let unicodeCodePoint = Int(unicodeStringComponent, radix: 16),
let emojiUnicodeScalar = UnicodeScalar(unicodeCodePoint) {
emoji.append(String(emojiUnicodeScalar))
} else {
throw DecodingError.dataCorruptedError(forKey: .codepoint, in: container, debugDescription: "Cannot initialize emoji")
}
}
let name = try container.decode(String.self, forKey: .name)
let shortNames: [String]
if let decodedShortNames = try container.decodeIfPresent([String].self, forKey: .shortNames) {
shortNames = decodedShortNames
} else {
shortNames = []
}
let keywords: [String]
if let decodedKeywords = try container.decodeIfPresent([String].self, forKey: .keywords) {
keywords = decodedKeywords
} else {
keywords = []
}
self.init(shortName: shortName,
value: emoji,
name: name,
shortNames: shortNames,
keywords: keywords)
}
}

View file

@ -0,0 +1,67 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
struct EmojiMartStore {
let categories: [EmojiJSONCategory]
let emojis: [EmojiItem]
}
// MARK: - Decodable
extension EmojiMartStore: Decodable {
/// JSON keys associated to EmojiJSONStore properties.
enum CodingKeys: String, CodingKey {
case categories
case emojis
}
/// JSON key associated to emoji short name.
struct EmojiKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let emojisContainer = try container.nestedContainer(keyedBy: EmojiKey.self, forKey: .emojis)
let emojis: [EmojiItem] = emojisContainer.allKeys.compactMap { (emojiKey) -> EmojiItem? in
let emojiItem: EmojiItem?
do {
emojiItem = try emojisContainer.decode(EmojiItem.self, forKey: emojiKey)
} catch {
print("[EmojiJSONStore] init(from decoder: Decoder) failed to parse emojiItem \(emojiKey) with error: \(error)")
emojiItem = nil
}
return emojiItem
}
let categories = try container.decode([EmojiJSONCategory].self, forKey: .categories)
self.init(categories: categories, emojis: emojis)
}
}

View file

@ -0,0 +1,40 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
struct EmojiMartCategory {
/// Emoji category identifier (e.g. "people")
let identifier: String
/// Emoji category name in english (e.g. "Smiley & People")
let name: String
/// List of emoji short names associated to the category (e.g. "people")
let emojiShortNames: [String]
}
// MARK: - Decodable
extension EmojiMartCategory: Decodable {
/// JSON keys associated to EmojiJSONCategory properties.
enum CodingKeys: String, CodingKey {
case identifier = "id"
case name
case emojiShortNames = "emojis"
}
}

View file

@ -0,0 +1,72 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
enum EmojiServiceError: Error {
case emojiJSONFileNotFound
}
/// Emoji service powered by Emoji Mart data (https://github.com/missive/emoji-mart/)
final class EmojiMartService: EmojiServiceType {
// MARK: - Constants
/// Emoji data coming from https://github.com/missive/emoji-mart/blob/master/data/apple.json
private static let jsonFilename = "apple_emojis_data"
// MARK: - Properties
private let serializationService: SerializationServiceType = SerializationService()
private let serviceQueue = DispatchQueue(label: "\(type(of: EmojiMartService.self))")
// MARK: - Public
func getEmojiCategories(completion: @escaping (MXResponse<[EmojiCategory]>) -> Void) {
self.serviceQueue.async {
do {
let emojiJSONData = try self.getEmojisJSONData()
let emojiJSONStore: EmojiMartStore = try self.serializationService.deserialize(emojiJSONData)
let emojiCategories = self.emojiCategories(from: emojiJSONStore)
completion(MXResponse.success(emojiCategories))
} catch {
completion(MXResponse.failure(error))
}
}
}
// MARK: - Private
private func getEmojisJSONData() throws -> Data {
guard let jsonDataURL = Bundle.main.url(forResource: EmojiMartService.jsonFilename, withExtension: "json") else {
throw EmojiServiceError.emojiJSONFileNotFound
}
let jsonData = try Data(contentsOf: jsonDataURL)
return jsonData
}
private func emojiCategories(from emojiJSONStore: EmojiMartStore) -> [EmojiCategory] {
let allEmojiItems = emojiJSONStore.emojis
return emojiJSONStore.categories.map { (jsonCategory) -> EmojiCategory in
let emojiItems = jsonCategory.emojiShortNames.compactMap({ (shortName) -> EmojiItem? in
return allEmojiItems.first(where: { $0.shortName == shortName })
})
return EmojiCategory(identifier: jsonCategory.identifier, emojis: emojiItems)
}
}
}

View file

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

View file

@ -0,0 +1,21 @@
/*
Copyright 2019 New Vector Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import Foundation
protocol EmojiServiceType {
func getEmojiCategories(completion: @escaping (MXResponse<[EmojiCategory]>) -> Void)
}

View file

@ -0,0 +1,32 @@
/*
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 {
/// 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)
}
}

View file

@ -0,0 +1,57 @@
/*
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 {
// 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]
/// 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]
// MARK: - Setup
init(shortName: String,
value: String,
name: String,
shortNames: [String] = [],
keywords: [String] = [],
variations: [EmojiItem] = []) {
self.shortName = shortName
self.value = value
self.name = name
self.shortNames = shortNames
self.keywords = keywords
self.variations = variations
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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 emoji picker navigation flow.
protocol EmojiPickerCoordinatorType: Coordinator, Presentable {
var delegate: EmojiPickerCoordinatorDelegate? { get }
}

View file

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

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionReusableView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="EmojiPickerHeaderView" id="U6b-Vx-4bR" customClass="EmojiPickerHeaderView" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="320" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wrW-Gv-YJV">
<rect key="frame" x="20" y="8" width="280" height="34"/>
<fontDescription key="fontDescription" type="system" weight="medium" pointSize="15"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="wrW-Gv-YJV" secondAttribute="bottom" constant="8" id="D7a-Br-Vlz"/>
<constraint firstItem="wrW-Gv-YJV" firstAttribute="top" secondItem="U6b-Vx-4bR" secondAttribute="top" constant="8" id="IGK-ta-CnU"/>
<constraint firstItem="wrW-Gv-YJV" firstAttribute="leading" secondItem="VXr-Tz-HHm" secondAttribute="leading" constant="20" id="T3V-4C-3Qu"/>
<constraint firstItem="VXr-Tz-HHm" firstAttribute="trailing" secondItem="wrW-Gv-YJV" secondAttribute="trailing" constant="20" id="wSZ-15-Co2"/>
</constraints>
<viewLayoutGuide key="safeArea" id="VXr-Tz-HHm"/>
<connections>
<outlet property="titleLabel" destination="wrW-Gv-YJV" id="wYw-xg-hha"/>
</connections>
<point key="canvasLocation" x="-404" y="-81"/>
</collectionReusableView>
</objects>
</document>

View file

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

View file

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

View file

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

View file

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_1" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX" customClass="EmojiPickerViewCell" customModule="Riot" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Sam-a3-I0s">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="252" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" text="👍" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9GU-d6-Cnv">
<rect key="frame" x="6" y="4" width="38" height="42"/>
<fontDescription key="fontDescription" type="system" pointSize="30"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.95294117649999999" green="0.97254901959999995" blue="0.99215686270000003" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="9GU-d6-Cnv" firstAttribute="top" secondItem="Sam-a3-I0s" secondAttribute="top" constant="4" id="T08-V8-wu1"/>
<constraint firstAttribute="bottom" secondItem="9GU-d6-Cnv" secondAttribute="bottom" constant="4" id="dgR-Un-bnz"/>
<constraint firstAttribute="trailing" secondItem="9GU-d6-Cnv" secondAttribute="trailing" constant="6" id="gnQ-6e-N8Y"/>
<constraint firstItem="9GU-d6-Cnv" firstAttribute="leading" secondItem="Sam-a3-I0s" secondAttribute="leading" constant="6" id="wHy-AN-9GI"/>
</constraints>
</view>
</subviews>
</view>
<constraints>
<constraint firstAttribute="trailing" secondItem="Sam-a3-I0s" secondAttribute="trailing" id="5FI-St-rx2"/>
<constraint firstItem="Sam-a3-I0s" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="Oge-ZF-LX9"/>
<constraint firstAttribute="bottom" secondItem="Sam-a3-I0s" secondAttribute="bottom" id="f0f-GW-KI4"/>
<constraint firstItem="Sam-a3-I0s" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="vze-ak-cqC"/>
</constraints>
<viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/>
<connections>
<outlet property="emojiLabel" destination="9GU-d6-Cnv" id="5kc-lg-TpL"/>
<outlet property="reactionBackgroundView" destination="Sam-a3-I0s" id="x8I-E5-BRG"/>
</connections>
</collectionViewCell>
</objects>
</document>

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="V8j-Lb-PgC">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Emoji Picker View Controller-->
<scene sceneID="mt5-wz-YKA">
<objects>
<viewController extendedLayoutIncludesOpaqueBars="YES" automaticallyAdjustsScrollViewInsets="NO" id="V8j-Lb-PgC" customClass="EmojiPickerViewController" customModule="Riot" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="EL9-GA-lwo">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="Kjj-O1-UsZ">
<rect key="frame" x="0.0" y="20" width="375" height="647"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="iPs-P1-bkS">
<size key="itemSize" width="50" height="50"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells/>
<connections>
<outlet property="dataSource" destination="V8j-Lb-PgC" id="ZkG-A0-JGC"/>
<outlet property="delegate" destination="V8j-Lb-PgC" id="iXC-RG-INF"/>
</connections>
</collectionView>
</subviews>
<color key="backgroundColor" red="0.94509803921568625" green="0.96078431372549022" blue="0.97254901960784312" alpha="1" colorSpace="calibratedRGB"/>
<constraints>
<constraint firstItem="Kjj-O1-UsZ" firstAttribute="leading" secondItem="EL9-GA-lwo" secondAttribute="leading" id="6Ys-O9-EZy"/>
<constraint firstItem="bFg-jh-JZB" firstAttribute="top" secondItem="Kjj-O1-UsZ" secondAttribute="top" id="MQy-0K-KQf"/>
<constraint firstAttribute="trailing" secondItem="Kjj-O1-UsZ" secondAttribute="trailing" id="XkY-kj-afK"/>
<constraint firstAttribute="bottom" secondItem="Kjj-O1-UsZ" secondAttribute="bottom" id="cdk-7X-2Pn"/>
</constraints>
<viewLayoutGuide key="safeArea" id="bFg-jh-JZB"/>
</view>
<extendedEdge key="edgesForExtendedLayout" bottom="YES"/>
<connections>
<outlet property="collectionView" destination="Kjj-O1-UsZ" id="iBb-J9-0Uc"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="zK0-v6-7Wt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3199.1999999999998" y="-647.22638680659679"/>
</scene>
</scenes>
</document>

View file

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

View file

@ -0,0 +1,153 @@
// 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: EmojiServiceType
private let emojiStore: EmojiStore
private let processingQueue: DispatchQueue
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 = EmojiMartService()
self.emojiStore = EmojiStore.shared
self.processingQueue = DispatchQueue(label: "\(type(of: self))")
}
// 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?) {
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))
}
}
}
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.shortName, 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
}
}
}

View file

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

View file

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

View file

@ -124,7 +124,7 @@
#import "Riot-Swift.h"
@interface RoomViewController () <UISearchBarDelegate, UIGestureRecognizerDelegate, RoomTitleViewTapGestureDelegate, RoomParticipantsViewControllerDelegate, MXKRoomMemberDetailsViewControllerDelegate, ContactsTableViewControllerDelegate, MXServerNoticesDelegate, RoomContextualMenuViewControllerDelegate,
ReactionsMenuViewModelCoordinatorDelegate, EditHistoryCoordinatorBridgePresenterDelegate, MXKDocumentPickerPresenterDelegate>
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

View file

@ -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 = EmojiMartService()
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.shortName, "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)
}
}
}

View file

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