mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 23:32:41 +00:00
Merge pull request #2605 from vector-im/riot_2370
Reactions: Emoji picker
This commit is contained in:
commit
436e5f0fa6
52 changed files with 2303 additions and 29 deletions
|
@ -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)
|
||||
===============================================
|
||||
|
|
4
Podfile
4
Podfile
|
@ -79,6 +79,10 @@ abstract_target 'RiotPods' do
|
|||
target "Riot" do
|
||||
import_MatrixKit
|
||||
pod 'DGCollectionViewLeftAlignFlowLayout', '~> 1.0.4'
|
||||
|
||||
target 'RiotTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
target "RiotShareExtension" do
|
||||
|
|
|
@ -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;
|
||||
|
|
23
Riot/Assets/Images.xcassets/Room/more_reactions.imageset/Contents.json
vendored
Normal file
23
Riot/Assets/Images.xcassets/Room/more_reactions.imageset/Contents.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
BIN
Riot/Assets/Images.xcassets/Room/more_reactions.imageset/more_reactions.png
vendored
Normal file
BIN
Riot/Assets/Images.xcassets/Room/more_reactions.imageset/more_reactions.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
Riot/Assets/Images.xcassets/Room/more_reactions.imageset/more_reactions@2x.png
vendored
Normal file
BIN
Riot/Assets/Images.xcassets/Room/more_reactions.imageset/more_reactions@2x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
BIN
Riot/Assets/Images.xcassets/Room/more_reactions.imageset/more_reactions@3x.png
vendored
Normal file
BIN
Riot/Assets/Images.xcassets/Room/more_reactions.imageset/more_reactions@3x.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 6 KiB |
1
Riot/Assets/apple_emojis_data.json
Normal file
1
Riot/Assets/apple_emojis_data.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -916,3 +916,15 @@
|
|||
// MARK: File upload
|
||||
"file_upload_error_title" = "File upload";
|
||||
"file_upload_error_unsupported_file_type_message" = "File type not supported.";
|
||||
|
||||
// MARK: Emoji picker
|
||||
"emoji_picker_title" = "Reactions";
|
||||
|
||||
"emoji_picker_people_category" = "Smileys & People";
|
||||
"emoji_picker_nature_category" = "Animals & Nature";
|
||||
"emoji_picker_foods_category" = "Food & Drink";
|
||||
"emoji_picker_activity_category" = "Activities";
|
||||
"emoji_picker_places_category" = "Travel & Places";
|
||||
"emoji_picker_objects_category" = "Objects";
|
||||
"emoji_picker_symbols_category" = "Symbols";
|
||||
"emoji_picker_flags_category" = "Flags";
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
25
Riot/Categories/UISearchBar.swift
Normal file
25
Riot/Categories/UISearchBar.swift
Normal 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
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
35
Riot/Managers/Serialization/SerializationService.swift
Normal file
35
Riot/Managers/Serialization/SerializationService.swift
Normal 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)
|
||||
}
|
||||
}
|
22
Riot/Managers/Serialization/SerializationServiceType.swift
Normal file
22
Riot/Managers/Serialization/SerializationServiceType.swift
Normal 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
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -20,4 +20,5 @@ import UIKit
|
|||
enum ReactionsMenuViewAction {
|
||||
case loadData
|
||||
case tap(reaction: String)
|
||||
case moreReactions
|
||||
}
|
||||
|
|
|
@ -56,6 +56,8 @@ import Foundation
|
|||
self.coordinatorDelegate?.reactionsMenuViewModel(self, didAddReaction: reaction, forEventId: self.eventId)
|
||||
}
|
||||
}
|
||||
case .moreReactions:
|
||||
self.coordinatorDelegate?.reactionsMenuViewModelDidTapMoreReactions(self, forEventId: self.eventId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
extension EmojiItem: Decodable {
|
||||
|
||||
/// JSON keys associated to EmojiItem properties.
|
||||
/// See https://github.com/missive/emoji-mart/blob/master/src/utils/data.js for minified letters informations.
|
||||
///
|
||||
/// - shortName: The commonly-agreed short name for the emoji, as supported in GitHub and others via the :short_name: syntax.
|
||||
/// - name: The offical Unicode name.
|
||||
/// - codepoint: The Unicode codepoint, as 4-5 hex digits. Where an emoji needs 2 or more codepoints, they are specified like 1F1EA-1F1F8.
|
||||
/// - shortNames: An array of all the other known short names.
|
||||
/// - keywords: Associated emoji keywords.
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case shortName
|
||||
case name = "a"
|
||||
case codepoint = "b"
|
||||
case shortNames = "n"
|
||||
case keywords = "j"
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
guard let shortName = decoder.codingPath.last?.stringValue else {
|
||||
throw DecodingError.dataCorruptedError(forKey: .shortName, in: container, debugDescription: "Cannot initialize short name")
|
||||
}
|
||||
|
||||
let emojiUnicodeStringValue = try container.decode(String.self, forKey: .codepoint)
|
||||
|
||||
let unicodeStringComponents = emojiUnicodeStringValue.components(separatedBy: "-")
|
||||
|
||||
var emoji = ""
|
||||
|
||||
for unicodeStringComponent in unicodeStringComponents {
|
||||
if let unicodeCodePoint = Int(unicodeStringComponent, radix: 16),
|
||||
let emojiUnicodeScalar = UnicodeScalar(unicodeCodePoint) {
|
||||
emoji.append(String(emojiUnicodeScalar))
|
||||
} else {
|
||||
throw DecodingError.dataCorruptedError(forKey: .codepoint, in: container, debugDescription: "Cannot initialize emoji")
|
||||
}
|
||||
}
|
||||
|
||||
let name = try container.decode(String.self, forKey: .name)
|
||||
|
||||
let shortNames: [String]
|
||||
|
||||
if let decodedShortNames = try container.decodeIfPresent([String].self, forKey: .shortNames) {
|
||||
shortNames = decodedShortNames
|
||||
} else {
|
||||
shortNames = []
|
||||
}
|
||||
|
||||
let keywords: [String]
|
||||
|
||||
if let decodedKeywords = try container.decodeIfPresent([String].self, forKey: .keywords) {
|
||||
keywords = decodedKeywords
|
||||
} else {
|
||||
keywords = []
|
||||
}
|
||||
|
||||
self.init(shortName: shortName,
|
||||
value: emoji,
|
||||
name: name,
|
||||
shortNames: shortNames,
|
||||
keywords: keywords)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
struct EmojiMartStore {
|
||||
let categories: [EmojiJSONCategory]
|
||||
let emojis: [EmojiItem]
|
||||
}
|
||||
|
||||
// MARK: - Decodable
|
||||
extension EmojiMartStore: Decodable {
|
||||
|
||||
/// JSON keys associated to EmojiJSONStore properties.
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case categories
|
||||
case emojis
|
||||
}
|
||||
|
||||
/// JSON key associated to emoji short name.
|
||||
struct EmojiKey: CodingKey {
|
||||
var stringValue: String
|
||||
|
||||
init?(stringValue: String) {
|
||||
self.stringValue = stringValue
|
||||
}
|
||||
|
||||
var intValue: Int? { return nil }
|
||||
init?(intValue: Int) { return nil }
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
let emojisContainer = try container.nestedContainer(keyedBy: EmojiKey.self, forKey: .emojis)
|
||||
|
||||
let emojis: [EmojiItem] = emojisContainer.allKeys.compactMap { (emojiKey) -> EmojiItem? in
|
||||
let emojiItem: EmojiItem?
|
||||
|
||||
do {
|
||||
emojiItem = try emojisContainer.decode(EmojiItem.self, forKey: emojiKey)
|
||||
} catch {
|
||||
print("[EmojiJSONStore] init(from decoder: Decoder) failed to parse emojiItem \(emojiKey) with error: \(error)")
|
||||
emojiItem = nil
|
||||
}
|
||||
|
||||
return emojiItem
|
||||
}
|
||||
|
||||
let categories = try container.decode([EmojiJSONCategory].self, forKey: .categories)
|
||||
|
||||
self.init(categories: categories, emojis: emojis)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
struct EmojiMartCategory {
|
||||
|
||||
/// Emoji category identifier (e.g. "people")
|
||||
let identifier: String
|
||||
|
||||
/// Emoji category name in english (e.g. "Smiley & People")
|
||||
let name: String
|
||||
|
||||
/// List of emoji short names associated to the category (e.g. "people")
|
||||
let emojiShortNames: [String]
|
||||
}
|
||||
|
||||
// MARK: - Decodable
|
||||
extension EmojiMartCategory: Decodable {
|
||||
|
||||
/// JSON keys associated to EmojiJSONCategory properties.
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case identifier = "id"
|
||||
case name
|
||||
case emojiShortNames = "emojis"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
enum EmojiServiceError: Error {
|
||||
case emojiJSONFileNotFound
|
||||
}
|
||||
|
||||
/// Emoji service powered by Emoji Mart data (https://github.com/missive/emoji-mart/)
|
||||
final class EmojiMartService: EmojiServiceType {
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
/// Emoji data coming from https://github.com/missive/emoji-mart/blob/master/data/apple.json
|
||||
private static let jsonFilename = "apple_emojis_data"
|
||||
|
||||
// MARK: - Properties
|
||||
|
||||
private let serializationService: SerializationServiceType = SerializationService()
|
||||
private let serviceQueue = DispatchQueue(label: "\(type(of: EmojiMartService.self))")
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
func getEmojiCategories(completion: @escaping (MXResponse<[EmojiCategory]>) -> Void) {
|
||||
self.serviceQueue.async {
|
||||
do {
|
||||
let emojiJSONData = try self.getEmojisJSONData()
|
||||
let emojiJSONStore: EmojiMartStore = try self.serializationService.deserialize(emojiJSONData)
|
||||
let emojiCategories = self.emojiCategories(from: emojiJSONStore)
|
||||
completion(MXResponse.success(emojiCategories))
|
||||
} catch {
|
||||
completion(MXResponse.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func getEmojisJSONData() throws -> Data {
|
||||
guard let jsonDataURL = Bundle.main.url(forResource: EmojiMartService.jsonFilename, withExtension: "json") else {
|
||||
throw EmojiServiceError.emojiJSONFileNotFound
|
||||
}
|
||||
let jsonData = try Data(contentsOf: jsonDataURL)
|
||||
return jsonData
|
||||
}
|
||||
|
||||
private func emojiCategories(from emojiJSONStore: EmojiMartStore) -> [EmojiCategory] {
|
||||
let allEmojiItems = emojiJSONStore.emojis
|
||||
|
||||
return emojiJSONStore.categories.map { (jsonCategory) -> EmojiCategory in
|
||||
let emojiItems = jsonCategory.emojiShortNames.compactMap({ (shortName) -> EmojiItem? in
|
||||
return allEmojiItems.first(where: { $0.shortName == shortName })
|
||||
})
|
||||
return EmojiCategory(identifier: jsonCategory.identifier, emojis: emojiItems)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
struct EmojiMartStore {
|
||||
let categories: [EmojiMartCategory]
|
||||
let emojis: [EmojiItem]
|
||||
}
|
||||
|
||||
// MARK: - Decodable
|
||||
extension EmojiMartStore: Decodable {
|
||||
|
||||
/// JSON keys associated to EmojiMartStore properties.
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case categories
|
||||
case emojis
|
||||
}
|
||||
|
||||
/// JSON key associated to emoji short name.
|
||||
struct EmojiKey: CodingKey {
|
||||
var stringValue: String
|
||||
|
||||
init?(stringValue: String) {
|
||||
self.stringValue = stringValue
|
||||
}
|
||||
|
||||
var intValue: Int? { return nil }
|
||||
init?(intValue: Int) { return nil }
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
let emojisContainer = try container.nestedContainer(keyedBy: EmojiKey.self, forKey: .emojis)
|
||||
|
||||
let emojis: [EmojiItem] = emojisContainer.allKeys.compactMap { (emojiKey) -> EmojiItem? in
|
||||
let emojiItem: EmojiItem?
|
||||
|
||||
do {
|
||||
emojiItem = try emojisContainer.decode(EmojiItem.self, forKey: emojiKey)
|
||||
} catch {
|
||||
print("[EmojiJSONStore] init(from decoder: Decoder) failed to parse emojiItem \(emojiKey) with error: \(error)")
|
||||
emojiItem = nil
|
||||
}
|
||||
|
||||
return emojiItem
|
||||
}
|
||||
|
||||
let categories = try container.decode([EmojiMartCategory].self, forKey: .categories)
|
||||
|
||||
self.init(categories: categories, emojis: emojis)
|
||||
}
|
||||
}
|
21
Riot/Modules/Room/EmojiPicker/Data/EmojiServiceType.swift
Normal file
21
Riot/Modules/Room/EmojiPicker/Data/EmojiServiceType.swift
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
Copyright 2019 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol EmojiServiceType {
|
||||
func getEmojiCategories(completion: @escaping (MXResponse<[EmojiCategory]>) -> Void)
|
||||
}
|
32
Riot/Modules/Room/EmojiPicker/Data/Store/EmojiCategory.swift
Normal file
32
Riot/Modules/Room/EmojiPicker/Data/Store/EmojiCategory.swift
Normal 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)
|
||||
}
|
||||
}
|
57
Riot/Modules/Room/EmojiPicker/Data/Store/EmojiItem.swift
Normal file
57
Riot/Modules/Room/EmojiPicker/Data/Store/EmojiItem.swift
Normal 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
|
||||
}
|
||||
}
|
69
Riot/Modules/Room/EmojiPicker/Data/Store/EmojiStore.swift
Normal file
69
Riot/Modules/Room/EmojiPicker/Data/Store/EmojiStore.swift
Normal 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
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
}
|
76
Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinator.swift
Normal file
76
Riot/Modules/Room/EmojiPicker/EmojiPickerCoordinator.swift
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
}
|
36
Riot/Modules/Room/EmojiPicker/EmojiPickerHeaderView.swift
Normal file
36
Riot/Modules/Room/EmojiPicker/EmojiPickerHeaderView.swift
Normal 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
|
||||
}
|
||||
}
|
39
Riot/Modules/Room/EmojiPicker/EmojiPickerHeaderView.xib
Normal file
39
Riot/Modules/Room/EmojiPicker/EmojiPickerHeaderView.xib
Normal 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>
|
23
Riot/Modules/Room/EmojiPicker/EmojiPickerItemViewData.swift
Normal file
23
Riot/Modules/Room/EmojiPicker/EmojiPickerItemViewData.swift
Normal 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
|
||||
}
|
27
Riot/Modules/Room/EmojiPicker/EmojiPickerViewAction.swift
Normal file
27
Riot/Modules/Room/EmojiPicker/EmojiPickerViewAction.swift
Normal 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?)
|
||||
}
|
101
Riot/Modules/Room/EmojiPicker/EmojiPickerViewCell.swift
Normal file
101
Riot/Modules/Room/EmojiPicker/EmojiPickerViewCell.swift
Normal 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
|
||||
}
|
||||
|
||||
}
|
55
Riot/Modules/Room/EmojiPicker/EmojiPickerViewCell.xib
Normal file
55
Riot/Modules/Room/EmojiPicker/EmojiPickerViewCell.xib
Normal 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>
|
|
@ -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>
|
319
Riot/Modules/Room/EmojiPicker/EmojiPickerViewController.swift
Normal file
319
Riot/Modules/Room/EmojiPicker/EmojiPickerViewController.swift
Normal 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))
|
||||
}
|
||||
}
|
153
Riot/Modules/Room/EmojiPicker/EmojiPickerViewModel.swift
Normal file
153
Riot/Modules/Room/EmojiPicker/EmojiPickerViewModel.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
38
Riot/Modules/Room/EmojiPicker/EmojiPickerViewModelType.swift
Normal file
38
Riot/Modules/Room/EmojiPicker/EmojiPickerViewModelType.swift
Normal 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)
|
||||
}
|
26
Riot/Modules/Room/EmojiPicker/EmojiPickerViewState.swift
Normal file
26
Riot/Modules/Room/EmojiPicker/EmojiPickerViewState.swift
Normal 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)
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
142
RiotTests/EmojiServiceTests.swift
Normal file
142
RiotTests/EmojiServiceTests.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
5
RiotTests/RiotTests-Bridging-Header.h
Normal file
5
RiotTests/RiotTests-Bridging-Header.h
Normal 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"
|
Loading…
Reference in a new issue