mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Add UI to record a voice message from the composer toolbar (#1892)
This commit is contained in:
parent
0d72554e6f
commit
9ab3b20a49
@ -28,12 +28,14 @@
|
||||
06D3942496E9E0E655F14D21 /* NotificationManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A057F2FDC14866C3026A89A4 /* NotificationManagerProtocol.swift */; };
|
||||
071A017E415AD378F2961B11 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 227AC5D71A4CE43512062243 /* URL.swift */; };
|
||||
07756D532EFE33DD1FA258E5 /* GeoURITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A7ED2EF5BDBAD2A7DBC4636 /* GeoURITests.swift */; };
|
||||
086D01E79C8E8D3F004FAF21 /* AudioPlayerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9104846487244648D32C6D /* AudioPlayerProtocol.swift */; };
|
||||
08CB4BD12CEEDE6AAE4A18DD /* WindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035177BCD8E8308B098AC3C2 /* WindowManager.swift */; };
|
||||
095C0ACFC234E0550A6404C5 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */; };
|
||||
095D3906CF2F940C2D2D17CC /* RoomFlowCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FCB2126C091EEF2454B4D56 /* RoomFlowCoordinatorTests.swift */; };
|
||||
09713669577CDA8D012EE380 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 6647C55D93508C7CE9D954A5 /* MatrixRustSDK */; };
|
||||
09AAF04B27732046C755D914 /* SoftLogoutViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C5DAA1773F57653BF1C4F9 /* SoftLogoutViewModelTests.swift */; };
|
||||
09C83DDDB07C28364F325209 /* MockRoomTimelineController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52D7074991B3267B26D89B22 /* MockRoomTimelineController.swift */; };
|
||||
09EF4222EEBBA1A7B8F4071E /* VoiceMessageRecordingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E61DDB42C0DE429C0955D8 /* VoiceMessageRecordingButton.swift */; };
|
||||
0A194F5E70B5A628C1BF4476 /* AdvancedSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4999B5FD50AED7CB0F590FF8 /* AdvancedSettingsScreenModels.swift */; };
|
||||
0AA0477E063E72B786A983CF /* AnalyticsPromptScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 63E1FF2DA52B1DE7CAEC5422 /* AnalyticsPromptScreenViewModel.swift */; };
|
||||
0ACAA31FD0399CEEBA3ECC21 /* UserDetailsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85149F56BA333619900E2410 /* UserDetailsEditScreenViewModelProtocol.swift */; };
|
||||
@ -65,6 +67,7 @@
|
||||
13C77FDF17C4C6627CFFC205 /* RoomTimelineItemFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D25A35764C7B3DB78954AB5 /* RoomTimelineItemFactoryProtocol.swift */; };
|
||||
13CBC470FB619A6393A21908 /* RoomNotificationSettingsScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8296D6FB451E25CEC0767BBA /* RoomNotificationSettingsScreenCoordinator.swift */; };
|
||||
14343C2F9AD2BFEA92CA28FF /* MapTilerStyleBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7AE92E7BFF71797BDE1D261 /* MapTilerStyleBuilder.swift */; };
|
||||
1471A080552631358D152C18 /* AudioPlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BDB3E65A79779EDA5D33D8A /* AudioPlayerState.swift */; };
|
||||
149D1942DC005D0485FB8D93 /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DC1943ADE6A62ED5129D7C8 /* LoggingTests.swift */; };
|
||||
14E99D27628B1A6F0CB46FEA /* SeparatorRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A9F49B3EE59147AF2F70BB /* SeparatorRoomTimelineItem.swift */; };
|
||||
152AE2B8650FB23AFD2E28B9 /* MockAuthenticationServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65C2B80DD0BF6F10BB5FA922 /* MockAuthenticationServiceProxy.swift */; };
|
||||
@ -146,6 +149,7 @@
|
||||
2C4C750D0039AFABDF24236C /* TemplateScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 342BEBC3C5FC3F9943C41C4C /* TemplateScreenViewModelProtocol.swift */; };
|
||||
2C5E832434EE94E21AB3B238 /* EmojiPickerScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3EAE3E9D5EF4A6D5D9C6CFD /* EmojiPickerScreenViewModel.swift */; };
|
||||
2CA6ABBC9A88EB89EA52FCCB /* ConfettiScene.scn in Resources */ = {isa = PBXBuildFile; fileRef = B61C339A2FDDBD067FF6635C /* ConfettiScene.scn */; };
|
||||
2DA27D78560D5F79B917E163 /* AudioConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44E35AA87F49503E7B3BF6E /* AudioConverter.swift */; };
|
||||
2DA90E38FF4E696825810C1A /* WaitlistScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECB08484CD5D77C9BF97AA78 /* WaitlistScreenUITests.swift */; };
|
||||
2E43A3D221BE9587BC19C3F1 /* MatrixEntityRegexTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */; };
|
||||
2E8C6672D0EE7D5B1BEDB8E2 /* ServerConfirmationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F7478623CECC9438014244BA /* ServerConfirmationScreen.swift */; };
|
||||
@ -153,6 +157,7 @@
|
||||
2F623DA1122140A987B34D08 /* NotificationSettingsEditScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA7BB497B2F539C17E88F6B7 /* NotificationSettingsEditScreenViewModelProtocol.swift */; };
|
||||
2F66701B15657A87B4AC3A0A /* WaitlistScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09CE2B7AD979BDEE09FEDB08 /* WaitlistScreenModels.swift */; };
|
||||
2F94054F50E312AF30BE07F3 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40B21E611DADDEF00307E7AC /* String.swift */; };
|
||||
3042527CB344A9EF1157FC26 /* AudioRecorderStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C55CC239AE12339C565F6C9A /* AudioRecorderStateTests.swift */; };
|
||||
308BD9343B95657FAA583FB7 /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 19CD5B074D7DD44AF4C58BB6 /* SwiftState */; };
|
||||
3097A0A867D2B19CE32DAE58 /* UIKitBackgroundTaskService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DF1FFC3336EB23374BBBFCC /* UIKitBackgroundTaskService.swift */; };
|
||||
30CC1DB7CE357659C82AA115 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */; };
|
||||
@ -162,6 +167,7 @@
|
||||
32B7891D937377A59606EDFC /* UserFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21DD8599815136EFF5B73F38 /* UserFlowTests.swift */; };
|
||||
33094DB91C3A4131E76B2C07 /* AppLockScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5574FD6FC3C2DC0DF160A85 /* AppLockScreenViewModelProtocol.swift */; };
|
||||
339BC18777912E1989F2F17D /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584A61D9C459FAFEF038A7C0 /* Section.swift */; };
|
||||
33CA777C9DF263582D77A67F /* VoiceMessagePreviewComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFC9F57320EC80C7CE34FE4A /* VoiceMessagePreviewComposer.swift */; };
|
||||
33CAC1226DFB8B5D8447D286 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; };
|
||||
340D39DB87F3800D53A6A621 /* EmojiPickerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */; };
|
||||
34357B287357BC0B9715DD51 /* UserAgentBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2529D434C750ED78ADF1ED /* UserAgentBuilder.swift */; };
|
||||
@ -194,6 +200,7 @@
|
||||
3A7DD0D13B0FB8876D69D829 /* TextBasedRoomTimelineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2AB2C848BB9A7A9B618B7B89 /* TextBasedRoomTimelineTests.swift */; };
|
||||
3B0F9B57D25B07E66F15762A /* MediaUploadPreviewScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2E7C987AE5DC9087BB19F7D /* MediaUploadPreviewScreenModels.swift */; };
|
||||
3B28408450BCAED911283AA2 /* UserPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FA991289149D31F4286747 /* UserPreference.swift */; };
|
||||
3C31E1A65EEB61E72E1113B4 /* AudioRecorderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBEC57C204D77908E355EF42 /* AudioRecorderProtocol.swift */; };
|
||||
3C549A0BF39F8A854D45D9FD /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 0DD568A494247444A4B56031 /* Kingfisher */; };
|
||||
3C73442084BF8A6939F0F80B /* AnalyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */; };
|
||||
3DA57CA0D609A6B37CA1DC2F /* BugReportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6DC38E64A5ED3FDB201029A /* BugReportService.swift */; };
|
||||
@ -229,6 +236,7 @@
|
||||
4681820102DAC8BA586357D4 /* VoiceMessageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D7926A5684E18196B538 /* VoiceMessageCache.swift */; };
|
||||
46A261AA898344A1F3C406B1 /* ReportContentScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCE3636E3D01477C8B2E9D0 /* ReportContentScreenModels.swift */; };
|
||||
46BA7F4B4D3A7164DED44B88 /* FullscreenDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565F1B2B300597C616B37888 /* FullscreenDialog.swift */; };
|
||||
46C9F8FE3810A04A005FE16B /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B19B2BCC779ED934E0BBC2A /* AudioPlayer.swift */; };
|
||||
46D1E2940ED8CCBF62FE8854 /* CreatePollScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27EA0F71A3A400A202E15318 /* CreatePollScreen.swift */; };
|
||||
4714991754A08B58B4D7ED85 /* OnboardingScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F27BAB69EB568369F1F6B3 /* OnboardingScreenViewModelProtocol.swift */; };
|
||||
47305C0911C9E1AA774A4000 /* TemplateScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA90BD288E5AE6BC643AFDDF /* TemplateScreenCoordinator.swift */; };
|
||||
@ -288,6 +296,7 @@
|
||||
588411C8FD72B2A2DFE5F7DE /* XCUIElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = E992D7B8BE54B2AB454613AF /* XCUIElement.swift */; };
|
||||
5894C2514400A4FBC9327632 /* ServerConfirmationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03277E40D0E0DE0712021A71 /* ServerConfirmationScreenCoordinator.swift */; };
|
||||
5897A59DDBD3592282092223 /* MediaSourceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D49B9785E3AD7D1C15A29F2F /* MediaSourceProxy.swift */; };
|
||||
5992EF10AA157EBD97D88910 /* AudioRecorderState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6569593FA36B22259E806A67 /* AudioRecorderState.swift */; };
|
||||
5995C63B1C61DE1373AA2BCE /* ComposerToolbarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD95B3714F806AC9CF9A557B /* ComposerToolbarViewModel.swift */; };
|
||||
59F940FCBE6BC343AECEF75E /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2245243369B99216C7D84E /* ImageCache.swift */; };
|
||||
5B2D1210B40570D87B11BD3B /* ThreadDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CA3F8E905DF50BF22ECC18F /* ThreadDecorator.swift */; };
|
||||
@ -308,7 +317,6 @@
|
||||
5F06AD3C66884CE793AE6119 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */; };
|
||||
5F28C9146694B381BB82E18C /* AnalyticsPromptScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B65A314DF40B6BBF775C2BC /* AnalyticsPromptScreenCoordinator.swift */; };
|
||||
5F5488FBC9CFEB6F433D74A4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7109E709A7738E6BCC4553E6 /* Localizable.strings */; };
|
||||
5F8E96263497FFB7D3254EB2 /* AudioConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE1CD5EC6265A09315772DB7 /* AudioConverter.swift */; };
|
||||
60ED66E63A169E47489348A8 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 886A0A498FA01E8EDD451D05 /* Sentry */; };
|
||||
6146996D5C4DDD5DA816FC87 /* AuthenticationTextFieldStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCACD75595C40EACD6AD4A74 /* AuthenticationTextFieldStyle.swift */; };
|
||||
617624A97BDBB75ED3DD8156 /* RoomScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */; };
|
||||
@ -319,6 +327,7 @@
|
||||
62418EA4E3EB597AD184AEB6 /* PillConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB8D34E94AB07128DB73D6C7 /* PillConstants.swift */; };
|
||||
62910B515BCB4B455E24D7C1 /* AdvancedSettingsScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D086854995173E897F993C26 /* AdvancedSettingsScreenViewModelProtocol.swift */; };
|
||||
6298AB0906DDD3525CD78C6B /* LRUCache in Frameworks */ = {isa = PBXBuildFile; productRef = 1081D3630AAD3ACEDDEC3A98 /* LRUCache */; };
|
||||
62A7FC3A0191BC7181AA432B /* AudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 907FA4DE17DEA1A3738EFB83 /* AudioRecorder.swift */; };
|
||||
63E46D18B91D08E15FC04125 /* ExpiringTaskRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B25F959A434BB9923A3223F /* ExpiringTaskRunner.swift */; };
|
||||
642DF13C49ED4121C148230E /* TestablePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1E227F34BE43B08E098796E /* TestablePreview.swift */; };
|
||||
6448F8D1D3CA4CD27BB4CADD /* RoomMemberProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F36C5D9B37E50915ECBD3EE /* RoomMemberProxy.swift */; };
|
||||
@ -432,7 +441,6 @@
|
||||
829062DD3C3F7016FE1A6476 /* RoomDetailsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BFDAF6918BB096C44788FC9 /* RoomDetailsScreenUITests.swift */; };
|
||||
8317E1314C00DCCC99D30DA8 /* TextBasedRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9227F7495DA43324050A863 /* TextBasedRoomTimelineItem.swift */; };
|
||||
83A4DAB181C56987C3E804FF /* MapTilerStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F0B9F5BC4C80543DE7228B9D /* MapTilerStyle.swift */; };
|
||||
8418688282763F4B9DDC42FB /* AudioConverterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB4D7B3FBCA261927536FE64 /* AudioConverterProtocol.swift */; };
|
||||
84226AD2E1F1FBC965F3B09E /* UnitTestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8E19C4645D3F5F9FB02355 /* UnitTestsAppCoordinator.swift */; };
|
||||
84EFCB95F9DA2979C8042B26 /* UITestsSignalling.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F0192CE2F891141A25B49F /* UITestsSignalling.swift */; };
|
||||
8544657DEEE717ED2E22E382 /* RoomNotificationSettingsProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5D1BAA90F3A073D91B4F16B /* RoomNotificationSettingsProxyMock.swift */; };
|
||||
@ -472,6 +480,7 @@
|
||||
8BC8EF6705A78946C1F22891 /* SoftLogoutScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A7D4DDEEE5D2CA0C8D63CD /* SoftLogoutScreen.swift */; };
|
||||
8C050A8012E6078BEAEF5BC8 /* PillTextAttachmentData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 913C8E13B8B602C7B6C0C4AE /* PillTextAttachmentData.swift */; };
|
||||
8C1A5ECAF895D4CAF8C4D461 /* AppActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F21ED7205048668BEB44A38 /* AppActivityView.swift */; };
|
||||
8C27BEB00B903D953F31F962 /* VoiceMessageRecordingButtonTooltipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF449205DF1E9817115245C4 /* VoiceMessageRecordingButtonTooltipView.swift */; };
|
||||
8C454500B8073E1201F801A9 /* MXLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A34A814CBD56230BC74FFCF4 /* MXLogger.swift */; };
|
||||
8C706DA7EAC0974CA2F8F1CD /* MentionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 15748C254911E3654C93B0ED /* MentionBuilder.swift */; };
|
||||
8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; };
|
||||
@ -667,7 +676,6 @@
|
||||
C11939FDC40716C4387275A4 /* NotificationSettingsEditScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8544F7058D31DBEB8DBFF0F5 /* NotificationSettingsEditScreenViewModelTests.swift */; };
|
||||
C13128AAA787A4C2CBE4EE82 /* MessageForwardingScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */; };
|
||||
C1429699A6A5BB09A25775C1 /* AudioPlayerStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89233612A8632AD7E2803620 /* AudioPlayerStateTests.swift */; };
|
||||
C19085A284D54A166A64A86C /* AudioPlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA88C615D8BFCCEF0D2FEAC9 /* AudioPlayerState.swift */; };
|
||||
C1910A16BDF131FECA77BE22 /* EmojiPickerScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BEA38B9851CFCC4D67F5587D /* EmojiPickerScreenCoordinator.swift */; };
|
||||
C1A5C386319835FB0C77736B /* ReportContentScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A16CD2C62CB7DB78A4238485 /* ReportContentScreenCoordinator.swift */; };
|
||||
C1D0AB8222D7BAFC9AF9C8C0 /* MapLibreMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 622D09D4ECE759189009AEAF /* MapLibreMapView.swift */; };
|
||||
@ -699,6 +707,7 @@
|
||||
C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E0B4A34E69BD2132BEC521 /* MessageText.swift */; };
|
||||
C9BE065FA7D4E77E4C61CB69 /* MapLibreModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B81B6170DB690013CEB646F4 /* MapLibreModels.swift */; };
|
||||
C9F5B48D15B9BCAE1F8D564E /* RoomNotificationModeProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1511766C534367700C8DD75 /* RoomNotificationModeProxy.swift */; };
|
||||
CA5BFF0C2EF5A8EF40CA2D69 /* VoiceMessageRecordingComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCB6F36CCE44A29A06FCAF1C /* VoiceMessageRecordingComposer.swift */; };
|
||||
CAF8755E152204F55F8D6B5B /* RoomMembersListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B63F817FE305548DB4B512 /* RoomMembersListViewModelTests.swift */; };
|
||||
CB137BFB3E083C33E398A6CB /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 020597E28A4BC8E1BE8EDF6E /* KeychainAccess */; };
|
||||
CB498F4E27AA0545DCEF0F6F /* DTCoreText in Frameworks */ = {isa = PBXBuildFile; productRef = 36B7FC232711031AA2B0D188 /* DTCoreText */; };
|
||||
@ -711,7 +720,6 @@
|
||||
CCBEC2100CAF2EEBE9DB4156 /* TemplateScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA40B98B098B6F0371B750B3 /* TemplateScreenModels.swift */; };
|
||||
CCC3802A3C019A6FFAAA547A /* NotificationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07E65E613F057697A1A0BC03 /* NotificationViewController.swift */; };
|
||||
CD0088B763CD970CF1CBF8CB /* DateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5E97E9615A158C76B2AB77 /* DateTests.swift */; };
|
||||
CD1C6943F42F29079E5E7511 /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EDDE503C9BAC82B661E4164 /* AudioPlayer.swift */; };
|
||||
CD6A72B65D3B6076F4045C30 /* PHGPostHogConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */; };
|
||||
CDCA8A559E098503DDE29477 /* AttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5C6FBF97B6EED3D4FA5EFF /* AttributedStringBuilder.swift */; };
|
||||
CE1694C7BB93C3311524EF28 /* Untranslated.strings in Resources */ = {isa = PBXBuildFile; fileRef = D2F7194F440375338F8E2487 /* Untranslated.strings */; };
|
||||
@ -809,6 +817,7 @@
|
||||
EAC6FE2CD4F50A43068ADCD8 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 997C7385E1A07E061D7E2100 /* GZIP */; };
|
||||
EAF2B3E6C6AEC4AD3A8BD454 /* RoomMemberDetailsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A87D0471D438A233C2CF4A /* RoomMemberDetailsScreenViewModel.swift */; };
|
||||
EB88DBD77221E2CFE463018C /* NSE.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0D8F620C8B314840D8602E3F /* NSE.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
EBDB339A7C127F068B6E52E5 /* VoiceMessageRecordingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A634D8DD1E10D858CF7995D /* VoiceMessageRecordingView.swift */; };
|
||||
EBE13FAB4E29738AC41BD3E5 /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; };
|
||||
EC280623A42904341363EAAF /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = A20EA00CCB9DBE0FFB17DD09 /* Collections */; };
|
||||
EC658A57E715699C52DFBC77 /* CreatePollScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3EB1D0C69FEDD93404DF927E /* CreatePollScreenViewModelTests.swift */; };
|
||||
@ -829,7 +838,6 @@
|
||||
F06CE9132855E81EBB6DDC32 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 940C605265DD82DA0C655E23 /* Kingfisher */; };
|
||||
F07D88421A9BC4D03D4A5055 /* VideoRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */; };
|
||||
F0A26CD502C3A5868353B0FA /* ServerConfirmationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24DEE0682C95F897B6C7CB0D /* ServerConfirmationScreenViewModel.swift */; };
|
||||
F0B196905CD23E3B4505CB7B /* AudioPlayerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 048A3590E8379BCED4D30D5C /* AudioPlayerProtocol.swift */; };
|
||||
F0F82C3C848C865C3098AA52 /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 21C83087604B154AA30E9A8F /* SnapshotTesting */; };
|
||||
F118DD449066E594F63C697D /* RoomMemberProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5E17028C02DFA7DDA3931 /* RoomMemberProxyProtocol.swift */; };
|
||||
F12F6BED7B6D7EE4BEE55039 /* PlainMentionBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AE78FA0011E07920AE83135 /* PlainMentionBuilder.swift */; };
|
||||
@ -839,6 +847,7 @@
|
||||
F32B271F60531BE92C6E62A1 /* StickerRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 612EF972F2A1800682D32C5E /* StickerRoomTimelineView.swift */; };
|
||||
F37629BAA5E8F50AAF2A131D /* SoftLogoutScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB7BAD55A4E2B8E5828CD64C /* SoftLogoutScreenViewModel.swift */; };
|
||||
F3E2D3F7ACDED65A4E5CD8DE /* RoomMembersListScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF79FB25E2D4BD6F50CE7C9 /* RoomMembersListScreenViewModel.swift */; };
|
||||
F3F38062C6CA21CF403C5C90 /* AudioConverterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2757B1BE23DF8AA239937243 /* AudioConverterProtocol.swift */; };
|
||||
F40B097470D3110DFDB1FAAA /* LegalInformationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47873756E45B46683D97DC32 /* LegalInformationScreenModels.swift */; };
|
||||
F4433EF57B4BB3C077F8B00E /* SessionVerificationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD9E0FFA29EAACFF3AB9732 /* SessionVerificationScreenViewModel.swift */; };
|
||||
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; };
|
||||
@ -942,7 +951,6 @@
|
||||
03DD998E523D4EC93C7ED703 /* RoomNotificationSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
03FABD73FD8086EFAB699F42 /* MediaUploadPreviewScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
045253F9967A535EE5B16691 /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = "<group>"; };
|
||||
048A3590E8379BCED4D30D5C /* AudioPlayerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerProtocol.swift; sourceTree = "<group>"; };
|
||||
04BB8DDE245ED86C489BA983 /* AccessibilityIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityIdentifiers.swift; sourceTree = "<group>"; };
|
||||
04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = "<group>"; };
|
||||
052B2F924572AFD70B5F500E /* StartChatScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
@ -962,6 +970,7 @@
|
||||
099F2D36C141D845A445B1E6 /* EmojiProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiProviderTests.swift; sourceTree = "<group>"; };
|
||||
09CE2B7AD979BDEE09FEDB08 /* WaitlistScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreenModels.swift; sourceTree = "<group>"; };
|
||||
0A3E77399BD262D301451BF2 /* RoomDetailsEditScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
0A634D8DD1E10D858CF7995D /* VoiceMessageRecordingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingView.swift; sourceTree = "<group>"; };
|
||||
0AE449DFBA7CC863EEB2FD2A /* FormattingToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattingToolbar.swift; sourceTree = "<group>"; };
|
||||
0B987FC3FDBAA0E1C5AA235C /* PaginationIndicatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIndicatorRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSenderAvatarView.swift; sourceTree = "<group>"; };
|
||||
@ -1056,6 +1065,7 @@
|
||||
24F5530B2212862FA4BEFF2D /* HomeScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemProxy.swift; sourceTree = "<group>"; };
|
||||
260004737C573A56FA01E86E /* Encodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encodable.swift; sourceTree = "<group>"; };
|
||||
2757B1BE23DF8AA239937243 /* AudioConverterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioConverterProtocol.swift; sourceTree = "<group>"; };
|
||||
277C20CDD5B64510401B6D0D /* ServerConfigurationScreenViewStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfigurationScreenViewStateTests.swift; sourceTree = "<group>"; };
|
||||
27A1AD6389A4659AF0CEAE62 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = "<group>"; };
|
||||
27A9E3FBE8A66B5A17AD7F74 /* AppRoutes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRoutes.swift; sourceTree = "<group>"; };
|
||||
@ -1070,13 +1080,13 @@
|
||||
2AB2C848BB9A7A9B618B7B89 /* TextBasedRoomTimelineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextBasedRoomTimelineTests.swift; sourceTree = "<group>"; };
|
||||
2AF715D4FD4710EBB637D661 /* SettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
2BB385E148DE55C85C0A02D6 /* SoftLogoutScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenModels.swift; sourceTree = "<group>"; };
|
||||
2BDB3E65A79779EDA5D33D8A /* AudioPlayerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerState.swift; sourceTree = "<group>"; };
|
||||
2BFDCA5A09EE70BC17F2EFA7 /* URLComponents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLComponents.swift; sourceTree = "<group>"; };
|
||||
2C0197EAE9D45A662B8847B6 /* RoomTimelineControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerProtocol.swift; sourceTree = "<group>"; };
|
||||
2CA028DCD4157F9A1F999827 /* BackgroundTaskProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTaskProtocol.swift; sourceTree = "<group>"; };
|
||||
2CEBCB9676FCD1D0F13188DD /* StringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringTests.swift; sourceTree = "<group>"; };
|
||||
2D0946F77B696176E062D037 /* RoomMembersListScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenModels.swift; sourceTree = "<group>"; };
|
||||
2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemProxy.swift; sourceTree = "<group>"; };
|
||||
2EDDE503C9BAC82B661E4164 /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = "<group>"; };
|
||||
2EFE1922F39398ABFB36DF3F /* RoomDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsViewModelTests.swift; sourceTree = "<group>"; };
|
||||
2F36C5D9B37E50915ECBD3EE /* RoomMemberProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberProxy.swift; sourceTree = "<group>"; };
|
||||
2FD0E68C42CA7DDCD4CAD68D /* MentionSuggestionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionSuggestionItemView.swift; sourceTree = "<group>"; };
|
||||
@ -1243,6 +1253,7 @@
|
||||
6493AC9979CEB1410302BFE3 /* RoomDetailsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
64F49FB9EE2913234F06CE68 /* MediaPickerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPickerScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
653610CB5F9776EAAAB98155 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = fr; path = fr.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
6569593FA36B22259E806A67 /* AudioRecorderState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecorderState.swift; sourceTree = "<group>"; };
|
||||
65AAD845E53B0C8B5E0812C2 /* UserDiscoveryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDiscoveryService.swift; sourceTree = "<group>"; };
|
||||
65C2B80DD0BF6F10BB5FA922 /* MockAuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAuthenticationServiceProxy.swift; sourceTree = "<group>"; };
|
||||
6654859746B0BE9611459391 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
@ -1307,6 +1318,7 @@
|
||||
7A5D2323D7B6BF4913EB7EED /* landscape_test_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = landscape_test_image.jpg; sourceTree = "<group>"; };
|
||||
7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportViewModelTests.swift; sourceTree = "<group>"; };
|
||||
7B04BD3874D736127A8156B8 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
7B19B2BCC779ED934E0BBC2A /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = "<group>"; };
|
||||
7B25F959A434BB9923A3223F /* ExpiringTaskRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringTaskRunner.swift; sourceTree = "<group>"; };
|
||||
7B849D2FF2CC12BA411A1651 /* CreateRoomModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomModels.swift; sourceTree = "<group>"; };
|
||||
7B9FCA1CFD07B8CF9BD21266 /* FlowCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowCoordinatorProtocol.swift; sourceTree = "<group>"; };
|
||||
@ -1370,6 +1382,7 @@
|
||||
8F61A0DD8243B395499C99A2 /* InvitesScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitesScreenUITests.swift; sourceTree = "<group>"; };
|
||||
8F7D42E66E939B709C1EC390 /* MockRoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomSummaryProvider.swift; sourceTree = "<group>"; };
|
||||
8FC803282F9268D49F4ABF14 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = "<group>"; };
|
||||
907FA4DE17DEA1A3738EFB83 /* AudioRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecorder.swift; sourceTree = "<group>"; };
|
||||
90A55430639712CFACA34F43 /* TextRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
90F2F8998E5632668B0AD848 /* RoomTimelineItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineItemView.swift; sourceTree = "<group>"; };
|
||||
913C8E13B8B602C7B6C0C4AE /* PillTextAttachmentData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillTextAttachmentData.swift; sourceTree = "<group>"; };
|
||||
@ -1451,6 +1464,7 @@
|
||||
AC3F82523D6F48B926D6AF68 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
|
||||
AC4F10BDD56FA77FEC742333 /* VoiceMessageMediaManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageMediaManagerTests.swift; sourceTree = "<group>"; };
|
||||
AC5F5209279A752D98AAC4B2 /* CollapsibleFlowLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleFlowLayoutTests.swift; sourceTree = "<group>"; };
|
||||
AC9104846487244648D32C6D /* AudioPlayerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerProtocol.swift; sourceTree = "<group>"; };
|
||||
ACCC1874C122E2BBE648B8F5 /* LegalInformationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenUITests.swift; sourceTree = "<group>"; };
|
||||
AD378D580A41E42560C60E9C /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
AD6B522BD637845AB9570B10 /* RoomNotificationSettingsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsProxy.swift; sourceTree = "<group>"; };
|
||||
@ -1505,6 +1519,7 @@
|
||||
BA919F521E9F0EE3638AFC85 /* BugReportScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreen.swift; sourceTree = "<group>"; };
|
||||
BB23BEAF8831DC6A57E39F52 /* CreatePollScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePollScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
BBEC57C204D77908E355EF42 /* AudioRecorderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecorderProtocol.swift; sourceTree = "<group>"; };
|
||||
BC8AA23D4F37CC26564F63C5 /* LayoutMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutMocks.swift; sourceTree = "<group>"; };
|
||||
BC930E5F7F138112CAE5AC63 /* AppLockScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockScreenModels.swift; sourceTree = "<group>"; };
|
||||
BCF54536699ACEE3DB6BA3CB /* CompletionSuggestionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionSuggestionService.swift; sourceTree = "<group>"; };
|
||||
@ -1514,6 +1529,7 @@
|
||||
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>"; };
|
||||
BF34A2FD6797535C95AC918D /* PlaceholderScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
BFC9F57320EC80C7CE34FE4A /* VoiceMessagePreviewComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessagePreviewComposer.swift; sourceTree = "<group>"; };
|
||||
BFDCAC6CAAD65A2C24EA9C4B /* Dictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = "<group>"; };
|
||||
BFEA446F8618DBA79A9239CC /* MessageForwardingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreen.swift; sourceTree = "<group>"; };
|
||||
C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXAttributeScope.swift; sourceTree = "<group>"; };
|
||||
@ -1535,6 +1551,7 @@
|
||||
C4C89820BB2B88D4EA28131C /* BugReportScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
C4CD503F5E0938FE53C7C6E7 /* UserDetailsEditScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailsEditScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
C54464351F170D570110AFCA /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = "<group>"; };
|
||||
C55CC239AE12339C565F6C9A /* AudioRecorderStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecorderStateTests.swift; sourceTree = "<group>"; };
|
||||
C55D7E514F9DE4E3D72FDCAD /* SessionVerificationControllerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationControllerProxy.swift; sourceTree = "<group>"; };
|
||||
C5B7A755E985FA14469E86B2 /* RoomMembersListScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenUITests.swift; sourceTree = "<group>"; };
|
||||
C5F06F2F09B2EDD067DC2174 /* NotificationSettingsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreen.swift; sourceTree = "<group>"; };
|
||||
@ -1570,13 +1587,13 @@
|
||||
CC680E0E79D818706CB28CF8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
CC743C7A85E3171BCBF0A653 /* AvatarHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarHeaderView.swift; sourceTree = "<group>"; };
|
||||
CCACD75595C40EACD6AD4A74 /* AuthenticationTextFieldStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationTextFieldStyle.swift; sourceTree = "<group>"; };
|
||||
CCB6F36CCE44A29A06FCAF1C /* VoiceMessageRecordingComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingComposer.swift; sourceTree = "<group>"; };
|
||||
CD469F7513574341181F7EAA /* ServerSelectionScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreen.swift; sourceTree = "<group>"; };
|
||||
CD6613DE16AD26B3A74DA1F5 /* LocationRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationRoomTimelineItemContent.swift; sourceTree = "<group>"; };
|
||||
CD6B0C4639E066915B5E6463 /* target.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = target.yml; sourceTree = "<group>"; };
|
||||
CD700E035C85738EE4B97129 /* PerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformanceTests.swift; sourceTree = "<group>"; };
|
||||
CD95B3714F806AC9CF9A557B /* ComposerToolbarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarViewModel.swift; sourceTree = "<group>"; };
|
||||
CDB3227C7A74B734924942E9 /* RoomSummaryProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomSummaryProvider.swift; sourceTree = "<group>"; };
|
||||
CE1CD5EC6265A09315772DB7 /* AudioConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioConverter.swift; sourceTree = "<group>"; };
|
||||
CEE0E6043EFCF6FD2A341861 /* TimelineReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineReplyView.swift; sourceTree = "<group>"; };
|
||||
CEE41494C837AA403A06A5D9 /* UnitTests.xctestplan */ = {isa = PBXFileReference; path = UnitTests.xctestplan; sourceTree = "<group>"; };
|
||||
CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = "<group>"; };
|
||||
@ -1591,6 +1608,7 @@
|
||||
D1BC84BA0AF11C2128D58ABD /* Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = "<group>"; };
|
||||
D263254AFE5B7993FFBBF324 /* NSE.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NSE.entitlements; sourceTree = "<group>"; };
|
||||
D26813CCE39221FE30BF22CD /* PlatformViewVersionPredicate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformViewVersionPredicate.swift; sourceTree = "<group>"; };
|
||||
D2E61DDB42C0DE429C0955D8 /* VoiceMessageRecordingButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingButton.swift; sourceTree = "<group>"; };
|
||||
D316BB02636AF2174F2580E6 /* SoftLogoutScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
D33116993D54FADC0C721C1F /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
||||
D38391154120264910D19528 /* PollMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollMock.swift; sourceTree = "<group>"; };
|
||||
@ -1641,6 +1659,7 @@
|
||||
E413F4CBD7BF0588F394A9DD /* RoomDetailsEditScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
E43005941B3A2C9671E23C85 /* UserIndicatorModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserIndicatorModalView.swift; sourceTree = "<group>"; };
|
||||
E44928D844E16EE48A311FCA /* ComposerToolbarViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerToolbarViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
E44E35AA87F49503E7B3BF6E /* AudioConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioConverter.swift; sourceTree = "<group>"; };
|
||||
E461B3C8BBBFCA400B268D14 /* AppRouteURLParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteURLParserTests.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@ -1709,9 +1728,7 @@
|
||||
F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
F9ED8E731E21055F728E5FED /* TimelineStartRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStartRoomTimelineView.swift; sourceTree = "<group>"; };
|
||||
FA7BB497B2F539C17E88F6B7 /* NotificationSettingsEditScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
FA88C615D8BFCCEF0D2FEAC9 /* AudioPlayerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerState.swift; sourceTree = "<group>"; };
|
||||
FB0D6CB491777E7FC6B5BA12 /* CreateRoomScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreen.swift; sourceTree = "<group>"; };
|
||||
FB4D7B3FBCA261927536FE64 /* AudioConverterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioConverterProtocol.swift; sourceTree = "<group>"; };
|
||||
FB7BAD55A4E2B8E5828CD64C /* SoftLogoutScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftLogoutScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
FBB0328F2887BF0A65BC5D49 /* NotificationSettingsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsEditScreen.swift; sourceTree = "<group>"; };
|
||||
FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorProtocol.swift; sourceTree = "<group>"; };
|
||||
@ -1722,6 +1739,7 @@
|
||||
FE87C931165F5E201CACBB87 /* MediaPlayerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerProtocol.swift; sourceTree = "<group>"; };
|
||||
FEC2E8E1B20BB2EA07B0B61E /* WelcomeScreenScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreenScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
FEFEEE93B82937B2E86F92EB /* AnalyticsScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsScreen.swift; sourceTree = "<group>"; };
|
||||
FF449205DF1E9817115245C4 /* VoiceMessageRecordingButtonTooltipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingButtonTooltipView.swift; sourceTree = "<group>"; };
|
||||
FFECCE59967018204876D0A5 /* LocationMarkerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationMarkerView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@ -1850,6 +1868,16 @@
|
||||
path = RoomNotificationSettingsScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0371482D36C95ABAF9D4C651 /* Recorder */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
907FA4DE17DEA1A3738EFB83 /* AudioRecorder.swift */,
|
||||
BBEC57C204D77908E355EF42 /* AudioRecorderProtocol.swift */,
|
||||
6569593FA36B22259E806A67 /* AudioRecorderState.swift */,
|
||||
);
|
||||
path = Recorder;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
040A58C2A22F7195740EBF5C /* NCE */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1896,7 +1924,7 @@
|
||||
children = (
|
||||
4BF8D11D9ED15CFC373D0119 /* Analytics */,
|
||||
7803E03F759061C948D66B7E /* AppLock */,
|
||||
984A887BA0294FE3B00CE9B1 /* AudioPlayer */,
|
||||
FCE7249621F507F34A8122FB /* Audio */,
|
||||
AAFDD509929A0CCF8BCE51EB /* Authentication */,
|
||||
EBBEB5471737E9D116DF4738 /* Background */,
|
||||
0ED3F5C21537519389C07644 /* BugReport */,
|
||||
@ -2281,6 +2309,16 @@
|
||||
path = Emojis;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3A2CAA4ABF5E66C3C8BBA3E9 /* Player */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7B19B2BCC779ED934E0BBC2A /* AudioPlayer.swift */,
|
||||
AC9104846487244648D32C6D /* AudioPlayerProtocol.swift */,
|
||||
2BDB3E65A79779EDA5D33D8A /* AudioPlayerState.swift */,
|
||||
);
|
||||
path = Player;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3A304097A59704AC9B869EC6 /* Helpers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2491,6 +2529,11 @@
|
||||
2FD0E68C42CA7DDCD4CAD68D /* MentionSuggestionItemView.swift */,
|
||||
A0A01AECFF54281CF35909A6 /* MessageComposer.swift */,
|
||||
3E6A9B9DFEE964962C179DE3 /* RoomAttachmentPicker.swift */,
|
||||
BFC9F57320EC80C7CE34FE4A /* VoiceMessagePreviewComposer.swift */,
|
||||
D2E61DDB42C0DE429C0955D8 /* VoiceMessageRecordingButton.swift */,
|
||||
FF449205DF1E9817115245C4 /* VoiceMessageRecordingButtonTooltipView.swift */,
|
||||
CCB6F36CCE44A29A06FCAF1C /* VoiceMessageRecordingComposer.swift */,
|
||||
0A634D8DD1E10D858CF7995D /* VoiceMessageRecordingView.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
@ -2820,6 +2863,7 @@
|
||||
AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */,
|
||||
37CA26F55123E36B50DB0B3A /* AttributedStringTests.swift */,
|
||||
89233612A8632AD7E2803620 /* AudioPlayerStateTests.swift */,
|
||||
C55CC239AE12339C565F6C9A /* AudioRecorderStateTests.swift */,
|
||||
6DFCAA239095A116976E32C4 /* BackgroundTaskTests.swift */,
|
||||
EFFD3200F9960D4996159F10 /* BugReportServiceTests.swift */,
|
||||
7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */,
|
||||
@ -3346,18 +3390,6 @@
|
||||
path = TimelineItems;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
984A887BA0294FE3B00CE9B1 /* AudioPlayer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE1CD5EC6265A09315772DB7 /* AudioConverter.swift */,
|
||||
FB4D7B3FBCA261927536FE64 /* AudioConverterProtocol.swift */,
|
||||
2EDDE503C9BAC82B661E4164 /* AudioPlayer.swift */,
|
||||
048A3590E8379BCED4D30D5C /* AudioPlayerProtocol.swift */,
|
||||
FA88C615D8BFCCEF0D2FEAC9 /* AudioPlayerState.swift */,
|
||||
);
|
||||
path = AudioPlayer;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
99B9B46F2D621380428E68F7 /* ElementX */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -4146,6 +4178,17 @@
|
||||
path = Timeline;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FCE7249621F507F34A8122FB /* Audio */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E44E35AA87F49503E7B3BF6E /* AudioConverter.swift */,
|
||||
2757B1BE23DF8AA239937243 /* AudioConverterProtocol.swift */,
|
||||
3A2CAA4ABF5E66C3C8BBA3E9 /* Player */,
|
||||
0371482D36C95ABAF9D4C651 /* Recorder */,
|
||||
);
|
||||
path = Audio;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -4641,6 +4684,7 @@
|
||||
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */,
|
||||
5100F53E6884A15F9BA07CC3 /* AttributedStringTests.swift in Sources */,
|
||||
C1429699A6A5BB09A25775C1 /* AudioPlayerStateTests.swift in Sources */,
|
||||
3042527CB344A9EF1157FC26 /* AudioRecorderStateTests.swift in Sources */,
|
||||
0F9E38A75337D0146652ACAB /* BackgroundTaskTests.swift in Sources */,
|
||||
7F61F9ACD5EC9E845EF3EFBF /* BugReportServiceTests.swift in Sources */,
|
||||
C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */,
|
||||
@ -4788,11 +4832,14 @@
|
||||
D7CDBAE82782BD0529DECB5F /* AttributedString.swift in Sources */,
|
||||
3ED2725734568F6B8CC87544 /* AttributedStringBuilder.swift in Sources */,
|
||||
A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */,
|
||||
5F8E96263497FFB7D3254EB2 /* AudioConverter.swift in Sources */,
|
||||
8418688282763F4B9DDC42FB /* AudioConverterProtocol.swift in Sources */,
|
||||
CD1C6943F42F29079E5E7511 /* AudioPlayer.swift in Sources */,
|
||||
F0B196905CD23E3B4505CB7B /* AudioPlayerProtocol.swift in Sources */,
|
||||
C19085A284D54A166A64A86C /* AudioPlayerState.swift in Sources */,
|
||||
2DA27D78560D5F79B917E163 /* AudioConverter.swift in Sources */,
|
||||
F3F38062C6CA21CF403C5C90 /* AudioConverterProtocol.swift in Sources */,
|
||||
46C9F8FE3810A04A005FE16B /* AudioPlayer.swift in Sources */,
|
||||
086D01E79C8E8D3F004FAF21 /* AudioPlayerProtocol.swift in Sources */,
|
||||
1471A080552631358D152C18 /* AudioPlayerState.swift in Sources */,
|
||||
62A7FC3A0191BC7181AA432B /* AudioRecorder.swift in Sources */,
|
||||
3C31E1A65EEB61E72E1113B4 /* AudioRecorderProtocol.swift in Sources */,
|
||||
5992EF10AA157EBD97D88910 /* AudioRecorderState.swift in Sources */,
|
||||
F8E725D42023ECA091349245 /* AudioRoomTimelineItem.swift in Sources */,
|
||||
88F348E2CB14FF71CBBB665D /* AudioRoomTimelineItemContent.swift in Sources */,
|
||||
E62EC30B39354A391E32A126 /* AudioRoomTimelineView.swift in Sources */,
|
||||
@ -5316,6 +5363,11 @@
|
||||
4F2DF6138E87A4B8C2488CA3 /* VoiceMessageCacheProtocol.swift in Sources */,
|
||||
386720B603F87D156DB01FB2 /* VoiceMessageMediaManager.swift in Sources */,
|
||||
9DE801D278AC34737467F937 /* VoiceMessageMediaManagerProtocol.swift in Sources */,
|
||||
33CA777C9DF263582D77A67F /* VoiceMessagePreviewComposer.swift in Sources */,
|
||||
09EF4222EEBBA1A7B8F4071E /* VoiceMessageRecordingButton.swift in Sources */,
|
||||
8C27BEB00B903D953F31F962 /* VoiceMessageRecordingButtonTooltipView.swift in Sources */,
|
||||
CA5BFF0C2EF5A8EF40CA2D69 /* VoiceMessageRecordingComposer.swift in Sources */,
|
||||
EBDB339A7C127F068B6E52E5 /* VoiceMessageRecordingView.swift in Sources */,
|
||||
A9482B967FC85DA611514D35 /* VoiceMessageRoomPlaybackView.swift in Sources */,
|
||||
024E70451A7CD9E4E034D8A9 /* VoiceMessageRoomTimelineItem.swift in Sources */,
|
||||
87B4E59A4467F8EC75F82372 /* VoiceMessageRoomTimelineView.swift in Sources */,
|
||||
|
16
ElementX/Resources/Assets.xcassets/images/composer/mic-fill.imageset/Contents.json
vendored
Normal file
16
ElementX/Resources/Assets.xcassets/images/composer/mic-fill.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "mic-fill.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true,
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
5
ElementX/Resources/Assets.xcassets/images/composer/mic-fill.imageset/mic-fill.svg
vendored
Normal file
5
ElementX/Resources/Assets.xcassets/images/composer/mic-fill.imageset/mic-fill.svg
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="mic_FILL1_wght400_GRAD0_opsz20 1">
|
||||
<path id="Vector" d="M12.0294 14.15C11.1764 14.15 10.4458 13.8458 9.83746 13.2375C9.22913 12.6292 8.92496 11.9 8.92496 11.05V5.075C8.92496 4.22083 9.22913 3.49479 9.83746 2.89688C10.4458 2.29896 11.1764 2 12.0294 2C12.8823 2 13.6073 2.29896 14.2044 2.89688C14.8014 3.49479 15.1 4.22083 15.1 5.075V11.05C15.1 11.9 14.8014 12.6292 14.2044 13.2375C13.6073 13.8458 12.8823 14.15 12.0294 14.15ZM12.025 21C11.7416 21 11.4958 20.8958 11.2875 20.6875C11.0791 20.4792 10.975 20.2333 10.975 19.95V18.0047C9.44163 17.8016 8.14163 17.1625 7.07496 16.0875C6.0083 15.0125 5.34996 13.725 5.09996 12.225C5.04996 11.9153 5.13136 11.6421 5.34416 11.4052C5.55698 11.1684 5.84225 11.05 6.19996 11.05C6.4333 11.05 6.64163 11.1333 6.82496 11.3C7.0083 11.4667 7.12496 11.6833 7.17496 11.95C7.38833 13.0953 7.94636 14.0525 8.84906 14.8215C9.75178 15.5905 10.8104 15.975 12.025 15.975C13.2332 15.975 14.2864 15.5905 15.1845 14.8215C16.0825 14.0525 16.6377 13.0953 16.85 11.95C16.9 11.6833 17.0166 11.4667 17.2 11.3C17.3833 11.1333 17.6003 11.05 17.8509 11.05C18.191 11.05 18.4675 11.1684 18.6805 11.4052C18.8935 11.6421 18.975 11.9153 18.925 12.225C18.6949 13.7279 18.0457 15.0162 16.9774 16.0897C15.9091 17.1632 14.6083 17.8016 13.075 18.0047V19.95C13.075 20.2333 12.9708 20.4792 12.7625 20.6875C12.5541 20.8958 12.3083 21 12.025 21Z" fill="#1B1D22"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
16
ElementX/Resources/Assets.xcassets/images/composer/mic.imageset/Contents.json
vendored
Normal file
16
ElementX/Resources/Assets.xcassets/images/composer/mic.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "mic.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true,
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
5
ElementX/Resources/Assets.xcassets/images/composer/mic.imageset/mic.svg
vendored
Normal file
5
ElementX/Resources/Assets.xcassets/images/composer/mic.imageset/mic.svg
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="mic_FILL0_wght400_GRAD0_opsz24 1">
|
||||
<path id="Vector" d="M12.0001 14C11.1667 14 10.4584 13.7083 9.87506 13.125C9.29173 12.5417 9.00006 11.8333 9.00006 11V5C9.00006 4.16667 9.29173 3.45833 9.87506 2.875C10.4584 2.29167 11.1667 2 12.0001 2C12.8334 2 13.5417 2.29167 14.1251 2.875C14.7084 3.45833 15.0001 4.16667 15.0001 5V11C15.0001 11.8333 14.7084 12.5417 14.1251 13.125C13.5417 13.7083 12.8334 14 12.0001 14ZM12.0001 21C11.7167 21 11.4792 20.9042 11.2876 20.7125C11.0959 20.5208 11.0001 20.2833 11.0001 20V17.925C9.45006 17.7083 8.13339 17.0583 7.05006 15.975C5.96673 14.8917 5.30839 13.5917 5.07506 12.075C5.02506 11.7917 5.10006 11.5417 5.30006 11.325C5.50006 11.1083 5.76673 11 6.10006 11C6.33339 11 6.54173 11.0875 6.72506 11.2625C6.90839 11.4375 7.02506 11.65 7.07506 11.9C7.29173 13.0667 7.85839 14.0417 8.77506 14.825C9.69173 15.6083 10.7667 16 12.0001 16C13.2334 16 14.3084 15.6083 15.2251 14.825C16.1417 14.0417 16.7084 13.0667 16.9251 11.9C16.9751 11.65 17.0959 11.4375 17.2876 11.2625C17.4792 11.0875 17.6917 11 17.9251 11C18.2417 11 18.5001 11.1083 18.7001 11.325C18.9001 11.5417 18.9751 11.7917 18.9251 12.075C18.6917 13.5917 18.0334 14.8917 16.9501 15.975C15.8667 17.0583 14.5501 17.7083 13.0001 17.925V20C13.0001 20.2833 12.9042 20.5208 12.7126 20.7125C12.5209 20.9042 12.2834 21 12.0001 21ZM12.0001 12C12.2834 12 12.5209 11.9042 12.7126 11.7125C12.9042 11.5208 13.0001 11.2833 13.0001 11V5C13.0001 4.71667 12.9042 4.47917 12.7126 4.2875C12.5209 4.09583 12.2834 4 12.0001 4C11.7167 4 11.4792 4.09583 11.2876 4.2875C11.0959 4.47917 11.0001 4.71667 11.0001 5V11C11.0001 11.2833 11.0959 11.5208 11.2876 11.7125C11.4792 11.9042 11.7167 12 12.0001 12Z" fill="#656D77"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -17,4 +17,3 @@
|
||||
"soft_logout_clear_data_submit" = "Clear all data";
|
||||
"soft_logout_clear_data_dialog_title" = "Clear data";
|
||||
"soft_logout_clear_data_dialog_content" = "Clear all data currently stored on this device?\nSign in again to access your account data and messages.";
|
||||
|
||||
|
@ -49,6 +49,8 @@ internal enum Asset {
|
||||
internal static let inlineCode = ImageAsset(name: "images/inline-code")
|
||||
internal static let italic = ImageAsset(name: "images/italic")
|
||||
internal static let link = ImageAsset(name: "images/link")
|
||||
internal static let micFill = ImageAsset(name: "images/mic-fill")
|
||||
internal static let mic = ImageAsset(name: "images/mic")
|
||||
internal static let numberedList = ImageAsset(name: "images/numbered-list")
|
||||
internal static let quote = ImageAsset(name: "images/quote")
|
||||
internal static let sendMessage = ImageAsset(name: "images/send-message")
|
||||
|
@ -246,6 +246,74 @@ class AudioPlayerMock: AudioPlayerProtocol {
|
||||
await seekToClosure?(progress)
|
||||
}
|
||||
}
|
||||
class AudioRecorderMock: AudioRecorderProtocol {
|
||||
var actions: AnyPublisher<AudioRecorderAction, Never> {
|
||||
get { return underlyingActions }
|
||||
set(value) { underlyingActions = value }
|
||||
}
|
||||
var underlyingActions: AnyPublisher<AudioRecorderAction, Never>!
|
||||
var currentTime: TimeInterval {
|
||||
get { return underlyingCurrentTime }
|
||||
set(value) { underlyingCurrentTime = value }
|
||||
}
|
||||
var underlyingCurrentTime: TimeInterval!
|
||||
var isRecording: Bool {
|
||||
get { return underlyingIsRecording }
|
||||
set(value) { underlyingIsRecording = value }
|
||||
}
|
||||
var underlyingIsRecording: Bool!
|
||||
var url: URL?
|
||||
|
||||
//MARK: - recordWithOutputURL
|
||||
|
||||
var recordWithOutputURLCallsCount = 0
|
||||
var recordWithOutputURLCalled: Bool {
|
||||
return recordWithOutputURLCallsCount > 0
|
||||
}
|
||||
var recordWithOutputURLReceivedUrl: URL?
|
||||
var recordWithOutputURLReceivedInvocations: [URL] = []
|
||||
var recordWithOutputURLClosure: ((URL) -> Void)?
|
||||
|
||||
func recordWithOutputURL(_ url: URL) {
|
||||
recordWithOutputURLCallsCount += 1
|
||||
recordWithOutputURLReceivedUrl = url
|
||||
recordWithOutputURLReceivedInvocations.append(url)
|
||||
recordWithOutputURLClosure?(url)
|
||||
}
|
||||
//MARK: - stopRecording
|
||||
|
||||
var stopRecordingCallsCount = 0
|
||||
var stopRecordingCalled: Bool {
|
||||
return stopRecordingCallsCount > 0
|
||||
}
|
||||
var stopRecordingClosure: (() -> Void)?
|
||||
|
||||
func stopRecording() {
|
||||
stopRecordingCallsCount += 1
|
||||
stopRecordingClosure?()
|
||||
}
|
||||
//MARK: - averagePowerForChannelNumber
|
||||
|
||||
var averagePowerForChannelNumberCallsCount = 0
|
||||
var averagePowerForChannelNumberCalled: Bool {
|
||||
return averagePowerForChannelNumberCallsCount > 0
|
||||
}
|
||||
var averagePowerForChannelNumberReceivedChannelNumber: Int?
|
||||
var averagePowerForChannelNumberReceivedInvocations: [Int] = []
|
||||
var averagePowerForChannelNumberReturnValue: Float!
|
||||
var averagePowerForChannelNumberClosure: ((Int) -> Float)?
|
||||
|
||||
func averagePowerForChannelNumber(_ channelNumber: Int) -> Float {
|
||||
averagePowerForChannelNumberCallsCount += 1
|
||||
averagePowerForChannelNumberReceivedChannelNumber = channelNumber
|
||||
averagePowerForChannelNumberReceivedInvocations.append(channelNumber)
|
||||
if let averagePowerForChannelNumberClosure = averagePowerForChannelNumberClosure {
|
||||
return averagePowerForChannelNumberClosure(channelNumber)
|
||||
} else {
|
||||
return averagePowerForChannelNumberReturnValue
|
||||
}
|
||||
}
|
||||
}
|
||||
class BugReportServiceMock: BugReportServiceProtocol {
|
||||
var isRunning: Bool {
|
||||
get { return underlyingIsRunning }
|
||||
|
@ -30,6 +30,11 @@ enum ComposerToolbarViewModelAction {
|
||||
|
||||
case composerModeChanged(mode: RoomScreenComposerMode)
|
||||
case composerFocusedChanged(isFocused: Bool)
|
||||
|
||||
case startRecordingVoiceMessage
|
||||
case stopRecordingVoiceMessage
|
||||
case deleteRecordedVoiceMessage
|
||||
case sendVoiceMessage
|
||||
}
|
||||
|
||||
enum ComposerToolbarViewAction {
|
||||
@ -46,6 +51,9 @@ enum ComposerToolbarViewAction {
|
||||
case enableTextFormatting
|
||||
case composerAction(action: ComposerAction)
|
||||
case selectedSuggestion(_ suggestion: SuggestionItem)
|
||||
case startRecordingVoiceMessage
|
||||
case stopRecordingVoiceMessage
|
||||
case deleteRecordedVoiceMessage
|
||||
}
|
||||
|
||||
struct ComposerToolbarViewState: BindableState {
|
||||
@ -53,11 +61,33 @@ struct ComposerToolbarViewState: BindableState {
|
||||
var composerEmpty = true
|
||||
var areSuggestionsEnabled = true
|
||||
var suggestions: [SuggestionItem] = []
|
||||
|
||||
|
||||
var enableVoiceMessageComposer: Bool
|
||||
var audioPlayerState: AudioPlayerState
|
||||
var audioRecorderState: AudioRecorderState
|
||||
|
||||
var bindings: ComposerToolbarViewStateBindings
|
||||
|
||||
var showSendButton: Bool {
|
||||
switch composerMode {
|
||||
case .recordVoiceMessage:
|
||||
return false
|
||||
case .previewVoiceMessage:
|
||||
return true
|
||||
default:
|
||||
if enableVoiceMessageComposer {
|
||||
return !composerEmpty
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sendButtonDisabled: Bool {
|
||||
composerEmpty
|
||||
if case .previewVoiceMessage = composerMode {
|
||||
return false
|
||||
}
|
||||
return composerEmpty
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,12 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
||||
self.completionSuggestionService = completionSuggestionService
|
||||
self.appSettings = appSettings
|
||||
|
||||
super.init(initialViewState: ComposerToolbarViewState(areSuggestionsEnabled: completionSuggestionService.areSuggestionsEnabled, bindings: .init()), imageProvider: mediaProvider)
|
||||
super.init(initialViewState: ComposerToolbarViewState(areSuggestionsEnabled: completionSuggestionService.areSuggestionsEnabled,
|
||||
enableVoiceMessageComposer: appSettings.voiceMessageEnabled,
|
||||
audioPlayerState: .init(duration: 0),
|
||||
audioRecorderState: .init(),
|
||||
bindings: .init()),
|
||||
imageProvider: mediaProvider)
|
||||
|
||||
context.$viewState
|
||||
.map(\.composerMode)
|
||||
@ -98,10 +103,16 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
||||
wysiwygViewModel.setup()
|
||||
case .sendMessage:
|
||||
guard !state.sendButtonDisabled else { return }
|
||||
let sendHTML = ServiceLocator.shared.settings.richTextEditorEnabled
|
||||
actionsSubject.send(.sendMessage(plain: wysiwygViewModel.content.markdown,
|
||||
html: sendHTML ? wysiwygViewModel.content.html : nil,
|
||||
mode: state.composerMode))
|
||||
|
||||
switch state.composerMode {
|
||||
case .previewVoiceMessage:
|
||||
actionsSubject.send(.sendVoiceMessage)
|
||||
default:
|
||||
let sendHTML = ServiceLocator.shared.settings.richTextEditorEnabled
|
||||
actionsSubject.send(.sendMessage(plain: wysiwygViewModel.content.markdown,
|
||||
html: sendHTML ? wysiwygViewModel.content.html : nil,
|
||||
mode: state.composerMode))
|
||||
}
|
||||
case .cancelReply:
|
||||
set(mode: .default)
|
||||
case .cancelEdit:
|
||||
@ -130,6 +141,13 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
||||
}
|
||||
case .selectedSuggestion(let suggestion):
|
||||
handleSuggestion(suggestion)
|
||||
case .startRecordingVoiceMessage:
|
||||
state.bindings.composerActionsEnabled = false
|
||||
actionsSubject.send(.startRecordingVoiceMessage)
|
||||
case .stopRecordingVoiceMessage:
|
||||
actionsSubject.send(.stopRecordingVoiceMessage)
|
||||
case .deleteRecordedVoiceMessage:
|
||||
actionsSubject.send(.deleteRecordedVoiceMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +215,15 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
|
||||
guard mode != state.composerMode else { return }
|
||||
|
||||
state.composerMode = mode
|
||||
if mode != .default {
|
||||
switch mode {
|
||||
case .default:
|
||||
break
|
||||
case .recordVoiceMessage(let audioRecorderState):
|
||||
state.bindings.composerFocused = false
|
||||
state.audioRecorderState = audioRecorderState
|
||||
case .previewVoiceMessage(let audioPlayerState):
|
||||
state.audioPlayerState = audioPlayerState
|
||||
case .edit, .reply:
|
||||
// Focus composer when switching to reply/edit
|
||||
state.bindings.composerFocused = true
|
||||
}
|
||||
|
@ -22,16 +22,21 @@ struct ComposerToolbar: View {
|
||||
@ObservedObject var context: ComposerToolbarViewModel.Context
|
||||
let wysiwygViewModel: WysiwygComposerViewModel
|
||||
let keyCommandHandler: KeyCommandHandler
|
||||
|
||||
|
||||
@FocusState private var composerFocused: Bool
|
||||
@ScaledMetric private var sendButtonIconSize = 16
|
||||
@ScaledMetric private var trashButtonIconSize = 24
|
||||
@ScaledMetric(relativeTo: .title) private var closeRTEButtonSize = 30
|
||||
|
||||
@State private var showVoiceMessageRecordingTooltip = false
|
||||
@ScaledMetric private var voiceMessageTooltipPointerHeight = 6
|
||||
|
||||
@State private var frame: CGRect = .zero
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 8) {
|
||||
topBar
|
||||
|
||||
if context.composerActionsEnabled {
|
||||
bottomBar
|
||||
}
|
||||
@ -45,6 +50,10 @@ struct ComposerToolbar: View {
|
||||
.offset(y: -frame.height)
|
||||
}
|
||||
}
|
||||
.overlay(alignment: .bottomTrailing) {
|
||||
voiceMessageRecordingButtonTooltipView
|
||||
.offset(y: -frame.height - voiceMessageTooltipPointerHeight)
|
||||
}
|
||||
.alert(item: $context.alertInfo)
|
||||
}
|
||||
|
||||
@ -53,42 +62,55 @@ struct ComposerToolbar: View {
|
||||
context.send(viewAction: .selectedSuggestion(suggestion))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var topBar: some View {
|
||||
HStack(alignment: .bottom, spacing: 5) {
|
||||
if !context.composerActionsEnabled {
|
||||
RoomAttachmentPicker(context: context)
|
||||
switch context.viewState.composerMode {
|
||||
case .recordVoiceMessage(let state) where context.viewState.enableVoiceMessageComposer:
|
||||
VoiceMessageRecordingComposer(recorderState: state)
|
||||
.padding(.leading, 12)
|
||||
case .previewVoiceMessage(let state) where context.viewState.enableVoiceMessageComposer:
|
||||
voiceMessageTrashButton
|
||||
VoiceMessagePreviewComposer(playerState: state)
|
||||
default:
|
||||
if !context.composerActionsEnabled {
|
||||
RoomAttachmentPicker(context: context)
|
||||
}
|
||||
messageComposer
|
||||
.environmentObject(context)
|
||||
.onTapGesture {
|
||||
guard !composerFocused else { return }
|
||||
composerFocused = true
|
||||
}
|
||||
.padding(.leading, context.composerActionsEnabled ? 7 : 0)
|
||||
.padding(.trailing, context.composerActionsEnabled ? 4 : 0)
|
||||
}
|
||||
|
||||
messageComposer
|
||||
.environmentObject(context)
|
||||
.onTapGesture {
|
||||
guard !composerFocused else { return }
|
||||
composerFocused = true
|
||||
}
|
||||
.padding(.leading, context.composerActionsEnabled ? 7 : 0)
|
||||
.padding(.trailing, context.composerActionsEnabled ? 4 : 0)
|
||||
|
||||
if !context.composerActionsEnabled {
|
||||
sendButton
|
||||
.padding(.leading, 3)
|
||||
if context.viewState.showSendButton {
|
||||
sendButton
|
||||
.padding(.leading, 3)
|
||||
} else if context.viewState.enableVoiceMessageComposer {
|
||||
voiceMessageRecordingButton
|
||||
.padding(.leading, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var bottomBar: some View {
|
||||
HStack(alignment: .center, spacing: 9) {
|
||||
closeRTEButton
|
||||
|
||||
|
||||
FormattingToolbar(formatItems: context.formatItems) { action in
|
||||
context.send(viewAction: .composerAction(action: action.composerAction))
|
||||
}
|
||||
|
||||
|
||||
sendButton
|
||||
.padding(.leading, 7)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var closeRTEButton: some View {
|
||||
Button {
|
||||
context.composerActionsEnabled = false
|
||||
@ -103,7 +125,7 @@ struct ComposerToolbar: View {
|
||||
.accessibilityLabel(L10n.actionClose)
|
||||
.accessibilityIdentifier(A11yIdentifiers.roomScreen.composerToolbar.closeFormattingOptions)
|
||||
}
|
||||
|
||||
|
||||
private var sendButton: some View {
|
||||
Button {
|
||||
context.send(viewAction: .sendMessage)
|
||||
@ -141,7 +163,7 @@ struct ComposerToolbar: View {
|
||||
.focused($composerFocused)
|
||||
.onChange(of: context.composerFocused) { newValue in
|
||||
guard composerFocused != newValue else { return }
|
||||
|
||||
|
||||
composerFocused = newValue
|
||||
}
|
||||
.onChange(of: composerFocused) { newValue in
|
||||
@ -167,7 +189,7 @@ struct ComposerToolbar: View {
|
||||
context.send(viewAction: .handlePasteOrDrop(provider: provider))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var submitButtonImage: some View {
|
||||
// ZStack with opacity so the button size is consistent.
|
||||
ZStack {
|
||||
@ -184,12 +206,44 @@ struct ComposerToolbar: View {
|
||||
.accessibilityHidden(context.viewState.composerMode.isEdit)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class ItemProviderHelper: WysiwygItemProviderHelper {
|
||||
func isPasteSupported(for itemProvider: NSItemProvider) -> Bool {
|
||||
itemProvider.isSupportedForPasteOrDrop
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Voice message
|
||||
|
||||
private var voiceMessageRecordingButton: some View {
|
||||
VoiceMessageRecordingButton(showRecordTooltip: $showVoiceMessageRecordingTooltip, startRecording: {
|
||||
context.send(viewAction: .startRecordingVoiceMessage)
|
||||
}, stopRecording: {
|
||||
context.send(viewAction: .stopRecordingVoiceMessage)
|
||||
})
|
||||
.padding(4)
|
||||
}
|
||||
|
||||
private var voiceMessageTrashButton: some View {
|
||||
Button {
|
||||
context.send(viewAction: .deleteRecordedVoiceMessage)
|
||||
} label: {
|
||||
CompoundIcon(\.delete)
|
||||
.font(.compound.bodyLG)
|
||||
.foregroundColor(.compound.textCriticalPrimary)
|
||||
.frame(width: trashButtonIconSize, height: trashButtonIconSize)
|
||||
.padding(EdgeInsets(top: 10, leading: 11, bottom: 10, trailing: 11))
|
||||
.fixedSize()
|
||||
.accessibilityLabel(L10n.a11yDelete)
|
||||
}
|
||||
}
|
||||
|
||||
private var voiceMessageRecordingButtonTooltipView: some View {
|
||||
VoiceMessageRecordingButtonTooltipView(text: L10n.screenRoomVoiceMessageTooltip, pointerHeight: voiceMessageTooltipPointerHeight)
|
||||
.allowsHitTesting(false)
|
||||
.opacity(showVoiceMessageRecordingTooltip ? 1.0 : 0.0)
|
||||
.animation(.elementDefault, value: showVoiceMessageRecordingTooltip)
|
||||
}
|
||||
}
|
||||
|
||||
struct ComposerToolbar_Previews: PreviewProvider, TestablePreview {
|
||||
@ -203,7 +257,7 @@ struct ComposerToolbar_Previews: PreviewProvider, TestablePreview {
|
||||
.user(item: MentionSuggestionItem(id: "@user_mention_2:matrix.org", displayName: "User 2", avatarURL: URL.documentsDirectory))]
|
||||
|
||||
static var previews: some View {
|
||||
ComposerToolbar.mock()
|
||||
ComposerToolbar.mock(focused: true)
|
||||
|
||||
// Putting them is VStack allows the completion suggestion preview to work properly in tests
|
||||
VStack {
|
||||
@ -213,19 +267,82 @@ struct ComposerToolbar_Previews: PreviewProvider, TestablePreview {
|
||||
keyCommandHandler: { _ in false })
|
||||
}
|
||||
.previewDisplayName("With Suggestions")
|
||||
|
||||
VStack {
|
||||
ComposerToolbar.textWithVoiceMessage(focused: false)
|
||||
ComposerToolbar.textWithVoiceMessage(focused: true)
|
||||
ComposerToolbar.voiceMessageRecordingMock(recording: true)
|
||||
ComposerToolbar.voiceMessagePreviewMock(recording: false)
|
||||
}
|
||||
.previewDisplayName("Voice Message")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Mock
|
||||
|
||||
extension ComposerToolbar {
|
||||
static func mock() -> ComposerToolbar {
|
||||
static func mock(focused: Bool = true) -> ComposerToolbar {
|
||||
let wysiwygViewModel = WysiwygComposerViewModel()
|
||||
let composerViewModel = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel,
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock)
|
||||
var composerViewModel: ComposerToolbarViewModel {
|
||||
let model = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel,
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock)
|
||||
model.state.composerEmpty = focused
|
||||
return model
|
||||
}
|
||||
return ComposerToolbar(context: composerViewModel.context,
|
||||
wysiwygViewModel: wysiwygViewModel,
|
||||
keyCommandHandler: { _ in false })
|
||||
}
|
||||
|
||||
static func textWithVoiceMessage(focused: Bool = true) -> ComposerToolbar {
|
||||
let wysiwygViewModel = WysiwygComposerViewModel()
|
||||
var composerViewModel: ComposerToolbarViewModel {
|
||||
let model = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel,
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock)
|
||||
model.state.composerEmpty = focused
|
||||
model.state.enableVoiceMessageComposer = true
|
||||
return model
|
||||
}
|
||||
return ComposerToolbar(context: composerViewModel.context,
|
||||
wysiwygViewModel: wysiwygViewModel,
|
||||
keyCommandHandler: { _ in false })
|
||||
}
|
||||
|
||||
static func voiceMessageRecordingMock(recording: Bool) -> ComposerToolbar {
|
||||
let wysiwygViewModel = WysiwygComposerViewModel()
|
||||
var composerViewModel: ComposerToolbarViewModel {
|
||||
let model = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel,
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock)
|
||||
model.state.composerMode = .recordVoiceMessage(state: AudioRecorderState())
|
||||
model.state.enableVoiceMessageComposer = true
|
||||
return model
|
||||
}
|
||||
return ComposerToolbar(context: composerViewModel.context,
|
||||
wysiwygViewModel: wysiwygViewModel,
|
||||
keyCommandHandler: { _ in false })
|
||||
}
|
||||
|
||||
static func voiceMessagePreviewMock(recording: Bool) -> ComposerToolbar {
|
||||
let wysiwygViewModel = WysiwygComposerViewModel()
|
||||
var composerViewModel: ComposerToolbarViewModel {
|
||||
let model = ComposerToolbarViewModel(wysiwygViewModel: wysiwygViewModel,
|
||||
completionSuggestionService: CompletionSuggestionServiceMock(configuration: .init()),
|
||||
mediaProvider: MockMediaProvider(),
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
mentionDisplayHelper: ComposerMentionDisplayHelper.mock)
|
||||
model.state.composerMode = .previewVoiceMessage(state: AudioPlayerState(duration: 10.0))
|
||||
model.state.enableVoiceMessageComposer = true
|
||||
return model
|
||||
}
|
||||
return ComposerToolbar(context: composerViewModel.context,
|
||||
wysiwygViewModel: wysiwygViewModel,
|
||||
keyCommandHandler: { _ in false })
|
||||
|
@ -90,7 +90,7 @@ struct MessageComposer: View {
|
||||
MessageComposerReplyHeader(replyDetails: replyDetails, action: replyCancellationAction)
|
||||
case .edit:
|
||||
MessageComposerEditHeader(action: editCancellationAction)
|
||||
case .default:
|
||||
case .recordVoiceMessage, .previewVoiceMessage, .default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,63 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct VoiceMessagePreviewComposer: View {
|
||||
@ObservedObject var playerState: AudioPlayerState
|
||||
|
||||
@State private var resumePlaybackAfterScrubbing = false
|
||||
|
||||
var body: some View {
|
||||
VoiceMessageRoomPlaybackView(playerState: playerState,
|
||||
onPlayPause: onPlaybackPlayPause,
|
||||
onSeek: { onPlaybackSeek($0) },
|
||||
onScrubbing: { onPlaybackScrubbing($0) })
|
||||
.padding(.vertical, 4.0)
|
||||
.padding(.horizontal, 6.0)
|
||||
.background {
|
||||
let roundedRectangle = RoundedRectangle(cornerRadius: 12)
|
||||
ZStack {
|
||||
roundedRectangle
|
||||
.fill(Color.compound.bgSubtleSecondary)
|
||||
roundedRectangle
|
||||
.stroke(Color.compound._borderTextFieldFocused, lineWidth: 0.5)
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 42)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
private func onPlaybackPlayPause() { }
|
||||
|
||||
private func onPlaybackSeek(_ progress: Double) { }
|
||||
|
||||
private func onPlaybackScrubbing(_ dragging: Bool) { }
|
||||
}
|
||||
|
||||
struct VoiceMessagePreviewComposer_Previews: PreviewProvider, TestablePreview {
|
||||
static let playerState = AudioPlayerState(duration: 10.0,
|
||||
waveform: Waveform.mockWaveform,
|
||||
progress: 0.4)
|
||||
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
VoiceMessagePreviewComposer(playerState: playerState)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
//
|
||||
// Copyright 2023 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 Compound
|
||||
import SwiftUI
|
||||
|
||||
struct VoiceMessageRecordingButton: View {
|
||||
@ScaledMetric private var buttonIconSize = 24
|
||||
@State private var longPressConfirmed = false
|
||||
@State private var buttonPressed = false
|
||||
@State private var longPressTask = VoiceMessageButtonTask()
|
||||
|
||||
private let feedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
|
||||
private let delayBeforeRecording = 500
|
||||
|
||||
@Binding var showRecordTooltip: Bool
|
||||
var startRecording: (() -> Void)?
|
||||
var stopRecording: (() -> Void)?
|
||||
|
||||
var body: some View {
|
||||
Button { } label: {
|
||||
voiceMessageButtonImage
|
||||
}
|
||||
.onLongPressGesture(perform: { }, onPressingChanged: { pressing in
|
||||
buttonPressed = pressing
|
||||
if pressing {
|
||||
showRecordTooltip = true
|
||||
feedbackGenerator.prepare()
|
||||
longPressTask.task = Task {
|
||||
try? await Task.sleep(for: .milliseconds(delayBeforeRecording))
|
||||
guard !Task.isCancelled else {
|
||||
return
|
||||
}
|
||||
feedbackGenerator.impactOccurred()
|
||||
showRecordTooltip = false
|
||||
longPressConfirmed = true
|
||||
startRecording?()
|
||||
}
|
||||
} else {
|
||||
longPressTask.task?.cancel()
|
||||
showRecordTooltip = false
|
||||
guard longPressConfirmed else { return }
|
||||
longPressConfirmed = false
|
||||
stopRecording?()
|
||||
}
|
||||
})
|
||||
.fixedSize()
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var voiceMessageButtonImage: some View {
|
||||
(buttonPressed ? Image(asset: Asset.Images.micFill) : Image(asset: Asset.Images.mic))
|
||||
.resizable()
|
||||
.frame(width: buttonIconSize, height: buttonIconSize)
|
||||
.foregroundColor(.compound.iconSecondary)
|
||||
.padding(EdgeInsets(top: 6, leading: 6, bottom: 6, trailing: 6))
|
||||
.accessibilityLabel(L10n.a11yVoiceMessageRecord)
|
||||
}
|
||||
}
|
||||
|
||||
private class VoiceMessageButtonTask {
|
||||
@CancellableTask var task: Task<Void, Never>?
|
||||
}
|
||||
|
||||
struct VoiceMessageRecordingButton_Previews: PreviewProvider, TestablePreview {
|
||||
static var previews: some View {
|
||||
VoiceMessageRecordingButton(showRecordTooltip: .constant(false))
|
||||
.fixedSize(horizontal: true, vertical: true)
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
//
|
||||
// Copyright 2023 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 Compound
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct VoiceMessageRecordingButtonTooltipView: View {
|
||||
var text: String
|
||||
var radius: CGFloat = 4
|
||||
var corners: UIRectCorner = .allCorners
|
||||
@ScaledMetric var pointerHeight: CGFloat = 6
|
||||
@ScaledMetric var pointerWidth: CGFloat = 10
|
||||
@ScaledMetric var pointerOffset: CGFloat = 8
|
||||
|
||||
var body: some View {
|
||||
Text(text)
|
||||
.font(.compound.bodySMSemibold)
|
||||
.foregroundColor(.compound.textOnSolidPrimary)
|
||||
.padding(6)
|
||||
.background(
|
||||
TooltipShape(radius: radius,
|
||||
corners: corners,
|
||||
pointerHeight: pointerHeight,
|
||||
pointerWidth: pointerWidth,
|
||||
pointerOffset: pointerOffset)
|
||||
.fill(.compound.bgActionPrimaryRest)
|
||||
)
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
}
|
||||
|
||||
private struct TooltipShape: Shape {
|
||||
var radius: CGFloat = 4
|
||||
var corners: UIRectCorner = .allCorners
|
||||
var pointerHeight: CGFloat = 6
|
||||
var pointerWidth: CGFloat = 10
|
||||
var pointerOffset: CGFloat = 8
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
var path = Path()
|
||||
|
||||
let width = rect.size.width
|
||||
let height = rect.size.height
|
||||
|
||||
var topLeft: CGFloat = corners.contains(.topLeft) ? radius : 0.0
|
||||
var topRight: CGFloat = corners.contains(.topRight) ? radius : 0.0
|
||||
var bottomLeft: CGFloat = corners.contains(.bottomLeft) ? radius : 0.0
|
||||
var bottomRight: CGFloat = corners.contains(.bottomRight) ? radius : 0.0
|
||||
|
||||
// Make sure we do not exceed the size of the rectangle
|
||||
topRight = min(min(topRight, height / 2), width / 2)
|
||||
topLeft = min(min(topLeft, height / 2), width / 2)
|
||||
bottomLeft = min(min(bottomLeft, height / 2), width / 2)
|
||||
bottomRight = min(min(bottomRight, height / 2), width / 2)
|
||||
|
||||
path.move(to: CGPoint(x: width / 2.0, y: 0))
|
||||
path.addLine(to: CGPoint(x: width - topRight, y: 0))
|
||||
path.addArc(center: CGPoint(x: width - topRight, y: topRight), radius: topRight,
|
||||
startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false)
|
||||
|
||||
path.addLine(to: CGPoint(x: width, y: height - bottomRight))
|
||||
path.addArc(center: CGPoint(x: width - bottomRight, y: height - bottomRight), radius: bottomRight,
|
||||
startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false)
|
||||
|
||||
path.addLine(to: CGPoint(x: width - pointerOffset, y: height))
|
||||
path.addLine(to: CGPoint(x: width - pointerOffset - (pointerWidth / 2.0), y: height + pointerHeight))
|
||||
path.addLine(to: CGPoint(x: width - pointerOffset - pointerWidth, y: height))
|
||||
|
||||
path.addLine(to: CGPoint(x: bottomLeft, y: height))
|
||||
path.addArc(center: CGPoint(x: bottomLeft, y: height - bottomLeft), radius: bottomLeft,
|
||||
startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false)
|
||||
|
||||
path.addLine(to: CGPoint(x: 0, y: topLeft))
|
||||
path.addArc(center: CGPoint(x: topLeft, y: topLeft), radius: topLeft,
|
||||
startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)
|
||||
path.closeSubpath()
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
struct VoiceMessageRecordingButtonTooltipView_Previews: PreviewProvider, TestablePreview {
|
||||
static var previews: some View {
|
||||
VoiceMessageRecordingButtonTooltipView(text: "Hold to record")
|
||||
.fixedSize()
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
//
|
||||
// Copyright 2023 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 Compound
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct VoiceMessageRecordingComposer: View {
|
||||
@ObservedObject var recorderState: AudioRecorderState
|
||||
|
||||
var body: some View {
|
||||
VoiceMessageRecordingView(recorderState: recorderState)
|
||||
.padding(.vertical, 8.0)
|
||||
.padding(.horizontal, 12.0)
|
||||
.background {
|
||||
let roundedRectangle = RoundedRectangle(cornerRadius: 12)
|
||||
ZStack {
|
||||
roundedRectangle
|
||||
.fill(Color.compound.bgSubtleSecondary)
|
||||
roundedRectangle
|
||||
.stroke(Color.compound._borderTextFieldFocused, lineWidth: 0.5)
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 42)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
private func onPlaybackPlayPause() { }
|
||||
|
||||
private func onPlaybackSeek(_ progress: Double) { }
|
||||
|
||||
private func onPlaybackScrubbing(_ dragging: Bool) { }
|
||||
}
|
||||
|
||||
struct VoiceMessageRecordingComposer_Previews: PreviewProvider, TestablePreview {
|
||||
static let recorderState = AudioRecorderState()
|
||||
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
VoiceMessageRecordingComposer(recorderState: recorderState)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
//
|
||||
// Copyright 2023 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 Compound
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct VoiceMessageRecordingView: View {
|
||||
@ObservedObject var recorderState: AudioRecorderState
|
||||
@ScaledMetric private var waveformLineWidth = 2.0
|
||||
@ScaledMetric private var waveformLinePadding = 2.0
|
||||
@ScaledMetric private var recordingIndicatorSize = 8
|
||||
|
||||
private static let elapsedTimeFormatter: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "mm:ss"
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
var timeLabelContent: String {
|
||||
Self.elapsedTimeFormatter.string(from: Date(timeIntervalSinceReferenceDate: recorderState.duration))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Circle()
|
||||
.frame(width: recordingIndicatorSize, height: recordingIndicatorSize)
|
||||
.foregroundColor(.red)
|
||||
Text(timeLabelContent)
|
||||
.lineLimit(1)
|
||||
.font(.compound.bodySMSemibold)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.monospacedDigit()
|
||||
.fixedSize()
|
||||
WaveformView(lineWidth: waveformLineWidth, linePadding: waveformLinePadding, waveform: recorderState.waveform, progress: 0, showCursor: false)
|
||||
}
|
||||
.padding(.leading, 2)
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
}
|
||||
|
||||
struct VoiceMessageRecordingView_Previews: PreviewProvider, TestablePreview {
|
||||
static let waveform = Waveform(data: [3, 127, 400, 266, 126, 122, 373, 251, 45, 112,
|
||||
334, 205, 99, 138, 397, 354, 125, 361, 199, 51,
|
||||
294, 131, 19, 2, 3, 3, 1, 2, 0, 0,
|
||||
0, 0, 0, 0, 0, 3])
|
||||
|
||||
static let recorderState = AudioRecorderState()
|
||||
|
||||
static var previews: some View {
|
||||
VoiceMessageRecordingView(recorderState: recorderState)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
@ -40,6 +40,8 @@ enum RoomScreenComposerMode: Equatable {
|
||||
case `default`
|
||||
case reply(itemID: TimelineItemIdentifier, replyDetails: TimelineItemReplyDetails, isThread: Bool)
|
||||
case edit(originalItemId: TimelineItemIdentifier)
|
||||
case recordVoiceMessage(state: AudioRecorderState)
|
||||
case previewVoiceMessage(state: AudioPlayerState)
|
||||
|
||||
var isEdit: Bool {
|
||||
switch self {
|
||||
|
@ -186,6 +186,15 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
trackComposerMode(mode)
|
||||
case .composerFocusedChanged(isFocused: let isFocused):
|
||||
composerFocusedSubject.send(isFocused)
|
||||
case .startRecordingVoiceMessage:
|
||||
timelineController.pauseAudio()
|
||||
startRecordingVoiceMessage()
|
||||
case .stopRecordingVoiceMessage:
|
||||
stopRecordingVoiceMessage()
|
||||
case .deleteRecordedVoiceMessage:
|
||||
deleteCurrentVoiceMessage()
|
||||
case .sendVoiceMessage:
|
||||
Task { await sendCurrentVoiceMessage() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -466,9 +475,11 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
await timelineController.editMessage(message, html: html, original: originalItemId)
|
||||
case .default:
|
||||
await timelineController.sendMessage(message, html: html)
|
||||
case .recordVoiceMessage, .previewVoiceMessage:
|
||||
fatalError("invalid composer mode.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func trackComposerMode(_ mode: RoomScreenComposerMode) {
|
||||
var isEdit = false
|
||||
var isReply = false
|
||||
@ -888,6 +899,34 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
private func audioPlayerState(for itemID: TimelineItemIdentifier) -> AudioPlayerState {
|
||||
timelineController.audioPlayerState(for: itemID)
|
||||
}
|
||||
|
||||
// MARK: - Voice message
|
||||
|
||||
private func startRecordingVoiceMessage() {
|
||||
// Partially implemented
|
||||
|
||||
let audioRecordState = AudioRecorderState()
|
||||
actionsSubject.send(.composer(action: .setMode(mode: .recordVoiceMessage(state: audioRecordState))))
|
||||
}
|
||||
|
||||
private func stopRecordingVoiceMessage() {
|
||||
// Partially implemented
|
||||
|
||||
let audioPlayerState = AudioPlayerState(duration: 0)
|
||||
actionsSubject.send(.composer(action: .setMode(mode: .previewVoiceMessage(state: audioPlayerState))))
|
||||
}
|
||||
|
||||
private func deleteCurrentVoiceMessage() {
|
||||
// Partially implemented
|
||||
|
||||
actionsSubject.send(.composer(action: .setMode(mode: .default)))
|
||||
}
|
||||
|
||||
private func sendCurrentVoiceMessage() async {
|
||||
// Partially implemented
|
||||
|
||||
actionsSubject.send(.composer(action: .setMode(mode: .default)))
|
||||
}
|
||||
}
|
||||
|
||||
private extension RoomProxyProtocol {
|
||||
|
@ -30,6 +30,7 @@ enum AudioConverterPreferredFileExtension: String {
|
||||
struct AudioConverter: AudioConverterProtocol {
|
||||
func convertToOpusOgg(sourceURL: URL, destinationURL: URL) throws {
|
||||
do {
|
||||
MXLog.debug("converting \(sourceURL) to \(destinationURL)")
|
||||
try OGGConverter.convertM4aFileToOpusOGG(src: sourceURL, dest: destinationURL)
|
||||
} catch {
|
||||
MXLog.error("failed to convert to OpusOgg: \(error)")
|
@ -27,7 +27,8 @@ enum AudioPlayerPlaybackState {
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class AudioPlayerState: ObservableObject {
|
||||
class AudioPlayerState: ObservableObject, Identifiable {
|
||||
let id = UUID()
|
||||
let duration: Double
|
||||
let waveform: Waveform
|
||||
@Published private(set) var playbackState: AudioPlayerPlaybackState
|
||||
@ -150,3 +151,9 @@ class AudioPlayerState: ObservableObject {
|
||||
await audioPlayer.seek(to: progress)
|
||||
}
|
||||
}
|
||||
|
||||
extension AudioPlayerState: Equatable {
|
||||
nonisolated static func == (lhs: AudioPlayerState, rhs: AudioPlayerState) -> Bool {
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
}
|
115
ElementX/Sources/Services/Audio/Recorder/AudioRecorder.swift
Normal file
115
ElementX/Sources/Services/Audio/Recorder/AudioRecorder.swift
Normal file
@ -0,0 +1,115 @@
|
||||
//
|
||||
// Copyright 2023 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 AVFoundation
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
enum AudioRecorderError: Error {
|
||||
case genericError
|
||||
}
|
||||
|
||||
class AudioRecorder: NSObject, AudioRecorderProtocol, AVAudioRecorderDelegate {
|
||||
private let silenceThreshold: Float = -50.0
|
||||
|
||||
private var audioRecorder: AVAudioRecorder?
|
||||
|
||||
private let actionsSubject: PassthroughSubject<AudioRecorderAction, Never> = .init()
|
||||
var actions: AnyPublisher<AudioRecorderAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
var url: URL? {
|
||||
audioRecorder?.url
|
||||
}
|
||||
|
||||
var currentTime: TimeInterval {
|
||||
audioRecorder?.currentTime ?? 0
|
||||
}
|
||||
|
||||
var isRecording: Bool {
|
||||
audioRecorder?.isRecording ?? false
|
||||
}
|
||||
|
||||
func recordWithOutputURL(_ url: URL) {
|
||||
let settings = [AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
|
||||
AVSampleRateKey: 48000,
|
||||
AVEncoderBitRateKey: 128_000,
|
||||
AVNumberOfChannelsKey: 1,
|
||||
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue]
|
||||
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setCategory(.playAndRecord, mode: .default)
|
||||
try AVAudioSession.sharedInstance().setActive(true)
|
||||
audioRecorder = try AVAudioRecorder(url: url, settings: settings)
|
||||
audioRecorder?.delegate = self
|
||||
audioRecorder?.isMeteringEnabled = true
|
||||
audioRecorder?.record()
|
||||
actionsSubject.send(.didStartRecording)
|
||||
} catch {
|
||||
MXLog.error("audio recording failed: \(error)")
|
||||
actionsSubject.send(.didFailWithError(error: error))
|
||||
}
|
||||
}
|
||||
|
||||
func stopRecording() {
|
||||
audioRecorder?.stop()
|
||||
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setActive(false)
|
||||
} catch {
|
||||
actionsSubject.send(.didFailWithError(error: error))
|
||||
}
|
||||
}
|
||||
|
||||
func peakPowerForChannelNumber(_ channelNumber: Int) -> Float {
|
||||
guard isRecording, let audioRecorder else {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
audioRecorder.updateMeters()
|
||||
|
||||
return normalizedPowerLevelFromDecibels(audioRecorder.peakPower(forChannel: channelNumber))
|
||||
}
|
||||
|
||||
func averagePowerForChannelNumber(_ channelNumber: Int) -> Float {
|
||||
guard isRecording, let audioRecorder else {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
audioRecorder.updateMeters()
|
||||
|
||||
return normalizedPowerLevelFromDecibels(audioRecorder.averagePower(forChannel: channelNumber))
|
||||
}
|
||||
|
||||
// MARK: - AVAudioRecorderDelegate
|
||||
|
||||
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully success: Bool) {
|
||||
if success {
|
||||
actionsSubject.send(.didStopRecording)
|
||||
} else {
|
||||
actionsSubject.send(.didFailWithError(error: AudioRecorderError.genericError))
|
||||
}
|
||||
}
|
||||
|
||||
func audioRecorderEncodeErrorDidOccur(_ recorder: AVAudioRecorder, error: Error?) {
|
||||
actionsSubject.send(.didFailWithError(error: error ?? AudioRecorderError.genericError))
|
||||
}
|
||||
|
||||
private func normalizedPowerLevelFromDecibels(_ decibels: Float) -> Float {
|
||||
decibels / silenceThreshold
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
//
|
||||
// Copyright 2023 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 Combine
|
||||
import Foundation
|
||||
|
||||
enum AudioRecorderAction {
|
||||
case didStartRecording
|
||||
case didStopRecording
|
||||
case didFailWithError(error: Error)
|
||||
}
|
||||
|
||||
protocol AudioRecorderProtocol: AnyObject {
|
||||
var actions: AnyPublisher<AudioRecorderAction, Never> { get }
|
||||
var currentTime: TimeInterval { get }
|
||||
var isRecording: Bool { get }
|
||||
var url: URL? { get }
|
||||
|
||||
func recordWithOutputURL(_ url: URL)
|
||||
func stopRecording()
|
||||
func averagePowerForChannelNumber(_ channelNumber: Int) -> Float
|
||||
}
|
||||
|
||||
// sourcery: AutoMockable
|
||||
extension AudioRecorderProtocol { }
|
@ -0,0 +1,114 @@
|
||||
//
|
||||
// Copyright 2023 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 Combine
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
enum AudioRecorderRecordingState {
|
||||
case recording
|
||||
case stopped
|
||||
case error
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class AudioRecorderState: ObservableObject, Identifiable {
|
||||
let id = UUID()
|
||||
|
||||
@Published private(set) var recordingState: AudioRecorderRecordingState = .stopped
|
||||
@Published private(set) var duration = 0.0
|
||||
@Published private(set) var waveform = Waveform(data: Array(repeating: 0, count: 100))
|
||||
|
||||
private weak var audioRecorder: AudioRecorderProtocol?
|
||||
private var cancellables: Set<AnyCancellable> = []
|
||||
private var displayLink: CADisplayLink?
|
||||
|
||||
func attachAudioRecorder(_ audioRecorder: AudioRecorderProtocol) {
|
||||
if self.audioRecorder != nil {
|
||||
detachAudioRecorder()
|
||||
}
|
||||
recordingState = .stopped
|
||||
self.audioRecorder = audioRecorder
|
||||
subscribeToAudioRecorder(audioRecorder)
|
||||
}
|
||||
|
||||
func detachAudioRecorder() {
|
||||
guard audioRecorder != nil else { return }
|
||||
audioRecorder?.stopRecording()
|
||||
stopPublishUpdates()
|
||||
cancellables = []
|
||||
audioRecorder = nil
|
||||
recordingState = .stopped
|
||||
}
|
||||
|
||||
func reportError(_ error: Error) {
|
||||
recordingState = .error
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func subscribeToAudioRecorder(_ audioRecorder: AudioRecorderProtocol) {
|
||||
audioRecorder.actions
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] action in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.handleAudioRecorderAction(action)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
private func handleAudioRecorderAction(_ action: AudioRecorderAction) {
|
||||
switch action {
|
||||
case .didStartRecording:
|
||||
startPublishUpdates()
|
||||
recordingState = .recording
|
||||
case .didStopRecording:
|
||||
stopPublishUpdates()
|
||||
recordingState = .stopped
|
||||
case .didFailWithError:
|
||||
stopPublishUpdates()
|
||||
recordingState = .stopped
|
||||
}
|
||||
}
|
||||
|
||||
private func startPublishUpdates() {
|
||||
if displayLink != nil {
|
||||
stopPublishUpdates()
|
||||
}
|
||||
displayLink = CADisplayLink(target: self, selector: #selector(publishUpdate))
|
||||
displayLink?.preferredFrameRateRange = .init(minimum: 10, maximum: 20)
|
||||
displayLink?.add(to: .current, forMode: .common)
|
||||
}
|
||||
|
||||
@objc private func publishUpdate(displayLink: CADisplayLink) {
|
||||
if let currentTime = audioRecorder?.currentTime {
|
||||
duration = currentTime
|
||||
}
|
||||
}
|
||||
|
||||
private func stopPublishUpdates() {
|
||||
displayLink?.invalidate()
|
||||
displayLink = nil
|
||||
}
|
||||
}
|
||||
|
||||
extension AudioRecorderState: Equatable {
|
||||
nonisolated static func == (lhs: AudioRecorderState, rhs: AudioRecorderState) -> Bool {
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
}
|
@ -115,7 +115,7 @@ protocol RoomProxyProtocol {
|
||||
audioInfo: AudioInfo,
|
||||
progressSubject: CurrentValueSubject<Double, Never>?,
|
||||
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, RoomProxyError>
|
||||
|
||||
|
||||
func sendFile(url: URL,
|
||||
fileInfo: FileInfo,
|
||||
progressSubject: CurrentValueSubject<Double, Never>?,
|
||||
|
@ -67,7 +67,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
func processItemTap(_ itemID: TimelineItemIdentifier) async -> RoomTimelineControllerAction { .none }
|
||||
|
||||
func sendMessage(_ message: String, html: String?, inReplyTo itemID: TimelineItemIdentifier?) async { }
|
||||
|
||||
|
||||
func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async { }
|
||||
|
||||
func editMessage(_ newMessage: String, html: String?, original itemID: TimelineItemIdentifier) async { }
|
||||
@ -90,6 +90,8 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
func playPauseAudio(for itemID: TimelineItemIdentifier) async { }
|
||||
|
||||
func pauseAudio() { }
|
||||
|
||||
func seekAudio(for itemID: TimelineItemIdentifier, progress: Double) async { }
|
||||
|
||||
// MARK: - UI Test signalling
|
||||
|
@ -240,6 +240,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
if let playerState = timelineAudioPlayerStates[itemID] {
|
||||
return playerState
|
||||
}
|
||||
|
||||
let playerState = AudioPlayerState(duration: voiceMessageRoomTimelineItem.content.duration,
|
||||
waveform: voiceMessageRoomTimelineItem.content.waveform)
|
||||
timelineAudioPlayerStates[itemID] = playerState
|
||||
@ -278,6 +279,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
// Load content
|
||||
do {
|
||||
let url = try await voiceMessageMediaManager.loadVoiceMessageFromSource(source, body: nil)
|
||||
|
||||
// Make sure that the player is still attached, as it may have been detached while waiting for the voice message to be loaded.
|
||||
if playerState.isAttached {
|
||||
player.load(mediaSource: source, using: url)
|
||||
@ -297,10 +299,16 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func pauseAudio() {
|
||||
timelineAudioPlayerStates.forEach { _, playerState in
|
||||
playerState.detachAudioPlayer()
|
||||
}
|
||||
}
|
||||
|
||||
func seekAudio(for itemID: TimelineItemIdentifier, progress: Double) async {
|
||||
await timelineAudioPlayerStates[itemID]?.updateState(progress: progress)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@objc private func contentSizeCategoryDidChange() {
|
||||
|
@ -52,7 +52,7 @@ protocol RoomTimelineControllerProtocol {
|
||||
func sendReadReceipt(for itemID: TimelineItemIdentifier) async -> Result<Void, RoomTimelineControllerError>
|
||||
|
||||
func sendMessage(_ message: String, html: String?, inReplyTo itemID: TimelineItemIdentifier?) async
|
||||
|
||||
|
||||
func editMessage(_ newMessage: String, html: String?, original itemID: TimelineItemIdentifier) async
|
||||
|
||||
func toggleReaction(_ reaction: String, to itemID: TimelineItemIdentifier) async
|
||||
@ -69,6 +69,8 @@ protocol RoomTimelineControllerProtocol {
|
||||
|
||||
func playPauseAudio(for itemID: TimelineItemIdentifier) async
|
||||
|
||||
func pauseAudio()
|
||||
|
||||
func seekAudio(for itemID: TimelineItemIdentifier, progress: Double) async
|
||||
}
|
||||
|
||||
|
@ -18,16 +18,16 @@ import SwiftUI
|
||||
|
||||
struct VoiceMessageRoomPlaybackView: View {
|
||||
@ObservedObject var playerState: AudioPlayerState
|
||||
@ScaledMetric private var waveformLineWidth = 2.0
|
||||
@ScaledMetric private var waveformLinePadding = 2.0
|
||||
|
||||
let onPlayPause: () -> Void
|
||||
let onSeek: (Double) -> Void
|
||||
let onScrubbing: (Bool) -> Void
|
||||
|
||||
private let feedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
|
||||
@State private var sendFeedback = false
|
||||
|
||||
@ScaledMetric private var waveformLineWidth = 2.0
|
||||
@ScaledMetric private var waveformLinePadding = 2.0
|
||||
private let waveformMaxWidth: CGFloat = 150
|
||||
|
||||
@ScaledMetric private var playPauseButtonSize = 32
|
||||
@ScaledMetric private var playPauseImagePadding = 8
|
||||
|
||||
@ -67,6 +67,7 @@ struct VoiceMessageRoomPlaybackView: View {
|
||||
HStack {
|
||||
playPauseButton
|
||||
Text(timeLabelContent)
|
||||
.lineLimit(1)
|
||||
.font(.compound.bodySMSemibold)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.monospacedDigit()
|
||||
@ -99,7 +100,6 @@ struct VoiceMessageRoomPlaybackView: View {
|
||||
}
|
||||
})
|
||||
}
|
||||
.frame(maxWidth: waveformMaxWidth)
|
||||
}
|
||||
.onChange(of: dragState) { newDragState in
|
||||
switch newDragState {
|
||||
@ -139,6 +139,7 @@ struct VoiceMessageRoomPlaybackView: View {
|
||||
.offset(x: playerState.playbackState == .playing ? 0 : 2)
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.compound.iconSecondary)
|
||||
.accessibilityLabel(playerState.playbackState == .playing ? L10n.a11yPause : L10n.a11yPlay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ class VoiceMessageMediaManager: VoiceMessageMediaManagerProtocol {
|
||||
if let fileURL = voiceMessageCache.fileURL(for: source) {
|
||||
return fileURL
|
||||
}
|
||||
|
||||
|
||||
// Otherwise, load the file from source
|
||||
guard case .success(let fileHandle) = await mediaProvider.loadFileFromSource(source, body: body) else {
|
||||
throw MediaProviderError.failedRetrievingFile
|
||||
|
100
UnitTests/Sources/AudioRecorderStateTests.swift
Normal file
100
UnitTests/Sources/AudioRecorderStateTests.swift
Normal file
@ -0,0 +1,100 @@
|
||||
//
|
||||
// Copyright 2023 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 Combine
|
||||
@testable import ElementX
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
class AudioRecorderStateTests: XCTestCase {
|
||||
private var audioRecorderState: AudioRecorderState!
|
||||
private var audioRecorderMock: AudioRecorderMock!
|
||||
|
||||
private var audioRecorderActionsSubject: PassthroughSubject<AudioRecorderAction, Never>!
|
||||
private var audioRecorderActions: AnyPublisher<AudioRecorderAction, Never> {
|
||||
audioRecorderActionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
private func buildAudioRecorderMock() -> AudioRecorderMock {
|
||||
let audioRecorderMock = AudioRecorderMock()
|
||||
audioRecorderMock.underlyingActions = audioRecorderActions
|
||||
audioRecorderMock.currentTime = 0.0
|
||||
return audioRecorderMock
|
||||
}
|
||||
|
||||
override func setUp() async throws {
|
||||
audioRecorderActionsSubject = .init()
|
||||
audioRecorderState = AudioRecorderState()
|
||||
audioRecorderMock = buildAudioRecorderMock()
|
||||
}
|
||||
|
||||
func testAttach() async throws {
|
||||
audioRecorderState.attachAudioRecorder(audioRecorderMock)
|
||||
|
||||
XCTAssertEqual(audioRecorderState.recordingState, .stopped)
|
||||
}
|
||||
|
||||
func testDetach() async throws {
|
||||
audioRecorderState.attachAudioRecorder(audioRecorderMock)
|
||||
|
||||
audioRecorderState.detachAudioRecorder()
|
||||
XCTAssert(audioRecorderMock.stopRecordingCalled)
|
||||
XCTAssertEqual(audioRecorderState.recordingState, .stopped)
|
||||
}
|
||||
|
||||
func testReportError() async throws {
|
||||
XCTAssertEqual(audioRecorderState.recordingState, .stopped)
|
||||
audioRecorderState.reportError(AudioRecorderError.genericError)
|
||||
XCTAssertEqual(audioRecorderState.recordingState, .error)
|
||||
}
|
||||
|
||||
func testHandlingAudioRecorderActionDidStartRecording() async throws {
|
||||
audioRecorderState.attachAudioRecorder(audioRecorderMock)
|
||||
|
||||
let deferred = deferFulfillment(audioRecorderState.$recordingState) { action in
|
||||
switch action {
|
||||
case .recording:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
audioRecorderActionsSubject.send(.didStartRecording)
|
||||
try await deferred.fulfill()
|
||||
XCTAssertEqual(audioRecorderState.recordingState, .recording)
|
||||
}
|
||||
|
||||
func testHandlingAudioPlayerActionDidStopRecording() async throws {
|
||||
audioRecorderState.attachAudioRecorder(audioRecorderMock)
|
||||
|
||||
let deferred = deferFulfillment(audioRecorderState.$recordingState) { action in
|
||||
switch action {
|
||||
case .stopped:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
audioRecorderActionsSubject.send(.didStopRecording)
|
||||
try await deferred.fulfill()
|
||||
|
||||
// The state is expected to be .readyToPlay
|
||||
XCTAssertEqual(audioRecorderState.recordingState, .stopped)
|
||||
}
|
||||
}
|
BIN
UnitTests/__Snapshots__/PreviewTests/test_composerToolbar.Voice-Message.png
(Stored with Git LFS)
Normal file
BIN
UnitTests/__Snapshots__/PreviewTests/test_composerToolbar.Voice-Message.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessagePreviewComposer.1.png
(Stored with Git LFS)
Normal file
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessagePreviewComposer.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessageRecordingButton.1.png
(Stored with Git LFS)
Normal file
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessageRecordingButton.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessageRecordingButtonTooltipView.1.png
(Stored with Git LFS)
Normal file
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessageRecordingButtonTooltipView.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessageRecordingComposer.1.png
(Stored with Git LFS)
Normal file
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessageRecordingComposer.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessageRecordingView.1.png
(Stored with Git LFS)
Normal file
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessageRecordingView.1.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessageRoomPlaybackView.1.png
(Stored with Git LFS)
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessageRoomPlaybackView.1.png
(Stored with Git LFS)
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessageRoomTimelineView.Bubble.png
(Stored with Git LFS)
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessageRoomTimelineView.Bubble.png
(Stored with Git LFS)
Binary file not shown.
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessageRoomTimelineView.Plain.png
(Stored with Git LFS)
BIN
UnitTests/__Snapshots__/PreviewTests/test_voiceMessageRoomTimelineView.Plain.png
(Stored with Git LFS)
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user