mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Timeline Reactions: Emoji picker (#350)
* Screens template * Loading and parsing * Gridview * New json * Search method * Unit tests * Added emoji provider state * Removed containsIgnoringCase * Added plus icon, added double tap gesture, dismiss sheet after selecting emoji * Renamed EmojisProvider to EmojiProvider * Renamed EmojiPickerScreenScreen * Extra padding * Renamed EmojisLoaderProtocol * Category names * Moved method into public section of the class * Return all categories when search string is empty * Added cancel button and tittle * Removed empty lines * Removed swiftlint warning
This commit is contained in:
parent
dcedb3fb98
commit
7f91852291
@ -29,6 +29,7 @@
|
||||
09AAF04B27732046C755D914 /* SoftLogoutViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */; };
|
||||
0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */; };
|
||||
0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */; };
|
||||
0B4F67EF7AD6264047613E0B /* EmojiProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E31F530ED515C10A5915D1B9 /* EmojiProviderTests.swift */; };
|
||||
0BEFE400B4802FE8C9DB39B3 /* FilePreviewViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62BDF0FF4F59AF6EA858B70B /* FilePreviewViewModel.swift */; };
|
||||
0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53482ECA4B6633961EC224F5 /* ScrollViewAdapter.swift */; };
|
||||
0C38C3E771B472E27295339D /* SessionVerificationModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB9A17AC512A7EF4B106E5 /* SessionVerificationModels.swift */; };
|
||||
@ -61,6 +62,7 @@
|
||||
1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */; };
|
||||
1B4B3E847BF944DB2C1C217F /* BackgroundTaskServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */; };
|
||||
1CF18DE71D5D23C61BD88852 /* DebugScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9238D3A3A00F45E841FE4EFF /* DebugScreen.swift */; };
|
||||
1D69E31913DF66426985909B /* EmojiPickerScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11151E78D6BB2B04A8FBD389 /* EmojiPickerScreenViewModelProtocol.swift */; };
|
||||
1E59B77A0B2CE83DCC1B203C /* LoginViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A05707BF550D770168A406DB /* LoginViewModelTests.swift */; };
|
||||
1F04C63D4FA95948E3F52147 /* FileRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */; };
|
||||
1F3232BD368DF430AB433907 /* DesignKit in Frameworks */ = {isa = PBXBuildFile; productRef = A5A56C4F47C368EBE5C5E870 /* DesignKit */; };
|
||||
@ -86,6 +88,7 @@
|
||||
2B9AEEC12B1BBE5BD61D0F5E /* UserSessionFlowCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3429142FE11930422E7CC1A0 /* UserSessionFlowCoordinatorStateMachine.swift */; };
|
||||
2BA59D0AEFB4B82A2EC2A326 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */; };
|
||||
2BAA5B222856068158D0B3C6 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = B1E8B697DF78FE7F61FC6CA4 /* MatrixRustSDK */; };
|
||||
2C5E832434EE94E21AB3B238 /* EmojiPickerScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3EAE3E9D5EF4A6D5D9C6CFD /* EmojiPickerScreenViewModel.swift */; };
|
||||
2CA8AD07773A38BA4662098B /* MediaProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC3D31C2DA6910AA0079678A /* MediaProxyProtocol.swift */; };
|
||||
2CB6787E25B11711518E9588 /* OnboardingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E6281B199D8A8F0892490C2E /* OnboardingCoordinator.swift */; };
|
||||
2D794361CFE790C8FB3C9C0F /* message.caf in Resources */ = {isa = PBXBuildFile; fileRef = ED482057AE39D5C6D9C5F3D8 /* message.caf */; };
|
||||
@ -142,6 +145,7 @@
|
||||
4E945AD6862C403F74E57755 /* RoomTimelineItemFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 105B2A8426404EF66F00CFDB /* RoomTimelineItemFactory.swift */; };
|
||||
4FC1EFE4968A259CBBACFAFB /* RoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */; };
|
||||
4FF90E2242DBD596E1ED2E27 /* AppCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 077D7C3BE199B6E5DDEC07EC /* AppCoordinatorStateMachine.swift */; };
|
||||
501304F26B52DF7024011B6C /* EmojiMartJSONLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BF9E3E6A23180EC05F06460 /* EmojiMartJSONLoaderTests.swift */; };
|
||||
50C90117FE25390BFBD40173 /* RustTracing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542D4F49FABA056DEEEB3400 /* RustTracing.swift */; };
|
||||
518C93DC6516D3D018DE065F /* UNNotificationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */; };
|
||||
51DB67C5B5BC68B0A6FF54D4 /* MockRoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ACBDC1D28EFB7789EB467E0 /* MockRoomProxy.swift */; };
|
||||
@ -157,11 +161,18 @@
|
||||
5B8B51CEC4717AF487794685 /* NotificationServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B490675B8E31423AF116BDA /* NotificationServiceProxy.swift */; };
|
||||
5C02841B2A86327B2C377682 /* NotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C830A64609CBD152F06E0457 /* NotificationConstants.swift */; };
|
||||
5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */; };
|
||||
5D04B17929378AB300FD5B00 /* apple_emojis_data.json in Resources */ = {isa = PBXBuildFile; fileRef = 5D04B17829378AB300FD5B00 /* apple_emojis_data.json */; };
|
||||
5D04B17B29378D3600FD5B00 /* EmojiMartEmoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D04B17A29378D3600FD5B00 /* EmojiMartEmoji.swift */; };
|
||||
5D04B17D2937ADE300FD5B00 /* EmojiItemSkin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D04B17C2937ADE300FD5B00 /* EmojiItemSkin.swift */; };
|
||||
5D04B17F293A333600FD5B00 /* EmojiPickerSearchFieldView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D04B17E293A333600FD5B00 /* EmojiPickerSearchFieldView.swift */; };
|
||||
5D04B181293A337400FD5B00 /* EmojiPickerHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D04B180293A337400FD5B00 /* EmojiPickerHeaderView.swift */; };
|
||||
5D2AF8C0DF872E7985F8FE54 /* TimelineDeliveryStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */; };
|
||||
5D430CDE11EAC3E8E6B80A66 /* RoomTimelineViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FEE631F3A4AFDC6652DD9DA /* RoomTimelineViewFactory.swift */; };
|
||||
5D70FAE4D2BF4553AFFFFE41 /* NotificationItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */; };
|
||||
5D7960B32C350FA93F48D02B /* OnboardingModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB33A751BFDA223BDD106EC0 /* OnboardingModels.swift */; };
|
||||
5D9F0695DC6C0057F85C12B6 /* UserNotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1113CA0A67B4AA227AAFB63B /* UserNotificationController.swift */; };
|
||||
5DE2282C293F29FC001790FD /* EmojiProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DE2282B293F29FC001790FD /* EmojiProvider.swift */; };
|
||||
5DE2282E293F2CF6001790FD /* EmojiLoaderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DE2282D293F2CF6001790FD /* EmojiLoaderProtocol.swift */; };
|
||||
5E0F2E612718BB4397A6D40A /* TextRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */; };
|
||||
5E25568E1CDAD983517E58B5 /* MediaSourceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179423E34EE846E048E49CBF /* MediaSourceProxy.swift */; };
|
||||
5E540CAEF764D7FBD8D80776 /* VideoPlayerModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A3FC45B7643298BF361CEB1 /* VideoPlayerModels.swift */; };
|
||||
@ -183,6 +194,7 @@
|
||||
67D6E0700A9C1E676F6231F8 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 940C605265DD82DA0C655E23 /* Kingfisher */; };
|
||||
67E391A2E00709FB41903B36 /* MockMediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6920A4869821BF72FFC58842 /* MockMediaProvider.swift */; };
|
||||
6832733838C57A7D3FE8FEB5 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 36B7FC232711031AA2B0D188 /* DTCoreText */; };
|
||||
68998AE39FDE863ED69711F8 /* TimelineItemReactionsMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA4807E660393F385D673DA2 /* TimelineItemReactionsMenuView.swift */; };
|
||||
68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */; };
|
||||
690ED5315B401238A3249DCB /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 3FDFF4C1153D263BAB93C1F3 /* README.md */; };
|
||||
69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */; };
|
||||
@ -203,9 +215,11 @@
|
||||
7096FA3AC218D914E88BFB70 /* AggregratedReaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15BE37BE2FB86E00C8D150A /* AggregratedReaction.swift */; };
|
||||
719E7AAD1F8E68F68F30FECD /* Task.swift in Sources */ = {isa = PBXBuildFile; fileRef = A40C19719687984FD9478FBE /* Task.swift */; };
|
||||
7354D094A4C59B555F407FA1 /* RustTracing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 542D4F49FABA056DEEEB3400 /* RustTracing.swift */; };
|
||||
7361B011A79BF723D8C9782B /* EmojiCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C1A3D524D63815B28FA4D62 /* EmojiCategory.swift */; };
|
||||
7405B4824D45BA7C3D943E76 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0CBC76C80E04345E11F2DB /* Application.swift */; };
|
||||
744C029EB6C43429926A0499 /* AnalyticsPromptViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A86C95340248A8B7BA9A43 /* AnalyticsPromptViewModelProtocol.swift */; };
|
||||
74604ACFDBE7F54260E7B617 /* ApplicationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */; };
|
||||
748F482FEF4E04D61C39AAD7 /* EmojiPickerScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */; };
|
||||
755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */; };
|
||||
758BF44CA565AB0AB84F2185 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7109E709A7738E6BCC4553E6 /* Localizable.strings */; };
|
||||
75EA4ABBFAA810AFF289D6F4 /* TemplateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB6E40BAD4504D899FAAC9A /* TemplateViewModel.swift */; };
|
||||
@ -231,6 +245,7 @@
|
||||
8024BE37156FF0A95A7A3465 /* AnalyticsPromptUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF11DD57D9FACF2A757AB024 /* AnalyticsPromptUITests.swift */; };
|
||||
80D00A7C62AAB44F54725C43 /* PermalinkBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F754E66A8970963B15B2A41E /* PermalinkBuilder.swift */; };
|
||||
8196A2E71ACC902DD69F24EE /* UserNotificationControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DE6C5C756E1393202BA95CD /* UserNotificationControllerTests.swift */; };
|
||||
8292BBE82AD31A393183CF28 /* EmojiPickerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E016C12585FC5A6EA6941B5 /* EmojiPickerScreen.swift */; };
|
||||
834DD9E41FC42A509BAD52E3 /* NavigationControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 109361C96BFFBE2FD89BF15C /* NavigationControllerTests.swift */; };
|
||||
83E5054739949181CA981193 /* LoginCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD667C4BB98CF4F3FE2CE3B0 /* LoginCoordinator.swift */; };
|
||||
841172E1576A863F4450132D /* WeakKeyDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0ADFDC712027931F2216668 /* WeakKeyDictionary.swift */; };
|
||||
@ -255,6 +270,7 @@
|
||||
90EB25D13AE6EEF034BDE9D2 /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71D52BAA5BADB06E5E8C295D /* Assets.swift */; };
|
||||
91DFCB641FBA03EE2DA0189E /* FilePreviewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FB27E1BE894F9F9F0134372 /* FilePreviewScreen.swift */; };
|
||||
9219640F4D980CFC5FE855AD /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 536E72DCBEEC4A1FE66CFDCE /* target.yml */; };
|
||||
92B95779840CD749117B3615 /* EmojiMartStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C38AE3617D7619EF30CDD229 /* EmojiMartStore.swift */; };
|
||||
93875ADD456142D20823ED24 /* ServerSelectionViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAA4472821985BF868CC21C /* ServerSelectionViewModelTests.swift */; };
|
||||
93BA4A81B6D893271101F9F0 /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 531CE4334AC5CA8DFF6AEB84 /* DTCoreText */; };
|
||||
9462C62798F47E39DCC182D2 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA89A2DD51B6BBE1DA55E263 /* Application.swift */; };
|
||||
@ -339,11 +355,13 @@
|
||||
BCEC41FB1F2BB663183863E4 /* LoginServerInfoSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D379E13DD9D987470A3C70C /* LoginServerInfoSection.swift */; };
|
||||
BD782053BE4C3D2F0BDE5699 /* ServiceLocator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57F95CADD0A5DBD76B990FCB /* ServiceLocator.swift */; };
|
||||
BFB534E338A3D949944FB2F5 /* NotificationServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B490675B8E31423AF116BDA /* NotificationServiceProxy.swift */; };
|
||||
C1910A16BDF131FECA77BE22 /* EmojiPickerScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEA38B9851CFCC4D67F5587D /* EmojiPickerScreenCoordinator.swift */; };
|
||||
C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B251F5B4511D1CA0BA8361FE /* CoordinatorProtocol.swift */; };
|
||||
C35CF4DAB1467FE1BBDC204B /* MessageTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAF1C75771D9DC75877F4B4 /* MessageTimelineItem.swift */; };
|
||||
C4180F418235DAD9DD173951 /* TemplateScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9873076F224E4CE09D8BD47D /* TemplateScreenUITests.swift */; };
|
||||
C4F69156C31A447FEFF2A47C /* DTHTMLElement+AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E508AB0EDEE017FF4F6F8D1 /* DTHTMLElement+AttributedStringBuilder.swift */; };
|
||||
C55A44C99F64A479ABA85B46 /* RoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */; };
|
||||
C5FDDC40ABD907B7C47F89AB /* EmojiMartJSONLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3553C7E1218AB3EF08FFEEA4 /* EmojiMartJSONLoader.swift */; };
|
||||
C74EE50257ED925C2B8EFCE6 /* MockSoftLogoutScreenState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B869438A1B52836F912A702 /* MockSoftLogoutScreenState.swift */; };
|
||||
C76892321558E75101E68ED6 /* ReadableFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */; };
|
||||
C7B251DC896C0867C51B616D /* AnalyticsPrompt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 541542F5AC323709D8563458 /* AnalyticsPrompt.swift */; };
|
||||
@ -369,6 +387,7 @@
|
||||
D0619D2E6B9C511190FBEB95 /* RoomMessageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607974D08BD2AF83725D817A /* RoomMessageProtocol.swift */; };
|
||||
D2D70B5DB1A5E4AF0CD88330 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 033DB41C51865A2E83174E87 /* target.yml */; };
|
||||
D3E603A5E9D529CF293E1BF9 /* VideoPlayerCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1651A532305027D3F605E2B /* VideoPlayerCoordinator.swift */; };
|
||||
D5C805F49B2C75DC3793E780 /* EmojiItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A243E04B58DC6E41FDCD82 /* EmojiItem.swift */; };
|
||||
D5EA4C6C80579279770D5804 /* ImageRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */; };
|
||||
D6417E5A799C3C7F14F9EC0A /* SessionVerificationViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3069ADED46D063202FE7698 /* SessionVerificationViewModelProtocol.swift */; };
|
||||
D79F0F852C6A4255D5E616D2 /* UserNotificationControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ED2D2F6A137A95EA50413BE /* UserNotificationControllerProtocol.swift */; };
|
||||
@ -403,6 +422,7 @@
|
||||
EC280623A42904341363EAAF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 886A0A498FA01E8EDD451D05 /* Sentry */; };
|
||||
EC4C31963E755EEC77BD778C /* AnalyticsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B362E695A7103C11F64B185 /* AnalyticsSettings.swift */; };
|
||||
EE4F5601356228FF72FC56B6 /* MockClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F40F48279322E504153AB0D /* MockClientProxy.swift */; };
|
||||
EE6933C935080B4E0348A58B /* EmojiMartCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5C3AACCAA82392D08924496 /* EmojiMartCategory.swift */; };
|
||||
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 184CF8C196BE143AE226628D /* DecorationTimelineItemProtocol.swift */; };
|
||||
EEC40663922856C65D1E0DF5 /* KeychainControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */; };
|
||||
EF7924005216B8189898F370 /* BackgroundTaskProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CA028DCD4157F9A1F999827 /* BackgroundTaskProtocol.swift */; };
|
||||
@ -503,6 +523,7 @@
|
||||
0DB634B42CFE667112369D57 /* VideoPlayerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerScreen.swift; sourceTree = "<group>"; };
|
||||
0DD16CE9A66C9040B066AD60 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = vi; path = vi.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
0DE6C5C756E1393202BA95CD /* UserNotificationControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationControllerTests.swift; sourceTree = "<group>"; };
|
||||
0E016C12585FC5A6EA6941B5 /* EmojiPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreen.swift; sourceTree = "<group>"; };
|
||||
0E7062F88E9D5F79C8A80524 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = th; path = th.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
0E8BDC092D817B68CD9040C5 /* UserSessionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStore.swift; sourceTree = "<group>"; };
|
||||
0EE9EAF0309A2A1D67D8FAF5 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sv; path = sv.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
@ -514,6 +535,7 @@
|
||||
109361C96BFFBE2FD89BF15C /* NavigationControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationControllerTests.swift; sourceTree = "<group>"; };
|
||||
10CC626F97AD70FF0420C115 /* RoomSummaryProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProviderProtocol.swift; sourceTree = "<group>"; };
|
||||
1113CA0A67B4AA227AAFB63B /* UserNotificationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationController.swift; sourceTree = "<group>"; };
|
||||
11151E78D6BB2B04A8FBD389 /* EmojiPickerScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
111B698739E3410E2CDB7144 /* MXLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXLog.swift; sourceTree = "<group>"; };
|
||||
113356152C099951A6D17D85 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
1215A4FC53D2319E81AE8970 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
@ -586,15 +608,18 @@
|
||||
33E49C5C6F802B4D94CA78D1 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3429142FE11930422E7CC1A0 /* UserSessionFlowCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionFlowCoordinatorStateMachine.swift; sourceTree = "<group>"; };
|
||||
351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReactionsView.swift; sourceTree = "<group>"; };
|
||||
3553C7E1218AB3EF08FFEEA4 /* EmojiMartJSONLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartJSONLoader.swift; sourceTree = "<group>"; };
|
||||
3558A15CFB934F9229301527 /* RestorationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationToken.swift; sourceTree = "<group>"; };
|
||||
35AFCF4C05DEED04E3DB1A16 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3747C96188856006F784BF49 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ko; path = ko.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
3782C506F4FF1AADF61B6212 /* tlh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tlh; path = tlh.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
37A243E04B58DC6E41FDCD82 /* EmojiItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItem.swift; sourceTree = "<group>"; };
|
||||
398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadableFrameModifier.swift; sourceTree = "<group>"; };
|
||||
399427358A80BA2848E698A2 /* es-MX */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-MX"; path = "es-MX.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
39EBB6903EFD4236B8D11A42 /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "fr-CA"; path = "fr-CA.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
3ACBDC1D28EFB7789EB467E0 /* MockRoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomProxy.swift; sourceTree = "<group>"; };
|
||||
3B5B535DA49C54523FF7A412 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nn; path = nn.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3C1A3D524D63815B28FA4D62 /* EmojiCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiCategory.swift; sourceTree = "<group>"; };
|
||||
3CDF9E55650D6035D6536538 /* nb-NO */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "nb-NO"; path = "nb-NO.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
3D487C1185D658F8B15B8F55 /* SettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModelTests.swift; sourceTree = "<group>"; };
|
||||
3D4DD336905C72F95EAF34B7 /* ElementX-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ElementX-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
@ -662,8 +687,15 @@
|
||||
5773C86AF04AEF26515AD00C /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
57F95CADD0A5DBD76B990FCB /* ServiceLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceLocator.swift; sourceTree = "<group>"; };
|
||||
5B2F9D5C39A4494D19F33E38 /* SettingsViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
5D04B17829378AB300FD5B00 /* apple_emojis_data.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = apple_emojis_data.json; sourceTree = "<group>"; };
|
||||
5D04B17A29378D3600FD5B00 /* EmojiMartEmoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartEmoji.swift; sourceTree = "<group>"; };
|
||||
5D04B17C2937ADE300FD5B00 /* EmojiItemSkin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiItemSkin.swift; sourceTree = "<group>"; };
|
||||
5D04B17E293A333600FD5B00 /* EmojiPickerSearchFieldView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerSearchFieldView.swift; sourceTree = "<group>"; };
|
||||
5D04B180293A337400FD5B00 /* EmojiPickerHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerHeaderView.swift; sourceTree = "<group>"; };
|
||||
5D26A086A8278D39B5756D6F /* project.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = project.yml; sourceTree = "<group>"; };
|
||||
5D2D0A6F1ABC99D29462FB84 /* AuthenticationCoordinatorUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinatorUITests.swift; sourceTree = "<group>"; };
|
||||
5DE2282B293F29FC001790FD /* EmojiProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiProvider.swift; sourceTree = "<group>"; };
|
||||
5DE2282D293F2CF6001790FD /* EmojiLoaderProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmojiLoaderProtocol.swift; sourceTree = "<group>"; };
|
||||
5F12E996BFBEB43815189ABF /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = "<group>"; };
|
||||
5FF214969B25BFCBF87B908B /* bn-BD */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "bn-BD"; path = "bn-BD.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
@ -762,6 +794,7 @@
|
||||
9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceProtocol.swift; sourceTree = "<group>"; };
|
||||
9B1FBF8CA40199B8058B1F08 /* NotificationItemProxy+NSE.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationItemProxy+NSE.swift"; sourceTree = "<group>"; };
|
||||
9B577F829C693B8DFB7014FD /* RedactedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedactedRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
9BF9E3E6A23180EC05F06460 /* EmojiMartJSONLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartJSONLoaderTests.swift; sourceTree = "<group>"; };
|
||||
9C4048041C1A6B20CB97FD18 /* TestMeasurementParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestMeasurementParser.swift; sourceTree = "<group>"; };
|
||||
9C5E81214D27A6B898FC397D /* ElementX.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ElementX.entitlements; sourceTree = "<group>"; };
|
||||
9C7F7DE62D33C6A26CBFCD72 /* IntegrationTests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = IntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@ -834,6 +867,7 @@
|
||||
BB33A751BFDA223BDD106EC0 /* OnboardingModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingModels.swift; sourceTree = "<group>"; };
|
||||
BC9B05D6B293A039EB963CA7 /* az */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = az; path = az.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposerTextField.swift; sourceTree = "<group>"; };
|
||||
BEA38B9851CFCC4D67F5587D /* EmojiPickerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
BEBA759D1347CFFB3D84ED1F /* UserSessionStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStoreProtocol.swift; sourceTree = "<group>"; };
|
||||
BEE6BF9BA63FF42F8AF6EEEA /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sr; path = sr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
C00A7110B937C6AE2EF5D7D6 /* FileRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
@ -842,6 +876,7 @@
|
||||
C070FD43DC6BF4E50217965A /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = "<group>"; };
|
||||
C1198B925F4A88DA74083662 /* OnboardingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingViewModel.swift; sourceTree = "<group>"; };
|
||||
C2886615BEBAE33A0AA4D5F8 /* RoomScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenModels.swift; sourceTree = "<group>"; };
|
||||
C38AE3617D7619EF30CDD229 /* EmojiMartStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartStore.swift; sourceTree = "<group>"; };
|
||||
C3F652E88106B855A2A55ADE /* FilePreviewViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
C483956FA3D665E3842E319A /* SettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreen.swift; sourceTree = "<group>"; };
|
||||
C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxy.swift; sourceTree = "<group>"; };
|
||||
@ -906,6 +941,7 @@
|
||||
E18CF12478983A5EB390FB26 /* MessageComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposer.swift; sourceTree = "<group>"; };
|
||||
E24B88AD3D1599E8CB1376E0 /* AvatarSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarSize.swift; sourceTree = "<group>"; };
|
||||
E26747B3154A5DBC3A7E24A5 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = "<group>"; };
|
||||
E31F530ED515C10A5915D1B9 /* EmojiProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiProviderTests.swift; sourceTree = "<group>"; };
|
||||
E36CB905A2B9EC2C92A2DA7C /* KeychainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainController.swift; sourceTree = "<group>"; };
|
||||
E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsClientProtocol.swift; sourceTree = "<group>"; };
|
||||
E3E29F98CF0E960689A410E3 /* SettingsUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsUITests.swift; sourceTree = "<group>"; };
|
||||
@ -914,6 +950,7 @@
|
||||
E51E3D86A84341C3A0CB8A40 /* FileRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurHashDecode.swift; sourceTree = "<group>"; };
|
||||
E579A0DA01F488C97B771EF6 /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = lv; path = lv.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
E5C3AACCAA82392D08924496 /* EmojiMartCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiMartCategory.swift; sourceTree = "<group>"; };
|
||||
E5D2C0950F8196232D88045C /* ServerSelectionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreen.swift; sourceTree = "<group>"; };
|
||||
E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainControllerProtocol.swift; sourceTree = "<group>"; };
|
||||
E5F2B6443D1ED8602F328539 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
@ -938,11 +975,13 @@
|
||||
F03C9D319676F3C0DC6B0203 /* ScreenshotDetectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotDetectorTests.swift; sourceTree = "<group>"; };
|
||||
F0E7BF8F7BB1021F889C6483 /* MockBugReportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBugReportService.swift; sourceTree = "<group>"; };
|
||||
F15BE37BE2FB86E00C8D150A /* AggregratedReaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AggregratedReaction.swift; sourceTree = "<group>"; };
|
||||
F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = "<group>"; };
|
||||
F23BA6D4842D53C5AC9B7584 /* nn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nn; path = nn.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
F2D58333B377888012740101 /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = "<group>"; };
|
||||
F31A4E5941ACBA4BB9FEF94C /* UserNotificationToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationToastView.swift; sourceTree = "<group>"; };
|
||||
F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = "<group>"; };
|
||||
F3648F2FADEF2672D6A0D489 /* FileCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCacheTests.swift; sourceTree = "<group>"; };
|
||||
F3EAE3E9D5EF4A6D5D9C6CFD /* EmojiPickerScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
F506C6ADB1E1DA6638078E11 /* UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
F5C4AF6E3885730CD560311C /* ScreenshotDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotDetector.swift; sourceTree = "<group>"; };
|
||||
F6A8C632CEF4600107792899 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
@ -951,6 +990,7 @@
|
||||
F77C060C2ACC4CB7336A29E7 /* EmoteRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmoteRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
FA154570F693D93513E584C1 /* RoomMessageFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageFactory.swift; sourceTree = "<group>"; };
|
||||
FA4807E660393F385D673DA2 /* TimelineItemReactionsMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemReactionsMenuView.swift; sourceTree = "<group>"; };
|
||||
FAB10E673916D2B8D21FD197 /* TemplateModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateModels.swift; sourceTree = "<group>"; };
|
||||
FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorProtocol.swift; sourceTree = "<group>"; };
|
||||
FC3D31C2DA6910AA0079678A /* MediaProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProxyProtocol.swift; sourceTree = "<group>"; };
|
||||
@ -1059,6 +1099,7 @@
|
||||
0ED3F5C21537519389C07644 /* BugReport */,
|
||||
2D6DC9871FD7173E51D67C73 /* Cache */,
|
||||
8039515BAA53B7C3275AC64A /* Client */,
|
||||
39557ADF21345E18F3865B9E /* Emojis */,
|
||||
CA555F7C7CA382ACACF0D82B /* Keychain */,
|
||||
79E560F5113ED25D172E550C /* Media */,
|
||||
6DE13A7AE6587B079F4049D7 /* Notification */,
|
||||
@ -1183,6 +1224,16 @@
|
||||
path = UITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
323160803A296713F839540B /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0E016C12585FC5A6EA6941B5 /* EmojiPickerScreen.swift */,
|
||||
5D04B17E293A333600FD5B00 /* EmojiPickerSearchFieldView.swift */,
|
||||
5D04B180293A337400FD5B00 /* EmojiPickerHeaderView.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
328DD5DA1281F758B72006C7 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1215,6 +1266,19 @@
|
||||
path = ServerSelection;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
39557ADF21345E18F3865B9E /* Emojis */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3C1A3D524D63815B28FA4D62 /* EmojiCategory.swift */,
|
||||
37A243E04B58DC6E41FDCD82 /* EmojiItem.swift */,
|
||||
5DE2282D293F2CF6001790FD /* EmojiLoaderProtocol.swift */,
|
||||
5DE2282B293F29FC001790FD /* EmojiProvider.swift */,
|
||||
5BACB442D02C878293C04837 /* EmojiMart */,
|
||||
5D04B17C2937ADE300FD5B00 /* EmojiItemSkin.swift */,
|
||||
);
|
||||
path = Emojis;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3A304097A59704AC9B869EC6 /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1438,6 +1502,18 @@
|
||||
path = TimeLineItemContent;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5BACB442D02C878293C04837 /* EmojiMart */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3553C7E1218AB3EF08FFEEA4 /* EmojiMartJSONLoader.swift */,
|
||||
5D04B17829378AB300FD5B00 /* apple_emojis_data.json */,
|
||||
E5C3AACCAA82392D08924496 /* EmojiMartCategory.swift */,
|
||||
C38AE3617D7619EF30CDD229 /* EmojiMartStore.swift */,
|
||||
5D04B17A29378D3600FD5B00 /* EmojiMartEmoji.swift */,
|
||||
);
|
||||
path = EmojiMart;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5E01022071DDDC48EF453374 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1548,6 +1624,8 @@
|
||||
EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */,
|
||||
7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */,
|
||||
DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */,
|
||||
9BF9E3E6A23180EC05F06460 /* EmojiMartJSONLoaderTests.swift */,
|
||||
E31F530ED515C10A5915D1B9 /* EmojiProviderTests.swift */,
|
||||
F3648F2FADEF2672D6A0D489 /* FileCacheTests.swift */,
|
||||
DF38B69D2C331A499276F400 /* FilePreviewViewModelTests.swift */,
|
||||
505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */,
|
||||
@ -2019,6 +2097,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D5AC06FC11B6638F7BF1670E /* TimelineDeliveryStatusView.swift */,
|
||||
FA4807E660393F385D673DA2 /* TimelineItemReactionsMenuView.swift */,
|
||||
351E89CE2ED9B73C5CC47955 /* TimelineReactionsView.swift */,
|
||||
);
|
||||
path = Supplementary;
|
||||
@ -2112,7 +2191,9 @@
|
||||
E0EEBB2F7AA1BB36FC08F606 /* AnalyticsPrompt */,
|
||||
E74CD7681375AD2EAA34D66B /* Authentication */,
|
||||
4009BE2E791C16AC6EE39A7E /* BugReport */,
|
||||
F5A65D1D3B83593598DC278D /* EmojiPickerScreen */,
|
||||
B442FCF47E0A6F28D7D50A4D /* FilePreview */,
|
||||
FC26FB522EDED4965C5325F0 /* Folder */,
|
||||
B53CA9BECD3F97805E1432D0 /* HomeScreen */,
|
||||
3F38EAC92E2281990E65DAF2 /* OnboardingScreen */,
|
||||
A448A3A8F764174C60CD0CA1 /* Other */,
|
||||
@ -2171,6 +2252,25 @@
|
||||
path = Background;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F5A65D1D3B83593598DC278D /* EmojiPickerScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BEA38B9851CFCC4D67F5587D /* EmojiPickerScreenCoordinator.swift */,
|
||||
F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */,
|
||||
F3EAE3E9D5EF4A6D5D9C6CFD /* EmojiPickerScreenViewModel.swift */,
|
||||
11151E78D6BB2B04A8FBD389 /* EmojiPickerScreenViewModelProtocol.swift */,
|
||||
323160803A296713F839540B /* View */,
|
||||
);
|
||||
path = EmojiPickerScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FC26FB522EDED4965C5325F0 /* Folder */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
path = Folder;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FCDF06BDB123505F0334B4F9 /* Timeline */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2475,6 +2575,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B80C4FABB5529DF12436FFDA /* AppIcon.pdf in Resources */,
|
||||
5D04B17929378AB300FD5B00 /* apple_emojis_data.json in Resources */,
|
||||
992F5E750F5030C4BA2D0D03 /* Assets.xcassets in Resources */,
|
||||
B6DA66EFC13A90846B625836 /* InfoPlist.strings in Resources */,
|
||||
AB34401E4E1CAD5D2EC3072B /* LaunchScreen.storyboard in Resources */,
|
||||
@ -2641,6 +2742,8 @@
|
||||
7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */,
|
||||
C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */,
|
||||
9C45CE85325CD591DADBC4CA /* ElementXTests.swift in Sources */,
|
||||
501304F26B52DF7024011B6C /* EmojiMartJSONLoaderTests.swift in Sources */,
|
||||
0B4F67EF7AD6264047613E0B /* EmojiProviderTests.swift in Sources */,
|
||||
7E7DF1867F98B0D10A6C0A63 /* FileCacheTests.swift in Sources */,
|
||||
CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */,
|
||||
F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */,
|
||||
@ -2707,6 +2810,7 @@
|
||||
6D046D653DA28ADF1E6E59A4 /* BackgroundTaskServiceProtocol.swift in Sources */,
|
||||
CB326BAB54E9B68658909E36 /* Benchmark.swift in Sources */,
|
||||
38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */,
|
||||
5DE2282E293F2CF6001790FD /* EmojiLoaderProtocol.swift in Sources */,
|
||||
B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */,
|
||||
A32517FB1CA0BBCE2BC75249 /* BugReportCoordinator.swift in Sources */,
|
||||
00F3059B1E0CFCA019710C3E /* BugReportModels.swift in Sources */,
|
||||
@ -2726,9 +2830,21 @@
|
||||
1CF18DE71D5D23C61BD88852 /* DebugScreen.swift in Sources */,
|
||||
EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */,
|
||||
FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */,
|
||||
5D04B181293A337400FD5B00 /* EmojiPickerHeaderView.swift in Sources */,
|
||||
9738F894DB1BD383BE05767A /* ElementSettings.swift in Sources */,
|
||||
D8CFF02C2730EE5BC4F17ABF /* ElementToggleStyle.swift in Sources */,
|
||||
7C1A7B594B2F8143F0DD0005 /* ElementXAttributeScope.swift in Sources */,
|
||||
7361B011A79BF723D8C9782B /* EmojiCategory.swift in Sources */,
|
||||
D5C805F49B2C75DC3793E780 /* EmojiItem.swift in Sources */,
|
||||
EE6933C935080B4E0348A58B /* EmojiMartCategory.swift in Sources */,
|
||||
C5FDDC40ABD907B7C47F89AB /* EmojiMartJSONLoader.swift in Sources */,
|
||||
92B95779840CD749117B3615 /* EmojiMartStore.swift in Sources */,
|
||||
5D04B17B29378D3600FD5B00 /* EmojiMartEmoji.swift in Sources */,
|
||||
C1910A16BDF131FECA77BE22 /* EmojiPickerScreenCoordinator.swift in Sources */,
|
||||
748F482FEF4E04D61C39AAD7 /* EmojiPickerScreenModels.swift in Sources */,
|
||||
8292BBE82AD31A393183CF28 /* EmojiPickerScreen.swift in Sources */,
|
||||
2C5E832434EE94E21AB3B238 /* EmojiPickerScreenViewModel.swift in Sources */,
|
||||
1D69E31913DF66426985909B /* EmojiPickerScreenViewModelProtocol.swift in Sources */,
|
||||
6647430A45B4A8E692909A8F /* EmoteRoomTimelineItem.swift in Sources */,
|
||||
68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */,
|
||||
F6E860FF7B18B81DF43B30B8 /* EncryptedRoomTimelineItem.swift in Sources */,
|
||||
@ -2811,6 +2927,7 @@
|
||||
14132418A748C988B85B025E /* OnboardingPageIndicator.swift in Sources */,
|
||||
F257F964493A9CD02A6F720C /* OnboardingPageView.swift in Sources */,
|
||||
7F64FA937B95924B3A44EC12 /* OnboardingScreen.swift in Sources */,
|
||||
5D04B17F293A333600FD5B00 /* EmojiPickerSearchFieldView.swift in Sources */,
|
||||
CE7148E80F09B7305E026AC6 /* OnboardingViewModel.swift in Sources */,
|
||||
992477AB8E3F3C36D627D32E /* OnboardingViewModelProtocol.swift in Sources */,
|
||||
CD6A72B65D3B6076F4045C30 /* PHGPostHogConfiguration.swift in Sources */,
|
||||
@ -2856,6 +2973,7 @@
|
||||
6AC1DC1EAD9F7568360DA1BA /* ServerSelectionModels.swift in Sources */,
|
||||
388FD50AC66E9E684DDFA9D8 /* ServerSelectionScreen.swift in Sources */,
|
||||
BB01CC19C3D3322308D1B2CF /* ServerSelectionViewModel.swift in Sources */,
|
||||
5DE2282C293F29FC001790FD /* EmojiProvider.swift in Sources */,
|
||||
19839F3526CE8C35AAF241AD /* ServerSelectionViewModelProtocol.swift in Sources */,
|
||||
BD782053BE4C3D2F0BDE5699 /* ServiceLocator.swift in Sources */,
|
||||
237FC70AA257B935F53316BA /* SessionVerificationControllerProxy.swift in Sources */,
|
||||
@ -2883,6 +3001,7 @@
|
||||
A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */,
|
||||
066A1E9B94723EE9F3038044 /* Strings.swift in Sources */,
|
||||
44AE0752E001D1D10605CD88 /* Swipe.swift in Sources */,
|
||||
5D04B17D2937ADE300FD5B00 /* EmojiItemSkin.swift in Sources */,
|
||||
E290C78E7F09F47FD2662986 /* Task.swift in Sources */,
|
||||
43FD77998F33C32718C51450 /* TemplateCoordinator.swift in Sources */,
|
||||
63C9AF0FB8278AF1C0388A0C /* TemplateModels.swift in Sources */,
|
||||
@ -2895,6 +3014,7 @@
|
||||
5D2AF8C0DF872E7985F8FE54 /* TimelineDeliveryStatusView.swift in Sources */,
|
||||
157E5FDDF419C0B2CA7E2C28 /* TimelineItemBubbledStylerView.swift in Sources */,
|
||||
01CB8ACFA5E143E89C168CA8 /* TimelineItemContextMenu.swift in Sources */,
|
||||
68998AE39FDE863ED69711F8 /* TimelineItemReactionsMenuView.swift in Sources */,
|
||||
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */,
|
||||
440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */,
|
||||
9B582B3EEFEA615D4A6FBF1A /* TimelineReactionsView.swift in Sources */,
|
||||
|
@ -2347,3 +2347,11 @@
|
||||
"onboarding_new_app_layout_feedback_title" = "Give Feedback";
|
||||
"onboarding_new_app_layout_feedback_message" = "Tap top right to see the option to feedback.";
|
||||
"onboarding_new_app_layout_button_try" = "Try it out";
|
||||
"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";
|
||||
|
@ -1239,6 +1239,22 @@ public enum ElementL10n {
|
||||
public static var editPollTitle: String { return ElementL10n.tr("Localizable", "edit_poll_title") }
|
||||
/// (edited)
|
||||
public static var editedSuffix: String { return ElementL10n.tr("Localizable", "edited_suffix") }
|
||||
/// Activities
|
||||
public static var emojiPickerActivityCategory: String { return ElementL10n.tr("Localizable", "emoji_picker_activity_category") }
|
||||
/// Flags
|
||||
public static var emojiPickerFlagsCategory: String { return ElementL10n.tr("Localizable", "emoji_picker_flags_category") }
|
||||
/// Food & Drink
|
||||
public static var emojiPickerFoodsCategory: String { return ElementL10n.tr("Localizable", "emoji_picker_foods_category") }
|
||||
/// Animals & Nature
|
||||
public static var emojiPickerNatureCategory: String { return ElementL10n.tr("Localizable", "emoji_picker_nature_category") }
|
||||
/// Objects
|
||||
public static var emojiPickerObjectsCategory: String { return ElementL10n.tr("Localizable", "emoji_picker_objects_category") }
|
||||
/// Smileys & People
|
||||
public static var emojiPickerPeopleCategory: String { return ElementL10n.tr("Localizable", "emoji_picker_people_category") }
|
||||
/// Travel & Places
|
||||
public static var emojiPickerPlacesCategory: String { return ElementL10n.tr("Localizable", "emoji_picker_places_category") }
|
||||
/// Symbols
|
||||
public static var emojiPickerSymbolsCategory: String { return ElementL10n.tr("Localizable", "emoji_picker_symbols_category") }
|
||||
/// Your contact book is empty
|
||||
public static var emptyContactBook: String { return ElementL10n.tr("Localizable", "empty_contact_book") }
|
||||
/// Encrypted message
|
||||
|
@ -0,0 +1,55 @@
|
||||
//
|
||||
// Copyright 2022 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 SwiftUI
|
||||
|
||||
struct EmojiPickerScreenCoordinatorParameters {
|
||||
let emojiProvider: EmojiProviderProtocol
|
||||
let itemId: String
|
||||
}
|
||||
|
||||
enum EmojiPickerScreenCoordinatorAction {
|
||||
case selectEmoji(emojiId: String, itemId: String)
|
||||
}
|
||||
|
||||
final class EmojiPickerScreenCoordinator: CoordinatorProtocol {
|
||||
private let parameters: EmojiPickerScreenCoordinatorParameters
|
||||
private var viewModel: EmojiPickerScreenViewModelProtocol
|
||||
|
||||
var callback: ((EmojiPickerScreenCoordinatorAction) -> Void)?
|
||||
|
||||
init(parameters: EmojiPickerScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
viewModel = EmojiPickerScreenViewModel(emojiProvider: parameters.emojiProvider)
|
||||
}
|
||||
|
||||
func start() {
|
||||
viewModel.callback = { [weak self] action in
|
||||
guard let self else { return }
|
||||
MXLog.debug("EmojiPickerScreenViewModel did complete with result: \(action).")
|
||||
switch action {
|
||||
case let .selectEmoji(emojiId: emojiId):
|
||||
self.callback?(.selectEmoji(emojiId: emojiId, itemId: self.parameters.itemId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(EmojiPickerScreen(context: viewModel.context)
|
||||
.presentationDetents([.medium, .large]))
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
//
|
||||
// Copyright 2022 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 EmojiPickerScreenViewModelAction {
|
||||
case selectEmoji(emojiId: String)
|
||||
}
|
||||
|
||||
struct EmojiPickerScreenViewState: BindableState {
|
||||
var categories: [EmojiPickerEmojiCategoryViewData]
|
||||
}
|
||||
|
||||
enum EmojiPickerScreenViewAction {
|
||||
case search(searchString: String)
|
||||
case emojiSelected(emoji: EmojiPickerEmojiViewData)
|
||||
}
|
||||
|
||||
struct EmojiPickerEmojiCategoryViewData: Identifiable {
|
||||
let id: String
|
||||
let emojis: [EmojiPickerEmojiViewData]
|
||||
|
||||
var name: String {
|
||||
switch id {
|
||||
case "people":
|
||||
return ElementL10n.emojiPickerPeopleCategory
|
||||
case "nature":
|
||||
return ElementL10n.emojiPickerNatureCategory
|
||||
case "foods":
|
||||
return ElementL10n.emojiPickerFoodsCategory
|
||||
case "activity":
|
||||
return ElementL10n.emojiPickerActivityCategory
|
||||
case "places":
|
||||
return ElementL10n.emojiPickerPlacesCategory
|
||||
case "objects":
|
||||
return ElementL10n.emojiPickerObjectsCategory
|
||||
case "symbols":
|
||||
return ElementL10n.emojiPickerSymbolsCategory
|
||||
case "flags":
|
||||
return ElementL10n.emojiPickerFlagsCategory
|
||||
default:
|
||||
MXLog.failure("Missing translation for emoji category with id \(id)")
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EmojiPickerEmojiViewData: Identifiable {
|
||||
var id: String
|
||||
let value: String
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
//
|
||||
// Copyright 2022 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 SwiftUI
|
||||
|
||||
typealias EmojiPickerScreenViewModelType = StateStoreViewModel<EmojiPickerScreenViewState, EmojiPickerScreenViewAction>
|
||||
|
||||
class EmojiPickerScreenViewModel: EmojiPickerScreenViewModelType, EmojiPickerScreenViewModelProtocol {
|
||||
var callback: ((EmojiPickerScreenViewModelAction) -> Void)?
|
||||
|
||||
private let emojiProvider: EmojiProviderProtocol
|
||||
|
||||
init(emojiProvider: EmojiProviderProtocol) {
|
||||
let initialViewState = EmojiPickerScreenViewState(categories: [])
|
||||
self.emojiProvider = emojiProvider
|
||||
super.init(initialViewState: initialViewState)
|
||||
loadEmojis()
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: EmojiPickerScreenViewAction) async {
|
||||
switch viewAction {
|
||||
case let .search(searchString: searchString):
|
||||
let categories = await emojiProvider.getCategories(searchString: searchString)
|
||||
state.categories = convert(emojiCategories: categories)
|
||||
case let .emojiSelected(emoji: emoji):
|
||||
callback?(.selectEmoji(emojiId: emoji.id))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func loadEmojis() {
|
||||
Task(priority: .userInitiated) { [weak self] in
|
||||
let categories = await emojiProvider.getCategories(searchString: nil)
|
||||
self?.state.categories = convert(emojiCategories: categories)
|
||||
}
|
||||
}
|
||||
|
||||
private func convert(emojiCategories: [EmojiCategory]) -> [EmojiPickerEmojiCategoryViewData] {
|
||||
emojiCategories.compactMap { emojiCategory in
|
||||
|
||||
let emojisViewData: [EmojiPickerEmojiViewData] = emojiCategory.emojis.compactMap { emojiItem in
|
||||
|
||||
guard let firstSkin = emojiItem.skins.first else {
|
||||
return nil
|
||||
}
|
||||
return EmojiPickerEmojiViewData(id: emojiItem.id, value: firstSkin.value)
|
||||
}
|
||||
|
||||
return EmojiPickerEmojiCategoryViewData(id: emojiCategory.id, emojis: emojisViewData)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
//
|
||||
// Copyright 2022 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
|
||||
|
||||
@MainActor
|
||||
protocol EmojiPickerScreenViewModelProtocol {
|
||||
var callback: ((EmojiPickerScreenViewModelAction) -> Void)? { get set }
|
||||
var context: EmojiPickerScreenViewModelType.Context { get }
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
//
|
||||
// Copyright 2022 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 SwiftUI
|
||||
|
||||
struct EmojiPickerHeaderView: View {
|
||||
let title: String
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Text(title)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EmojiPickerHeaderView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
Group {
|
||||
EmojiPickerHeaderView(title: "")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
//
|
||||
// Copyright 2022 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 SwiftUI
|
||||
|
||||
struct EmojiPickerScreen: View {
|
||||
@ObservedObject var context: EmojiPickerScreenViewModel.Context
|
||||
@State var searchString = ""
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text(ElementL10n.reactions)
|
||||
.padding(.top, 20)
|
||||
EmojiPickerSearchFieldView(searchString: $searchString)
|
||||
.padding(.horizontal, 10)
|
||||
ScrollView {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 45))], spacing: 3) {
|
||||
ForEach(context.viewState.categories) { category in
|
||||
Section(header: EmojiPickerHeaderView(title: category.name)
|
||||
.padding(.horizontal, 13)
|
||||
.padding(.top, 10)) {
|
||||
ForEach(category.emojis) { emoji in
|
||||
Text(emoji.value)
|
||||
.frame(width: 45, height: 45)
|
||||
.onTapGesture {
|
||||
context.send(viewAction: .emojiSelected(emoji: emoji))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: searchString) { _ in
|
||||
context.send(viewAction: .search(searchString: searchString))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct EmojiPickerScreen_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
EmojiPickerScreen(context: EmojiPickerScreenViewModel(emojiProvider: EmojiProvider()).context)
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
//
|
||||
// Copyright 2022 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 SwiftUI
|
||||
|
||||
struct EmojiPickerSearchFieldView: View {
|
||||
@Binding var searchString: String
|
||||
@FocusState private var isSearchFocused: Bool
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Image(systemName: "magnifyingglass")
|
||||
TextField(ElementL10n.search, text: $searchString)
|
||||
.focused($isSearchFocused)
|
||||
if isSearchFocused {
|
||||
Spacer()
|
||||
Button {
|
||||
searchString = ""
|
||||
isSearchFocused = false
|
||||
} label: {
|
||||
Text(ElementL10n.actionCancel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EmojiPickerSearchFieldView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
EmojiPickerSearchFieldView(searchString: .constant(""))
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ struct RoomScreenCoordinatorParameters {
|
||||
let mediaProvider: MediaProviderProtocol
|
||||
let roomName: String?
|
||||
let roomAvatarUrl: String?
|
||||
let emojiProvide: EmojiProviderProtocol
|
||||
}
|
||||
|
||||
final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
@ -57,6 +58,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
self.displayVideo(for: videoURL)
|
||||
case .displayFile(let fileURL, let title):
|
||||
self.displayFile(for: fileURL, with: title)
|
||||
case .displayEmojiPicker(let itemId):
|
||||
self.displayEmojiPickerScreen(for: itemId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,4 +111,22 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
|
||||
navigationController.push(coordinator)
|
||||
}
|
||||
|
||||
private func displayEmojiPickerScreen(for itemId: String) {
|
||||
guard let emojiProvider = parameters?.emojiProvide else {
|
||||
fatalError()
|
||||
}
|
||||
let params = EmojiPickerScreenCoordinatorParameters(emojiProvider: emojiProvider,
|
||||
itemId: itemId)
|
||||
let coordinator = EmojiPickerScreenCoordinator(parameters: params)
|
||||
coordinator.callback = { [weak self] action in
|
||||
switch action {
|
||||
case let .selectEmoji(emojiId: emojiId, itemId: itemId):
|
||||
self?.navigationController.dismissSheet()
|
||||
MXLog.debug("Save \(emojiId) for \(itemId)")
|
||||
}
|
||||
}
|
||||
|
||||
navigationController.presentSheet(coordinator)
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import UIKit
|
||||
enum RoomScreenViewModelAction {
|
||||
case displayVideo(videoURL: URL)
|
||||
case displayFile(fileURL: URL, title: String?)
|
||||
case displayEmojiPicker(itemId: String)
|
||||
}
|
||||
|
||||
enum RoomScreenComposerMode: Equatable {
|
||||
@ -29,6 +30,7 @@ enum RoomScreenComposerMode: Equatable {
|
||||
}
|
||||
|
||||
enum RoomScreenViewAction {
|
||||
case displayEmojiPicker(itemId: String)
|
||||
case paginateBackwards
|
||||
case itemAppeared(id: String)
|
||||
case itemDisappeared(id: String)
|
||||
@ -38,6 +40,7 @@ enum RoomScreenViewAction {
|
||||
case sendReaction(key: String, eventID: String)
|
||||
case cancelReply
|
||||
case cancelEdit
|
||||
case displayReactionsMenuForItemId(itemId: String)
|
||||
}
|
||||
|
||||
struct RoomScreenViewState: BindableState {
|
||||
@ -48,6 +51,7 @@ struct RoomScreenViewState: BindableState {
|
||||
var isBackPaginating = false
|
||||
var showLoading = false
|
||||
var bindings: RoomScreenViewStateBindings
|
||||
var displayReactionsMenuForItemId = ""
|
||||
|
||||
var contextMenuBuilder: (@MainActor (_ itemId: String) -> TimelineItemContextMenu)?
|
||||
|
||||
|
@ -91,6 +91,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
|
||||
var callback: ((RoomScreenViewModelAction) -> Void)?
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
override func process(viewAction: RoomScreenViewAction) async {
|
||||
switch viewAction {
|
||||
case .paginateBackwards:
|
||||
@ -108,6 +109,10 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
case .sendReaction(let key, _):
|
||||
#warning("Reaction implementation awaiting SDK support.")
|
||||
MXLog.warning("React with \(key) failed. Not implemented.")
|
||||
case .displayEmojiPicker(let itemId):
|
||||
callback?(.displayEmojiPicker(itemId: itemId))
|
||||
case .displayReactionsMenuForItemId(let itemId):
|
||||
state.displayReactionsMenuForItemId = itemId
|
||||
case .cancelReply:
|
||||
state.composerMode = .default
|
||||
case .cancelEdit:
|
||||
|
@ -19,6 +19,7 @@ import SwiftUI
|
||||
struct RoomScreen: View {
|
||||
@ObservedObject private var settings = ElementSettings.shared
|
||||
@ObservedObject var context: RoomScreenViewModel.Context
|
||||
@State private var showReactionsMenuForItemId = ""
|
||||
|
||||
var body: some View {
|
||||
timeline
|
||||
|
@ -0,0 +1,51 @@
|
||||
//
|
||||
// Copyright 2022 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 SwiftUI
|
||||
|
||||
struct TimelineItemReactionsMenuView: View {
|
||||
private let emojis = ["👍🏼", "👎🏼", "😄", "🙏🏼", "😇"]
|
||||
|
||||
var onDisplayEmojiPicker: (() -> Void)?
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
HStack(spacing: 10) {
|
||||
ForEach(emojis, id: \.self) { emoji in
|
||||
Text(emoji)
|
||||
}
|
||||
}
|
||||
.padding(10)
|
||||
.background(.gray)
|
||||
.cornerRadius(15)
|
||||
HStack(spacing: 10) {
|
||||
Text("➕")
|
||||
}
|
||||
.padding(10)
|
||||
.background(.gray)
|
||||
.cornerRadius(15)
|
||||
.onTapGesture {
|
||||
onDisplayEmojiPicker?()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TimelineItemReactionsMenuView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
TimelineItemReactionsMenuView(onDisplayEmojiPicker: nil)
|
||||
}
|
||||
}
|
@ -102,6 +102,12 @@ struct TimelineTableView: UIViewRepresentable {
|
||||
}
|
||||
}
|
||||
|
||||
var displayReactionsMenuForItemId = "" {
|
||||
didSet {
|
||||
tableView?.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
/// The table's diffable data source.
|
||||
private var dataSource: UITableViewDiffableDataSource<TimelineSection, RoomTimelineViewProvider>?
|
||||
private var cancellables: Set<AnyCancellable> = []
|
||||
@ -175,25 +181,35 @@ struct TimelineTableView: UIViewRepresentable {
|
||||
|
||||
cell.item = timelineItem
|
||||
cell.contentConfiguration = UIHostingConfiguration {
|
||||
timelineItem
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.opacity(viewModelContext.viewState.opacity(for: timelineItem))
|
||||
.contextMenu {
|
||||
viewModelContext.viewState.contextMenuBuilder?(timelineItem.id)
|
||||
}
|
||||
.onAppear {
|
||||
viewModelContext.send(viewAction: .itemAppeared(id: timelineItem.id))
|
||||
}
|
||||
.onDisappear {
|
||||
viewModelContext.send(viewAction: .itemDisappeared(id: timelineItem.id))
|
||||
}
|
||||
.environment(\.openURL, OpenURLAction { url in
|
||||
viewModelContext.send(viewAction: .linkClicked(url: url))
|
||||
return .systemAction
|
||||
})
|
||||
.onTapGesture {
|
||||
viewModelContext.send(viewAction: .itemTapped(id: timelineItem.id))
|
||||
VStack {
|
||||
if viewModelContext.viewState.displayReactionsMenuForItemId == timelineItem.id {
|
||||
TimelineItemReactionsMenuView {
|
||||
viewModelContext.send(viewAction: .displayEmojiPicker(itemId: timelineItem.id))
|
||||
}
|
||||
}
|
||||
timelineItem
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.opacity(viewModelContext.viewState.opacity(for: timelineItem))
|
||||
.contextMenu {
|
||||
viewModelContext.viewState.contextMenuBuilder?(timelineItem.id)
|
||||
}
|
||||
.onAppear {
|
||||
viewModelContext.send(viewAction: .itemAppeared(id: timelineItem.id))
|
||||
}
|
||||
.onDisappear {
|
||||
viewModelContext.send(viewAction: .itemDisappeared(id: timelineItem.id))
|
||||
}
|
||||
.environment(\.openURL, OpenURLAction { url in
|
||||
viewModelContext.send(viewAction: .linkClicked(url: url))
|
||||
return .systemAction
|
||||
})
|
||||
.onTapGesture(count: 2) {
|
||||
viewModelContext.send(viewAction: .displayReactionsMenuForItemId(itemId: timelineItem.id))
|
||||
}
|
||||
.onTapGesture {
|
||||
viewModelContext.send(viewAction: .itemTapped(id: timelineItem.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
.margins(.all, self.timelineStyle.rowInsets)
|
||||
.minSize(height: 1)
|
||||
@ -240,6 +256,9 @@ struct TimelineTableView: UIViewRepresentable {
|
||||
if composerMode != viewModelContext.viewState.composerMode {
|
||||
composerMode = viewModelContext.viewState.composerMode
|
||||
}
|
||||
if displayReactionsMenuForItemId != viewModelContext.viewState.displayReactionsMenuForItemId {
|
||||
displayReactionsMenuForItemId = viewModelContext.viewState.displayReactionsMenuForItemId
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the table view with the latest items from the ``timelineItems`` array. After
|
||||
|
21
ElementX/Sources/Services/Emojis/EmojiCategory.swift
Normal file
21
ElementX/Sources/Services/Emojis/EmojiCategory.swift
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Copyright 2022 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: Equatable, Identifiable {
|
||||
let id: String
|
||||
let emojis: [EmojiItem]
|
||||
}
|
35
ElementX/Sources/Services/Emojis/EmojiItem.swift
Normal file
35
ElementX/Sources/Services/Emojis/EmojiItem.swift
Normal file
@ -0,0 +1,35 @@
|
||||
//
|
||||
// Copyright 2022 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: Equatable, Identifiable {
|
||||
var id: String
|
||||
let name: String
|
||||
let keywords: [String]
|
||||
let skins: [EmojiItemSkin]
|
||||
}
|
||||
|
||||
extension EmojiItem {
|
||||
init?(from emojiMart: EmojiMartEmoji) {
|
||||
id = emojiMart.id
|
||||
name = emojiMart.name
|
||||
keywords = emojiMart.keywords
|
||||
skins = emojiMart.skins.compactMap { emojiMartEmojiSkin in
|
||||
EmojiItemSkin(from: emojiMartEmojiSkin)
|
||||
}
|
||||
}
|
||||
}
|
36
ElementX/Sources/Services/Emojis/EmojiItemSkin.swift
Normal file
36
ElementX/Sources/Services/Emojis/EmojiItemSkin.swift
Normal file
@ -0,0 +1,36 @@
|
||||
//
|
||||
// Copyright 2022 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 EmojiItemSkin: Equatable {
|
||||
let value: String
|
||||
|
||||
init?(from emojiMartEmojiSkin: EmojiMartEmojiSkin) {
|
||||
let unicodeStringComponents = emojiMartEmojiSkin.unified.components(separatedBy: "-")
|
||||
|
||||
var emoji = ""
|
||||
|
||||
for unicodeStringComponent in unicodeStringComponents {
|
||||
guard let unicodeCodePoint = Int(unicodeStringComponent, radix: 16),
|
||||
let emojiUnicodeScalar = UnicodeScalar(unicodeCodePoint) else {
|
||||
return nil
|
||||
}
|
||||
emoji.append(String(emojiUnicodeScalar))
|
||||
}
|
||||
value = emoji
|
||||
}
|
||||
}
|
21
ElementX/Sources/Services/Emojis/EmojiLoaderProtocol.swift
Normal file
21
ElementX/Sources/Services/Emojis/EmojiLoaderProtocol.swift
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Copyright 2022 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 EmojiLoaderProtocol {
|
||||
func load() async -> [EmojiCategory]
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
//
|
||||
// Copyright 2022 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: Decodable {
|
||||
let id: String
|
||||
let emojis: [String]
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
//
|
||||
// Copyright 2022 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 EmojiMartEmoji: Decodable {
|
||||
let id: String
|
||||
let name: String
|
||||
let keywords: [String]
|
||||
let skins: [EmojiMartEmojiSkin]
|
||||
}
|
||||
|
||||
struct EmojiMartEmojiSkin: Decodable {
|
||||
let unified: String
|
||||
let native: String
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
//
|
||||
// Copyright 2022 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
|
||||
|
||||
private enum EmojiMartJSONLoaderError: Error {
|
||||
case fileNotFound
|
||||
}
|
||||
|
||||
class EmojiMartJSONLoader: EmojiLoaderProtocol {
|
||||
/// Emoji data coming from https://github.com/missive/emoji-mart/blob/main/packages/emoji-mart-data/sets/14/apple.json
|
||||
private let jsonFilename = "apple_emojis_data"
|
||||
|
||||
func load() async -> [EmojiCategory] {
|
||||
do {
|
||||
let data = try await loadJSONData()
|
||||
let store = try await decodeJSONData(data: data)
|
||||
return emojiCategories(from: store)
|
||||
} catch {
|
||||
MXLog.error("Couldn't parse emoji json")
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
private func loadJSONData() async throws -> Data {
|
||||
guard let jsonDataURL = Bundle.main.url(forResource: jsonFilename, withExtension: "json") else {
|
||||
throw EmojiMartJSONLoaderError.fileNotFound
|
||||
}
|
||||
return try Data(contentsOf: jsonDataURL)
|
||||
}
|
||||
|
||||
private func decodeJSONData(data: Data) async throws -> EmojiMartStore {
|
||||
try JSONDecoder().decode(EmojiMartStore.self, from: data)
|
||||
}
|
||||
|
||||
private func emojiCategories(from emojiMartStore: EmojiMartStore) -> [EmojiCategory] {
|
||||
emojiMartStore.categories.map { emojiMartCategory -> EmojiCategory in
|
||||
let emojiItems = emojiMartCategory.emojis.compactMap { emoji -> EmojiItem? in
|
||||
guard let emojiMartEmoji = emojiMartStore.emojis.first(where: { $0.id == emoji }) else {
|
||||
return nil
|
||||
}
|
||||
return EmojiItem(from: emojiMartEmoji)
|
||||
}
|
||||
return EmojiCategory(id: emojiMartCategory.id, emojis: emojiItems)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
//
|
||||
// Copyright 2022 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: Decodable {
|
||||
let categories: [EmojiMartCategory]
|
||||
let emojis: [EmojiMartEmoji]
|
||||
|
||||
enum CodingKeys: CodingKey {
|
||||
case categories
|
||||
case emojis
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
categories = try container.decode([EmojiMartCategory].self, forKey: .categories)
|
||||
|
||||
let emojisDictionary = try container.decode([String: EmojiMartEmoji].self, forKey: .emojis)
|
||||
emojis = emojisDictionary.map(\.value)
|
||||
}
|
||||
}
|
29850
ElementX/Sources/Services/Emojis/EmojiMart/apple_emojis_data.json
Normal file
29850
ElementX/Sources/Services/Emojis/EmojiMart/apple_emojis_data.json
Normal file
File diff suppressed because one or more lines are too long
76
ElementX/Sources/Services/Emojis/EmojiProvider.swift
Normal file
76
ElementX/Sources/Services/Emojis/EmojiProvider.swift
Normal file
@ -0,0 +1,76 @@
|
||||
//
|
||||
// Copyright 2022 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
|
||||
|
||||
@MainActor
|
||||
protocol EmojiProviderProtocol {
|
||||
func getCategories(searchString: String?) async -> [EmojiCategory]
|
||||
}
|
||||
|
||||
private enum EmojiProviderState {
|
||||
case notLoaded
|
||||
case inProgress(Task<[EmojiCategory], Never>)
|
||||
case loaded([EmojiCategory])
|
||||
}
|
||||
|
||||
class EmojiProvider: EmojiProviderProtocol {
|
||||
private let loader: EmojiLoaderProtocol
|
||||
private var state: EmojiProviderState = .notLoaded
|
||||
|
||||
init(loader: EmojiLoaderProtocol = EmojiMartJSONLoader()) {
|
||||
self.loader = loader
|
||||
Task {
|
||||
await loadIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
func getCategories(searchString: String? = nil) async -> [EmojiCategory] {
|
||||
let emojiCategories = await loadIfNeeded()
|
||||
if let searchString = searchString, searchString.isEmpty == false {
|
||||
return search(searchString: searchString, emojiCategories: emojiCategories)
|
||||
} else {
|
||||
return emojiCategories
|
||||
}
|
||||
}
|
||||
|
||||
private func search(searchString: String, emojiCategories: [EmojiCategory]) -> [EmojiCategory] {
|
||||
emojiCategories.compactMap { category in
|
||||
let emojis = category.emojis.filter { emoji in
|
||||
let searchArray = [emoji.id, emoji.name] + emoji.keywords
|
||||
return searchArray.description.range(of: searchString, options: .caseInsensitive) != nil
|
||||
}
|
||||
return emojis.isEmpty ? nil : EmojiCategory(id: category.id, emojis: emojis)
|
||||
}
|
||||
}
|
||||
|
||||
private func loadIfNeeded() async -> [EmojiCategory] {
|
||||
switch state {
|
||||
case .notLoaded:
|
||||
let task = Task {
|
||||
await loader.load()
|
||||
}
|
||||
state = .inProgress(task)
|
||||
let categories = await task.value
|
||||
state = .loaded(categories)
|
||||
return categories
|
||||
case .loaded(let categories):
|
||||
return categories
|
||||
case .inProgress(let task):
|
||||
return await task.value
|
||||
}
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
private let userSession: UserSessionProtocol
|
||||
private let navigationController: NavigationController
|
||||
private let bugReportService: BugReportServiceProtocol
|
||||
private let emojiProvider: EmojiProviderProtocol = EmojiProvider()
|
||||
|
||||
var callback: ((UserSessionFlowCoordinatorAction) -> Void)?
|
||||
|
||||
@ -148,7 +149,8 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
|
||||
timelineController: timelineController,
|
||||
mediaProvider: userSession.mediaProvider,
|
||||
roomName: roomProxy.displayName ?? roomProxy.name,
|
||||
roomAvatarUrl: roomProxy.avatarURL)
|
||||
roomAvatarUrl: roomProxy.avatarURL,
|
||||
emojiProvide: emojiProvider)
|
||||
let coordinator = RoomScreenCoordinator(parameters: parameters)
|
||||
|
||||
navigationController.push(coordinator) { [weak self] in
|
||||
|
@ -121,14 +121,16 @@ class MockScreen: Identifiable {
|
||||
timelineController: MockRoomTimelineController(),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
roomName: "Some room name",
|
||||
roomAvatarUrl: nil)
|
||||
roomAvatarUrl: nil,
|
||||
emojiProvide: EmojiProvider())
|
||||
return RoomScreenCoordinator(parameters: parameters)
|
||||
case .roomEncryptedWithAvatar:
|
||||
let parameters = RoomScreenCoordinatorParameters(navigationController: navigationController,
|
||||
timelineController: MockRoomTimelineController(),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
roomName: "Some room name",
|
||||
roomAvatarUrl: "mock_url")
|
||||
roomAvatarUrl: "mock_url",
|
||||
emojiProvide: EmojiProvider())
|
||||
return RoomScreenCoordinator(parameters: parameters)
|
||||
case .sessionVerification:
|
||||
let parameters = SessionVerificationCoordinatorParameters(sessionVerificationControllerProxy: MockSessionVerificationControllerProxy())
|
||||
|
32
UnitTests/Sources/EmojiMartJSONLoaderTests.swift
Normal file
32
UnitTests/Sources/EmojiMartJSONLoaderTests.swift
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright 2022 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 ElementX
|
||||
|
||||
final class EmojiMartJSONLoaderTests: XCTestCase {
|
||||
var sut: EmojiMartJSONLoader!
|
||||
|
||||
override func setUp() {
|
||||
sut = EmojiMartJSONLoader()
|
||||
}
|
||||
|
||||
func test_whenEmojisLoaded_CorrectCategoryCountReturned() async {
|
||||
let categories = await sut.load()
|
||||
XCTAssertEqual(categories.count, 8)
|
||||
}
|
||||
}
|
104
UnitTests/Sources/EmojiProviderTests.swift
Normal file
104
UnitTests/Sources/EmojiProviderTests.swift
Normal file
@ -0,0 +1,104 @@
|
||||
//
|
||||
// Copyright 2022 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 ElementX
|
||||
|
||||
final class EmojiProviderTests: XCTestCase {
|
||||
var sut: EmojiProvider!
|
||||
private var emojiLoaderMock: EmojiLoaderMock!
|
||||
|
||||
@MainActor override func setUp() {
|
||||
emojiLoaderMock = EmojiLoaderMock()
|
||||
sut = EmojiProvider(loader: emojiLoaderMock)
|
||||
}
|
||||
|
||||
func test_whenEmojisLoaded_categoriesAreLoadedFromLoader() async throws {
|
||||
let item = EmojiItem(id: "test", name: "test", keywords: ["1", "2"], skins: [try slightlySmilingFaceEmoji()])
|
||||
let category = EmojiCategory(id: "test", emojis: [item])
|
||||
emojiLoaderMock.categories = [category]
|
||||
let categories = await sut.getCategories()
|
||||
XCTAssertEqual(emojiLoaderMock.categories, categories)
|
||||
}
|
||||
|
||||
func test_whenEmojisLoadedAndSearchStringEmpty_allCategoriesReturned() async throws {
|
||||
let item = EmojiItem(id: "test", name: "test", keywords: ["1", "2"], skins: [try slightlySmilingFaceEmoji()])
|
||||
let category = EmojiCategory(id: "test", emojis: [item])
|
||||
emojiLoaderMock.categories = [category]
|
||||
let categories = await sut.getCategories(searchString: "")
|
||||
XCTAssertEqual(emojiLoaderMock.categories, categories)
|
||||
}
|
||||
|
||||
func test_whenEmojisLoadedSecondTime_cachedValuesAreUsed() async throws {
|
||||
let categoriesForFirstLoad = [EmojiCategory(id: "test",
|
||||
emojis: [EmojiItem(id: "test", name: "test", keywords: ["1", "2"], skins: [try slightlySmilingFaceEmoji()])])]
|
||||
let categoriesForSecondLoad = [EmojiCategory(id: "test2",
|
||||
emojis: [EmojiItem(id: "test2", name: "test2", keywords: ["3", "4"], skins: [try meltingFaceEmoji()])])]
|
||||
emojiLoaderMock.categories = categoriesForFirstLoad
|
||||
_ = await sut.getCategories()
|
||||
emojiLoaderMock.categories = categoriesForSecondLoad
|
||||
let categories = await sut.getCategories()
|
||||
XCTAssertEqual(categories, categoriesForFirstLoad)
|
||||
}
|
||||
|
||||
func test_whenEmojisSearched_correctNumberOfCategoriesReturned() async throws {
|
||||
let searchString = "smile"
|
||||
var categories = [EmojiCategory]()
|
||||
categories.append(EmojiCategory(id: "test",
|
||||
emojis: [EmojiItem(id: "\(searchString)_123",
|
||||
name: "emoji0",
|
||||
keywords: ["key1", "key1"],
|
||||
skins: [try slightlySmilingFaceEmoji()]),
|
||||
EmojiItem(id: "emoji_1",
|
||||
name: searchString,
|
||||
keywords: ["key1", "key1"],
|
||||
skins: [try slightlySmilingFaceEmoji()]),
|
||||
EmojiItem(id: "emoji_2",
|
||||
name: "emoji2",
|
||||
keywords: ["key1", "\(searchString)_123"],
|
||||
skins: [try slightlySmilingFaceEmoji()]),
|
||||
EmojiItem(id: "emoji_3",
|
||||
name: "emoji_3",
|
||||
keywords: ["key1", "key1"],
|
||||
skins: [try slightlySmilingFaceEmoji()])]))
|
||||
categories.append(EmojiCategory(id: "test",
|
||||
emojis: [EmojiItem(id: "\(searchString)_123",
|
||||
name: "emoji0",
|
||||
keywords: ["key1", "key1"],
|
||||
skins: [try slightlySmilingFaceEmoji()])]))
|
||||
emojiLoaderMock.categories = categories
|
||||
_ = await sut.getCategories()
|
||||
let result = await sut.getCategories(searchString: searchString)
|
||||
XCTAssertEqual(result.count, 2)
|
||||
XCTAssertEqual(result.first?.emojis.count, 3)
|
||||
}
|
||||
|
||||
private func slightlySmilingFaceEmoji() throws -> EmojiItemSkin {
|
||||
try XCTUnwrap(EmojiItemSkin(from: EmojiMartEmojiSkin(unified: "1f642", native: "🙂")))
|
||||
}
|
||||
|
||||
private func meltingFaceEmoji() throws -> EmojiItemSkin {
|
||||
try XCTUnwrap(EmojiItemSkin(from: EmojiMartEmojiSkin(unified: "1fae0", native: "🫠")))
|
||||
}
|
||||
}
|
||||
|
||||
private class EmojiLoaderMock: EmojiLoaderProtocol {
|
||||
var categories = [ElementX.EmojiCategory]()
|
||||
func load() async -> [ElementX.EmojiCategory] {
|
||||
categories
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user