mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Fixes #978 - Message forwarding support
* Add support for message forwarding * Move the TimelineProvider within the RoomProxy to avoid it going out of sync and have it automatically register for timeline updates on init * Implement single selection form button style for message forwarding * Add searching and filter out the source room from the list * Fixed emoji screen initial search bar height * Add view models tests * Add changelog * Update strings * Tweaks following code review
This commit is contained in:
@@ -112,6 +112,7 @@
|
||||
2AD59AD5B09498EF8B3B04EC /* InvitesScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E1BBA73B611EDEEA6E20E05 /* InvitesScreenModels.swift */; };
|
||||
2BA59D0AEFB4B82A2EC2A326 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 50009897F60FAE7D63EF5E5B /* Kingfisher */; };
|
||||
2BAA5B222856068158D0B3C6 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = B1E8B697DF78FE7F61FC6CA4 /* MatrixRustSDK */; };
|
||||
2BBA132149DEBED6624084A8 /* MessageForwardingScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06FAE373A7F20780BA84B59C /* MessageForwardingScreenCoordinator.swift */; };
|
||||
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 */; };
|
||||
@@ -253,6 +254,7 @@
|
||||
65EDA77363BEDC40CDE43B43 /* InvitesScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42ADEA322D2089391E049535 /* InvitesScreen.swift */; };
|
||||
661A664C6EDF856B05519206 /* FilePreviewScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = F562E2CBA002E8E1B6545C38 /* FilePreviewScreen.swift */; };
|
||||
663E198678778F7426A9B27D /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FAFE1C2149E6AC8156ED2B /* Collection.swift */; };
|
||||
6713835120D94BAA8ED7E3E5 /* MessageForwardingScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59846FA04E1DBBFDD8829C2A /* MessageForwardingScreenUITests.swift */; };
|
||||
67160204A8D362BB7D4AD259 /* Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 693E16574C6F7F9FA1015A8C /* Search.swift */; };
|
||||
67C05C50AD734283374605E3 /* MatrixEntityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */; };
|
||||
67D6E0700A9C1E676F6231F8 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = AD544C0FA48DFFB080920061 /* Collections */; };
|
||||
@@ -260,6 +262,7 @@
|
||||
6832733838C57A7D3FE8FEB5 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */; };
|
||||
6860721DB3091BE08164C132 /* MapAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B48B7AD4908C5C374517B892 /* MapAssets.xcassets */; };
|
||||
68AC3C84E2B438036B174E30 /* EmoteRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 471EB7D96AFEA8D787659686 /* EmoteRoomTimelineView.swift */; };
|
||||
695825D20A761C678809345D /* MessageForwardingScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */; };
|
||||
69BCBB4FB2DC3D61A28D3FD8 /* TimelineStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DC2C9E0E15C79BBDA80F0A2 /* TimelineStyle.swift */; };
|
||||
69C7B956B74BEC3DB88224EA /* NavigationSplitCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */; };
|
||||
6A0E7551E0D1793245F34CDD /* ClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D09A267106B9585D3D0CFC0D /* ClientError.swift */; };
|
||||
@@ -517,6 +520,7 @@
|
||||
BF675964C9159F718589C36A /* AnalyticsSettingsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16037EE9E9A52AF37B7818E3 /* AnalyticsSettingsScreenUITests.swift */; };
|
||||
C051475DFF4C8EBDDF4DC8E4 /* StartChatScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99E13633862847D8B7E2815 /* StartChatScreenModels.swift */; };
|
||||
C08AAE7563E0722C9383F51C /* RoomMembersListScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B8E176484A89BAC389D4076 /* RoomMembersListScreen.swift */; };
|
||||
C13128AAA787A4C2CBE4EE82 /* MessageForwardingScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.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 */; };
|
||||
@@ -542,6 +546,7 @@
|
||||
C76892321558E75101E68ED6 /* ReadableFrameModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398817652FA8ABAE0A31AC6D /* ReadableFrameModifier.swift */; };
|
||||
C7CFDB4929DDD9A3B5BA085D /* BugReportViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AB7ED3A898B07976F3AA90F /* BugReportViewModelTests.swift */; };
|
||||
C8BD80891BAD688EF2C15CDB /* MediaUploadPreviewScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74DD0855F2F76D47E5555082 /* MediaUploadPreviewScreenCoordinator.swift */; };
|
||||
C8E0FA0FF2CD6613264FA6B9 /* MessageForwardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFEA446F8618DBA79A9239CC /* MessageForwardingScreen.swift */; };
|
||||
C9BE065FA7D4E77E4C61CB69 /* MapLibreModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B81B6170DB690013CEB646F4 /* MapLibreModels.swift */; };
|
||||
CA45758F08DF42D41D8A4B29 /* FilePreviewViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF38B69D2C331A499276F400 /* FilePreviewViewModelTests.swift */; };
|
||||
CAF8755E152204F55F8D6B5B /* RoomMembersListViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69B63F817FE305548DB4B512 /* RoomMembersListViewModelTests.swift */; };
|
||||
@@ -657,11 +662,13 @@
|
||||
F4433EF57B4BB3C077F8B00E /* SessionVerificationScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADD9E0FFA29EAACFF3AB9732 /* SessionVerificationScreenViewModel.swift */; };
|
||||
F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */; };
|
||||
F519DE17A3A0F760307B2E6D /* InviteUsersScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D155E09BF961BBA8F85263 /* InviteUsersScreenViewModel.swift */; };
|
||||
F54E2D6CAD96E1AC15BC526F /* MessageForwardingScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E60332509665C00179ACF6 /* MessageForwardingScreenViewModel.swift */; };
|
||||
F587A9AF25A262DE5A7B0369 /* ProgressTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F28551E81CE3700E5F1EC9B5 /* ProgressTracker.swift */; };
|
||||
F5D2270B5021D521C0D22E11 /* FlowCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B9FCA1CFD07B8CF9BD21266 /* FlowCoordinatorProtocol.swift */; };
|
||||
F656F92A63D3DC1978D79427 /* Compound in Frameworks */ = {isa = PBXBuildFile; productRef = 07FEEEDB11543A7DED420F04 /* Compound */; };
|
||||
F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */; };
|
||||
F7567DD6635434E8C563BF85 /* AnalyticsClientProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3B97591B2D3D4D67553506D /* AnalyticsClientProtocol.swift */; };
|
||||
F777C6FEE7D106136E2ED2B2 /* MessageForwardingScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F6E6EDC4BBF962B2ED595A4 /* MessageForwardingScreenViewModelTests.swift */; };
|
||||
F78BAD28482A467287A9A5A3 /* EventBasedMessageTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0900BBF0A5D5D775E917C70 /* EventBasedMessageTimelineItemProtocol.swift */; };
|
||||
F7BC744FFA7FE248FAE7F570 /* UserIndicatorToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F57C8022B8A871A1DCD1750A /* UserIndicatorToastView.swift */; };
|
||||
F86102DC2C68BBBB0521BAAE /* SoftLogoutScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2BB385E148DE55C85C0A02D6 /* SoftLogoutScreenModels.swift */; };
|
||||
@@ -752,6 +759,7 @@
|
||||
05F598B1B346DAF223651C91 /* LoginScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
0685156EB62D7E243F097CFC /* ServerSelectionScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
06B098A612DCB5A7358EECD5 /* DeveloperOptionsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenModels.swift; sourceTree = "<group>"; };
|
||||
06FAE373A7F20780BA84B59C /* MessageForwardingScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
077D7C3BE199B6E5DDEC07EC /* AppCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorStateMachine.swift; sourceTree = "<group>"; };
|
||||
07E65E613F057697A1A0BC03 /* NotificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewController.swift; sourceTree = "<group>"; };
|
||||
086B997409328F091EBA43CE /* RoomScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenUITests.swift; sourceTree = "<group>"; };
|
||||
@@ -937,6 +945,7 @@
|
||||
50E31AB0E77BB70E2BC77463 /* MatrixUserShareLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixUserShareLink.swift; sourceTree = "<group>"; };
|
||||
51C2BCE0BC1FC69C1B36E688 /* BugReportScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportScreenModels.swift; sourceTree = "<group>"; };
|
||||
51C454AE59914B551A6D02C0 /* UserProfileProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileProxy.swift; sourceTree = "<group>"; };
|
||||
52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenModels.swift; sourceTree = "<group>"; };
|
||||
5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreen.swift; sourceTree = "<group>"; };
|
||||
52BD6ED18E2EB61E28C340AD /* AttributedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedString.swift; sourceTree = "<group>"; };
|
||||
52D7074991B3267B26D89B22 /* MockRoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomTimelineController.swift; sourceTree = "<group>"; };
|
||||
@@ -957,6 +966,7 @@
|
||||
584A61D9C459FAFEF038A7C0 /* Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = "<group>"; };
|
||||
58C2527813FDAE23E72A9063 /* AnalyticsSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
592A35163B0749C66BFD6186 /* MapLibreStaticMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreStaticMapView.swift; sourceTree = "<group>"; };
|
||||
59846FA04E1DBBFDD8829C2A /* MessageForwardingScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenUITests.swift; sourceTree = "<group>"; };
|
||||
5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
5B8F0ED874DF8C9A51B0AB6F /* SettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
5C7C7CFA6B2A62A685FF6CE3 /* DeveloperOptionsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenCoordinator.swift; sourceTree = "<group>"; };
|
||||
@@ -1007,6 +1017,7 @@
|
||||
6E5E9C044BEB7C70B1378E91 /* UserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSession.swift; sourceTree = "<group>"; };
|
||||
6EA1D2CBAEA5D0BD00B90D1B /* BindableState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindableState.swift; sourceTree = "<group>"; };
|
||||
6F3DFE5B444F131648066F05 /* StateStoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateStoreViewModel.swift; sourceTree = "<group>"; };
|
||||
6F6E6EDC4BBF962B2ED595A4 /* MessageForwardingScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
6FB31A32C93D94930B253FBF /* PermalinkBuilderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermalinkBuilderTests.swift; sourceTree = "<group>"; };
|
||||
6FC5015B9634698BDB8701AF /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = it; path = it.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
7023EB4F3B7C7D1FBA68638B /* TimelineItemDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemDebugView.swift; sourceTree = "<group>"; };
|
||||
@@ -1196,6 +1207,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>"; };
|
||||
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>"; };
|
||||
C070FD43DC6BF4E50217965A /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = "<group>"; };
|
||||
C08E9043618AE5B0BF7B07E1 /* TemplateScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||
@@ -1264,12 +1276,14 @@
|
||||
D6DC38E64A5ED3FDB201029A /* BugReportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportService.swift; sourceTree = "<group>"; };
|
||||
D77B3D4950F1707E66E4A45A /* AnalyticsConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsConfiguration.swift; sourceTree = "<group>"; };
|
||||
D8E057FB1F07A5C201C89061 /* MockServerSelectionScreenState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockServerSelectionScreenState.swift; sourceTree = "<group>"; };
|
||||
D8E60332509665C00179ACF6 /* MessageForwardingScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
D8F5F9E02B1AB5350B1815E7 /* TimelineStartRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStartRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
DA2AEC1AB349A341FE13DEC1 /* StartChatScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenUITests.swift; sourceTree = "<group>"; };
|
||||
DB06F22CFA34885B40976061 /* RoomDetailsEditScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreen.swift; sourceTree = "<group>"; };
|
||||
DBA8DC95C079805B0B56E8A9 /* SharedUserDefaultsKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedUserDefaultsKeys.swift; sourceTree = "<group>"; };
|
||||
DBFEAC3AC691CBB84983E275 /* ElementXTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXTests.swift; sourceTree = "<group>"; };
|
||||
DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotification+Creator.swift"; sourceTree = "<group>"; };
|
||||
DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
DE846DDA83BFD7EC5C03760B /* ServerConfirmationScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerConfirmationScreenUITests.swift; sourceTree = "<group>"; };
|
||||
DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaFileHandleProxy.swift; sourceTree = "<group>"; };
|
||||
DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationViewModelTests.swift; sourceTree = "<group>"; };
|
||||
@@ -1754,6 +1768,18 @@
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3348D14DBDB54E72FC67E2F3 /* MessageForwardingScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
06FAE373A7F20780BA84B59C /* MessageForwardingScreenCoordinator.swift */,
|
||||
52135BD9E0E7A091688F627A /* MessageForwardingScreenModels.swift */,
|
||||
D8E60332509665C00179ACF6 /* MessageForwardingScreenViewModel.swift */,
|
||||
DC10CCC8D68B863E20660DBC /* MessageForwardingScreenViewModelProtocol.swift */,
|
||||
E816FC2BB467DD0B041C5A03 /* View */,
|
||||
);
|
||||
path = MessageForwardingScreen;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
337015ADFBA3AB96660DB3A6 /* Generated */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -2313,6 +2339,7 @@
|
||||
F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */,
|
||||
AE203026B9AD3DB412439866 /* MediaUploadingPreprocessorTests.swift */,
|
||||
03FABD73FD8086EFAB699F42 /* MediaUploadPreviewScreenViewModelTests.swift */,
|
||||
6F6E6EDC4BBF962B2ED595A4 /* MessageForwardingScreenViewModelTests.swift */,
|
||||
F875D71347DC81EAE7687446 /* NavigationRootCoordinatorTests.swift */,
|
||||
78913D6E120D46138E97C107 /* NavigationSplitCoordinatorTests.swift */,
|
||||
9C698E30698EC59302A8EEBD /* NavigationStackCoordinatorTests.swift */,
|
||||
@@ -2646,6 +2673,7 @@
|
||||
ACCC1874C122E2BBE648B8F5 /* LegalInformationScreenUITests.swift */,
|
||||
1DB34B0C74CD242FED9DD069 /* LoginScreenUITests.swift */,
|
||||
39B6C8690AEA1E49FF1BAF95 /* MediaUploadPreviewScreenUITests.swift */,
|
||||
59846FA04E1DBBFDD8829C2A /* MessageForwardingScreenUITests.swift */,
|
||||
0C88046D6A070D9827181C4D /* OnboardingUITests.swift */,
|
||||
4132F882A984ED971338EE9D /* ReportContentScreenUITests.swift */,
|
||||
122186B7CD1BC46A9C629DD9 /* RoomDetailsEditScreenUITests.swift */,
|
||||
@@ -3165,6 +3193,7 @@
|
||||
948DD12A5533BE1BC260E437 /* LocationSharing */,
|
||||
87E2774157D9C4894BCFF3F8 /* MediaPickerScreen */,
|
||||
23605DD08620BE6558242469 /* MediaUploadPreviewScreen */,
|
||||
3348D14DBDB54E72FC67E2F3 /* MessageForwardingScreen */,
|
||||
3F38EAC92E2281990E65DAF2 /* OnboardingScreen */,
|
||||
A448A3A8F764174C60CD0CA1 /* Other */,
|
||||
5970F275D6014548DCED6106 /* ReportContentScreen */,
|
||||
@@ -3251,6 +3280,14 @@
|
||||
path = Authentication;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E816FC2BB467DD0B041C5A03 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BFEA446F8618DBA79A9239CC /* MessageForwardingScreen.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EBBEB5471737E9D116DF4738 /* Background */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -3804,6 +3841,7 @@
|
||||
167D00CAA13FAFB822298021 /* MediaProviderTests.swift in Sources */,
|
||||
B9A8C34A00D03094C0CF56F3 /* MediaUploadPreviewScreenViewModelTests.swift in Sources */,
|
||||
23701DE32ACD6FD40AA992C3 /* MediaUploadingPreprocessorTests.swift in Sources */,
|
||||
F777C6FEE7D106136E2ED2B2 /* MessageForwardingScreenViewModelTests.swift in Sources */,
|
||||
4E8F17EBA24FBBA6ABB62ECB /* MockBackgroundTaskService.swift in Sources */,
|
||||
1146E9EDCF8344F7D6E0D553 /* MockCoder.swift in Sources */,
|
||||
DC68E866D6E664B0D2B06E74 /* MockImageCache.swift in Sources */,
|
||||
@@ -4072,6 +4110,11 @@
|
||||
8A0BD60CA4A6004DB06B5403 /* MediaUploadingPreprocessor.swift in Sources */,
|
||||
24906A1E82D0046655958536 /* MessageComposer.swift in Sources */,
|
||||
072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */,
|
||||
C8E0FA0FF2CD6613264FA6B9 /* MessageForwardingScreen.swift in Sources */,
|
||||
2BBA132149DEBED6624084A8 /* MessageForwardingScreenCoordinator.swift in Sources */,
|
||||
695825D20A761C678809345D /* MessageForwardingScreenModels.swift in Sources */,
|
||||
F54E2D6CAD96E1AC15BC526F /* MessageForwardingScreenViewModel.swift in Sources */,
|
||||
C13128AAA787A4C2CBE4EE82 /* MessageForwardingScreenViewModelProtocol.swift in Sources */,
|
||||
152AE2B8650FB23AFD2E28B9 /* MockAuthenticationServiceProxy.swift in Sources */,
|
||||
EE4F5601356228FF72FC56B6 /* MockClientProxy.swift in Sources */,
|
||||
B659E3A49889E749E3239EA7 /* MockMediaProvider.swift in Sources */,
|
||||
@@ -4335,6 +4378,7 @@
|
||||
8868ACFA45E5B5E19514B575 /* LegalInformationScreenUITests.swift in Sources */,
|
||||
5C8AFBF168A41E20835F3B86 /* LoginScreenUITests.swift in Sources */,
|
||||
7FB0BDE26838F1A92782D5E1 /* MediaUploadPreviewScreenUITests.swift in Sources */,
|
||||
6713835120D94BAA8ED7E3E5 /* MessageForwardingScreenUITests.swift in Sources */,
|
||||
6B15FF984906AAFCF9DC4F58 /* OnboardingUITests.swift in Sources */,
|
||||
BA0D3DDCEDD97502DAC4B6E9 /* ReportContentScreenUITests.swift in Sources */,
|
||||
F16109A6F6DF03DA26D59233 /* RoomDetailsEditScreenUITests.swift in Sources */,
|
||||
|
||||
@@ -192,15 +192,16 @@
|
||||
"screen_account_provider_form_notice" = "Enter a search term or a domain address.";
|
||||
"screen_account_provider_form_subtitle" = "Search for a company, community, or private server.";
|
||||
"screen_account_provider_form_title" = "Find an account provider";
|
||||
"screen_account_provider_signin_subtitle" = "This is where you conversations will live — just like you would use an email provider to keep your emails.";
|
||||
"screen_account_provider_signin_title" = "You’re about to sign in to %@";
|
||||
"screen_account_provider_signup_subtitle" = "This is where you conversations will live — just like you would use an email provider to keep your emails.";
|
||||
"screen_account_provider_signup_title" = "You’re about to create an account on %@";
|
||||
"screen_analytics_prompt_data_usage" = "We <b>don't</b> record or profile any account data";
|
||||
"screen_analytics_prompt_help_us_improve" = "Help us identify issues and improve %1$@ by sharing anonymous usage data.";
|
||||
"screen_analytics_prompt_data_usage" = "We won't record or profile any personal data";
|
||||
"screen_analytics_prompt_help_us_improve" = "Share anonymous usage data to help us identify issues.";
|
||||
"screen_analytics_prompt_read_terms" = "You can read all our terms %1$@.";
|
||||
"screen_analytics_prompt_read_terms_content_link" = "here";
|
||||
"screen_analytics_prompt_settings" = "You can turn this off anytime in settings";
|
||||
"screen_analytics_prompt_third_party_sharing" = "We <b>don't</b> share information with third parties";
|
||||
"screen_analytics_prompt_settings" = "You can turn this off anytime";
|
||||
"screen_analytics_prompt_third_party_sharing" = "We won't share your data with third parties";
|
||||
"screen_analytics_prompt_title" = "Help improve %1$@";
|
||||
"screen_analytics_settings_share_data" = "Share analytics data";
|
||||
"screen_bug_report_attach_screenshot" = "Attach screenshot";
|
||||
@@ -223,6 +224,7 @@
|
||||
"screen_change_server_form_notice" = "You can only connect to an existing server that supports sliding sync. Your homeserver admin will need to configure it. %1$@";
|
||||
"screen_change_server_subtitle" = "What is the address of your server?";
|
||||
"screen_create_room_action_create_room" = "New room";
|
||||
"screen_create_room_action_invite_people" = "Invite friends to Element";
|
||||
"screen_create_room_add_people_title" = "Invite people";
|
||||
"screen_create_room_error_creating_room" = "An error occurred when creating the room";
|
||||
"screen_create_room_private_option_description" = "Messages in this room are encrypted. Encryption can’t be disabled afterwards.";
|
||||
@@ -269,6 +271,7 @@
|
||||
"screen_room_details_encryption_enabled_subtitle" = "Messages are secured with locks. Only you and the recipients have the unique keys to unlock them.";
|
||||
"screen_room_details_encryption_enabled_title" = "Message encryption enabled";
|
||||
"screen_room_details_invite_people_title" = "Invite people";
|
||||
"screen_room_details_notification_title" = "Notification";
|
||||
"screen_room_details_room_name_label" = "Room name";
|
||||
"screen_room_details_share_room_title" = "Share room";
|
||||
"screen_room_details_updating_room" = "Updating room…";
|
||||
@@ -283,6 +286,7 @@
|
||||
"screen_room_member_details_unblock_user" = "Unblock user";
|
||||
"screen_room_member_list_pending_header_title" = "Pending";
|
||||
"screen_room_member_list_room_members_header_title" = "Room members";
|
||||
"screen_room_no_permission_to_post" = "You do not have permission to post to this room";
|
||||
"screen_room_retry_send_menu_send_again_action" = "Send again";
|
||||
"screen_room_retry_send_menu_title" = "Your message failed to send";
|
||||
"screen_roomlist_a11y_create_message" = "Create a new conversation or room";
|
||||
@@ -377,7 +381,7 @@
|
||||
"dialog_title_success" = "Success";
|
||||
"notification_room_action_quick_reply" = "Quick reply";
|
||||
"rageshake_detection_dialog_content" = "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?";
|
||||
"screen_analytics_settings_help_us_improve" = "Help us identify issues and improve %1$@ by sharing anonymous usage data.";
|
||||
"screen_analytics_settings_help_us_improve" = "Share anonymous usage data to help us identify issues.";
|
||||
"screen_analytics_settings_read_terms" = "You can read all our terms %1$@.";
|
||||
"screen_analytics_settings_read_terms_content_link" = "here";
|
||||
"screen_bug_report_rash_logs_alert_title" = "%1$@ crashed the last time it was used. Would you like to share a crash report with us?";
|
||||
|
||||
@@ -29,6 +29,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||
private let navigationSplitCoordinator: NavigationSplitCoordinator
|
||||
private let emojiProvider: EmojiProviderProtocol
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
|
||||
private let stateMachine: StateMachine<State, Event> = .init(state: .initial)
|
||||
|
||||
@@ -46,12 +47,14 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
roomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol,
|
||||
navigationStackCoordinator: NavigationStackCoordinator,
|
||||
navigationSplitCoordinator: NavigationSplitCoordinator,
|
||||
emojiProvider: EmojiProviderProtocol) {
|
||||
emojiProvider: EmojiProviderProtocol,
|
||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
self.userSession = userSession
|
||||
self.roomTimelineControllerFactory = roomTimelineControllerFactory
|
||||
self.navigationStackCoordinator = navigationStackCoordinator
|
||||
self.navigationSplitCoordinator = navigationSplitCoordinator
|
||||
self.emojiProvider = emojiProvider
|
||||
self.userIndicatorController = userIndicatorController
|
||||
|
||||
setupStateMachine()
|
||||
}
|
||||
@@ -137,6 +140,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case (.dismissEmojiPicker, .emojiPicker(let roomID, _)):
|
||||
return .room(roomID: roomID)
|
||||
|
||||
case (.presentMessageForwarding(let itemID), .room(let roomID)):
|
||||
return .messageForwarding(roomID: roomID, itemID: itemID)
|
||||
case (.dismissMessageForwarding, .messageForwarding(let roomID, _)):
|
||||
return .room(roomID: roomID)
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -152,7 +160,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
dismissRoom(animated: animated)
|
||||
presentRoom(roomID, animated: animated)
|
||||
case (_, .presentRoom(let roomID), .room):
|
||||
presentRoom(roomID, animated: animated)
|
||||
let destinationRoomProxy = (context.userInfo as? EventUserInfo)?.destinationRoomProxy
|
||||
presentRoom(roomID, animated: animated, destinationRoomProxy: destinationRoomProxy)
|
||||
case (.room, .dismissRoom, .initial):
|
||||
dismissRoom(animated: animated)
|
||||
|
||||
@@ -199,6 +208,11 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case (.roomMemberDetails, .dismissRoomMemberDetails, .room):
|
||||
break
|
||||
|
||||
case (.room, .presentMessageForwarding(let eventID), .messageForwarding):
|
||||
presentMessageForwarding(for: eventID)
|
||||
case (.messageForwarding, .dismissMessageForwarding, .room):
|
||||
break
|
||||
|
||||
default:
|
||||
fatalError("Unknown transition: \(context)")
|
||||
}
|
||||
@@ -217,23 +231,39 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
private func presentRoom(_ roomID: String, animated: Bool) {
|
||||
|
||||
/// Updates the navigation stack so it displays the timeline for the given room
|
||||
/// - Parameters:
|
||||
/// - roomID: the identifier of the room that is to be presented
|
||||
/// - animated: whether it should animate the transition
|
||||
/// - destinationRoomProxy: an optional already build roomProxy for the target room. It is currently used when
|
||||
/// forwarding messages so that we can take advantage of the local echo
|
||||
/// and have the message already there when presenting the room
|
||||
private func presentRoom(_ roomID: String, animated: Bool, destinationRoomProxy: RoomProxyProtocol? = nil) {
|
||||
Task {
|
||||
await asyncPresentRoom(roomID, animated: animated)
|
||||
await asyncPresentRoom(roomID, animated: animated, destinationRoomProxy: destinationRoomProxy)
|
||||
}
|
||||
}
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity function_body_length
|
||||
private func asyncPresentRoom(_ roomID: String, animated: Bool) async {
|
||||
private func asyncPresentRoom(_ roomID: String, animated: Bool, destinationRoomProxy: RoomProxyProtocol? = nil) async {
|
||||
if let roomProxy, roomProxy.id == roomID {
|
||||
navigationStackCoordinator.popToRoot()
|
||||
return
|
||||
}
|
||||
|
||||
guard let roomProxy = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||
MXLog.error("Invalid room identifier: \(roomID)")
|
||||
stateMachine.tryEvent(.dismissRoom)
|
||||
return
|
||||
let roomProxy: RoomProxyProtocol
|
||||
|
||||
if let destinationRoomProxy {
|
||||
roomProxy = destinationRoomProxy
|
||||
} else {
|
||||
guard let proxy = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||
MXLog.error("Invalid room identifier: \(roomID)")
|
||||
stateMachine.tryEvent(.dismissRoom)
|
||||
return
|
||||
}
|
||||
|
||||
roomProxy = proxy
|
||||
}
|
||||
|
||||
actionsSubject.send(.presentedRoom(roomID))
|
||||
@@ -277,6 +307,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
stateMachine.tryEvent(.presentEmojiPicker(itemID: itemID))
|
||||
case .presentRoomMemberDetails(member: let member):
|
||||
stateMachine.tryEvent(.presentRoomMemberDetails(member: .init(value: member)))
|
||||
case .presentMessageForwarding(let itemID):
|
||||
stateMachine.tryEvent(.presentMessageForwarding(itemID: itemID))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
@@ -398,7 +430,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case .cancel:
|
||||
break
|
||||
case .finish:
|
||||
self?.showSuccess(label: L10n.commonReportSubmitted)
|
||||
userIndicatorController.submitIndicator(UserIndicator(title: L10n.commonReportSubmitted, iconName: "checkmark"))
|
||||
}
|
||||
}
|
||||
navigationCoordinator.setRootCoordinator(coordinator)
|
||||
@@ -492,9 +524,65 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
|
||||
self?.stateMachine.tryEvent(.dismissRoomMemberDetails)
|
||||
}
|
||||
}
|
||||
|
||||
private func presentMessageForwarding(for eventID: String) {
|
||||
guard let roomProxy, let roomSummaryProvider = userSession.clientProxy.roomSummaryProvider else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
let messageForwardingNavigationStackCoordinator = NavigationStackCoordinator()
|
||||
|
||||
let parameters = MessageForwardingScreenCoordinatorParameters(roomSummaryProvider: roomSummaryProvider,
|
||||
sourceRoomID: roomProxy.id)
|
||||
let coordinator = MessageForwardingScreenCoordinator(parameters: parameters)
|
||||
|
||||
coordinator.actions.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .dismiss:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
case .send(let roomID):
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
|
||||
Task {
|
||||
await self.forward(eventID: eventID, toRoomID: roomID)
|
||||
}
|
||||
}
|
||||
}.store(in: &cancellables)
|
||||
|
||||
messageForwardingNavigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
|
||||
private func showSuccess(label: String) {
|
||||
ServiceLocator.shared.userIndicatorController.submitIndicator(UserIndicator(title: label, iconName: "checkmark"))
|
||||
navigationStackCoordinator.setSheetCoordinator(messageForwardingNavigationStackCoordinator) { [weak self] in
|
||||
self?.stateMachine.tryEvent(.dismissMessageForwarding)
|
||||
}
|
||||
}
|
||||
|
||||
private func forward(eventID: String, toRoomID roomID: String) async {
|
||||
guard let roomProxy else {
|
||||
MXLog.error("Failed retrieving current room with id: \(roomID)")
|
||||
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
||||
return
|
||||
}
|
||||
|
||||
guard let messageEventContent = roomProxy.messageEventContent(for: eventID) else {
|
||||
MXLog.error("Failed retrieving forwarded message event content for eventID: \(eventID)")
|
||||
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
||||
return
|
||||
}
|
||||
|
||||
guard let targetRoomProxy = await userSession.clientProxy.roomForIdentifier(roomID) else {
|
||||
MXLog.error("Failed retrieving room to forward to with id: \(roomID)")
|
||||
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
||||
return
|
||||
}
|
||||
|
||||
if case .failure(let error) = await targetRoomProxy.sendMessageEventContent(messageEventContent) {
|
||||
MXLog.error("Failed forwarding message with error: \(error)")
|
||||
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
|
||||
return
|
||||
}
|
||||
|
||||
stateMachine.tryEvent(.presentRoom(roomID: roomID), userInfo: EventUserInfo(animated: true, destinationRoomProxy: targetRoomProxy))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,10 +609,12 @@ private extension RoomFlowCoordinator {
|
||||
case mediaUploadPreview(roomID: String, fileURL: URL)
|
||||
case emojiPicker(roomID: String, itemID: String)
|
||||
case roomMemberDetails(roomID: String, member: HashableRoomMemberWrapper)
|
||||
case messageForwarding(roomID: String, itemID: String)
|
||||
}
|
||||
|
||||
struct EventUserInfo {
|
||||
let animated: Bool
|
||||
var destinationRoomProxy: RoomProxyProtocol?
|
||||
}
|
||||
|
||||
enum Event: EventType {
|
||||
@@ -551,5 +641,8 @@ private extension RoomFlowCoordinator {
|
||||
|
||||
case presentRoomMemberDetails(member: HashableRoomMemberWrapper)
|
||||
case dismissRoomMemberDetails
|
||||
|
||||
case presentMessageForwarding(itemID: String)
|
||||
case dismissMessageForwarding
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
roomTimelineControllerFactory: roomTimelineControllerFactory,
|
||||
navigationStackCoordinator: detailNavigationStackCoordinator,
|
||||
navigationSplitCoordinator: navigationSplitCoordinator,
|
||||
emojiProvider: EmojiProvider())
|
||||
emojiProvider: EmojiProvider(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
|
||||
setupStateMachine()
|
||||
|
||||
|
||||
@@ -466,6 +466,8 @@ public enum L10n {
|
||||
public static var screenAccountProviderFormSubtitle: String { return L10n.tr("Localizable", "screen_account_provider_form_subtitle") }
|
||||
/// Find an account provider
|
||||
public static var screenAccountProviderFormTitle: String { return L10n.tr("Localizable", "screen_account_provider_form_title") }
|
||||
/// This is where you conversations will live — just like you would use an email provider to keep your emails.
|
||||
public static var screenAccountProviderSigninSubtitle: String { return L10n.tr("Localizable", "screen_account_provider_signin_subtitle") }
|
||||
/// You’re about to sign in to %@
|
||||
public static func screenAccountProviderSigninTitle(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_account_provider_signin_title", String(describing: p1))
|
||||
@@ -476,30 +478,26 @@ public enum L10n {
|
||||
public static func screenAccountProviderSignupTitle(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_account_provider_signup_title", String(describing: p1))
|
||||
}
|
||||
/// We <b>don't</b> record or profile any account data
|
||||
/// We won't record or profile any personal data
|
||||
public static var screenAnalyticsPromptDataUsage: String { return L10n.tr("Localizable", "screen_analytics_prompt_data_usage") }
|
||||
/// Help us identify issues and improve %1$@ by sharing anonymous usage data.
|
||||
public static func screenAnalyticsPromptHelpUsImprove(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_analytics_prompt_help_us_improve", String(describing: p1))
|
||||
}
|
||||
/// Share anonymous usage data to help us identify issues.
|
||||
public static var screenAnalyticsPromptHelpUsImprove: String { return L10n.tr("Localizable", "screen_analytics_prompt_help_us_improve") }
|
||||
/// You can read all our terms %1$@.
|
||||
public static func screenAnalyticsPromptReadTerms(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_analytics_prompt_read_terms", String(describing: p1))
|
||||
}
|
||||
/// here
|
||||
public static var screenAnalyticsPromptReadTermsContentLink: String { return L10n.tr("Localizable", "screen_analytics_prompt_read_terms_content_link") }
|
||||
/// You can turn this off anytime in settings
|
||||
/// You can turn this off anytime
|
||||
public static var screenAnalyticsPromptSettings: String { return L10n.tr("Localizable", "screen_analytics_prompt_settings") }
|
||||
/// We <b>don't</b> share information with third parties
|
||||
/// We won't share your data with third parties
|
||||
public static var screenAnalyticsPromptThirdPartySharing: String { return L10n.tr("Localizable", "screen_analytics_prompt_third_party_sharing") }
|
||||
/// Help improve %1$@
|
||||
public static func screenAnalyticsPromptTitle(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_analytics_prompt_title", String(describing: p1))
|
||||
}
|
||||
/// Help us identify issues and improve %1$@ by sharing anonymous usage data.
|
||||
public static func screenAnalyticsSettingsHelpUsImprove(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_analytics_settings_help_us_improve", String(describing: p1))
|
||||
}
|
||||
/// Share anonymous usage data to help us identify issues.
|
||||
public static var screenAnalyticsSettingsHelpUsImprove: String { return L10n.tr("Localizable", "screen_analytics_settings_help_us_improve") }
|
||||
/// You can read all our terms %1$@.
|
||||
public static func screenAnalyticsSettingsReadTerms(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_analytics_settings_read_terms", String(describing: p1))
|
||||
@@ -558,6 +556,8 @@ public enum L10n {
|
||||
public static var screenChangeServerTitle: String { return L10n.tr("Localizable", "screen_change_server_title") }
|
||||
/// New room
|
||||
public static var screenCreateRoomActionCreateRoom: String { return L10n.tr("Localizable", "screen_create_room_action_create_room") }
|
||||
/// Invite friends to Element
|
||||
public static var screenCreateRoomActionInvitePeople: String { return L10n.tr("Localizable", "screen_create_room_action_invite_people") }
|
||||
/// Invite people
|
||||
public static var screenCreateRoomAddPeopleTitle: String { return L10n.tr("Localizable", "screen_create_room_add_people_title") }
|
||||
/// An error occurred when creating the room
|
||||
@@ -684,6 +684,8 @@ public enum L10n {
|
||||
public static var screenRoomDetailsInvitePeopleTitle: String { return L10n.tr("Localizable", "screen_room_details_invite_people_title") }
|
||||
/// Leave room
|
||||
public static var screenRoomDetailsLeaveRoomTitle: String { return L10n.tr("Localizable", "screen_room_details_leave_room_title") }
|
||||
/// Notification
|
||||
public static var screenRoomDetailsNotificationTitle: String { return L10n.tr("Localizable", "screen_room_details_notification_title") }
|
||||
/// People
|
||||
public static var screenRoomDetailsPeopleTitle: String { return L10n.tr("Localizable", "screen_room_details_people_title") }
|
||||
/// Room name
|
||||
@@ -724,6 +726,8 @@ public enum L10n {
|
||||
public static var screenRoomMemberListPendingHeaderTitle: String { return L10n.tr("Localizable", "screen_room_member_list_pending_header_title") }
|
||||
/// Room members
|
||||
public static var screenRoomMemberListRoomMembersHeaderTitle: String { return L10n.tr("Localizable", "screen_room_member_list_room_members_header_title") }
|
||||
/// You do not have permission to post to this room
|
||||
public static var screenRoomNoPermissionToPost: String { return L10n.tr("Localizable", "screen_room_no_permission_to_post") }
|
||||
/// Remove
|
||||
public static var screenRoomRetrySendMenuRemoveAction: String { return L10n.tr("Localizable", "screen_room_retry_send_menu_remove_action") }
|
||||
/// Send again
|
||||
|
||||
@@ -457,6 +457,11 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
set(value) { underlyingUpdatesPublisher = value }
|
||||
}
|
||||
var underlyingUpdatesPublisher: AnyPublisher<TimelineDiff, Never>!
|
||||
var timelineProvider: RoomTimelineProviderProtocol {
|
||||
get { return underlyingTimelineProvider }
|
||||
set(value) { underlyingTimelineProvider = value }
|
||||
}
|
||||
var underlyingTimelineProvider: RoomTimelineProviderProtocol!
|
||||
|
||||
//MARK: - loadAvatarURLForUserId
|
||||
|
||||
@@ -500,23 +505,6 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
return loadDisplayNameForUserIdReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - registerTimelineListenerIfNeeded
|
||||
|
||||
var registerTimelineListenerIfNeededCallsCount = 0
|
||||
var registerTimelineListenerIfNeededCalled: Bool {
|
||||
return registerTimelineListenerIfNeededCallsCount > 0
|
||||
}
|
||||
var registerTimelineListenerIfNeededReturnValue: Result<[TimelineItem], RoomProxyError>!
|
||||
var registerTimelineListenerIfNeededClosure: (() -> Result<[TimelineItem], RoomProxyError>)?
|
||||
|
||||
func registerTimelineListenerIfNeeded() -> Result<[TimelineItem], RoomProxyError> {
|
||||
registerTimelineListenerIfNeededCallsCount += 1
|
||||
if let registerTimelineListenerIfNeededClosure = registerTimelineListenerIfNeededClosure {
|
||||
return registerTimelineListenerIfNeededClosure()
|
||||
} else {
|
||||
return registerTimelineListenerIfNeededReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - paginateBackwards
|
||||
|
||||
var paginateBackwardsRequestSizeUntilNumberOfItemsCallsCount = 0
|
||||
@@ -559,6 +547,48 @@ class RoomProxyMock: RoomProxyProtocol {
|
||||
return sendReadReceiptForReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - messageEventContent
|
||||
|
||||
var messageEventContentForCallsCount = 0
|
||||
var messageEventContentForCalled: Bool {
|
||||
return messageEventContentForCallsCount > 0
|
||||
}
|
||||
var messageEventContentForReceivedEventID: String?
|
||||
var messageEventContentForReceivedInvocations: [String] = []
|
||||
var messageEventContentForReturnValue: RoomMessageEventContent?
|
||||
var messageEventContentForClosure: ((String) -> RoomMessageEventContent?)?
|
||||
|
||||
func messageEventContent(for eventID: String) -> RoomMessageEventContent? {
|
||||
messageEventContentForCallsCount += 1
|
||||
messageEventContentForReceivedEventID = eventID
|
||||
messageEventContentForReceivedInvocations.append(eventID)
|
||||
if let messageEventContentForClosure = messageEventContentForClosure {
|
||||
return messageEventContentForClosure(eventID)
|
||||
} else {
|
||||
return messageEventContentForReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - sendMessageEventContent
|
||||
|
||||
var sendMessageEventContentCallsCount = 0
|
||||
var sendMessageEventContentCalled: Bool {
|
||||
return sendMessageEventContentCallsCount > 0
|
||||
}
|
||||
var sendMessageEventContentReceivedMessageContent: RoomMessageEventContent?
|
||||
var sendMessageEventContentReceivedInvocations: [RoomMessageEventContent] = []
|
||||
var sendMessageEventContentReturnValue: Result<Void, RoomProxyError>!
|
||||
var sendMessageEventContentClosure: ((RoomMessageEventContent) async -> Result<Void, RoomProxyError>)?
|
||||
|
||||
func sendMessageEventContent(_ messageContent: RoomMessageEventContent) async -> Result<Void, RoomProxyError> {
|
||||
sendMessageEventContentCallsCount += 1
|
||||
sendMessageEventContentReceivedMessageContent = messageContent
|
||||
sendMessageEventContentReceivedInvocations.append(messageContent)
|
||||
if let sendMessageEventContentClosure = sendMessageEventContentClosure {
|
||||
return await sendMessageEventContentClosure(messageContent)
|
||||
} else {
|
||||
return sendMessageEventContentReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - sendMessage
|
||||
|
||||
var sendMessageInReplyToCallsCount = 0
|
||||
|
||||
@@ -74,7 +74,6 @@ extension RoomProxyMock {
|
||||
|
||||
updateMembersClosure = { }
|
||||
acceptInvitationClosure = { .success(()) }
|
||||
registerTimelineListenerIfNeededClosure = { .success([]) }
|
||||
underlyingUpdatesPublisher = Empty(completeImmediately: false).eraseToAnyPublisher()
|
||||
setNameClosure = { _ in .success(()) }
|
||||
setTopicClosure = { _ in .success(()) }
|
||||
|
||||
@@ -76,12 +76,15 @@ enum UserAvatarSizeOnScreen {
|
||||
enum RoomAvatarSizeOnScreen {
|
||||
case timeline
|
||||
case home
|
||||
case messageForwarding
|
||||
case details
|
||||
|
||||
var value: CGFloat {
|
||||
switch self {
|
||||
case .timeline:
|
||||
return 32
|
||||
case .messageForwarding:
|
||||
return 36
|
||||
case .home:
|
||||
return 52
|
||||
case .details:
|
||||
|
||||
@@ -23,7 +23,8 @@ struct FormRowAccessory: View {
|
||||
enum Kind {
|
||||
case navigationLink
|
||||
case progressView
|
||||
case selection(isSelected: Bool)
|
||||
case singleSelection(isSelected: Bool)
|
||||
case multipleSelection(isSelected: Bool)
|
||||
}
|
||||
|
||||
let kind: Kind
|
||||
@@ -36,8 +37,12 @@ struct FormRowAccessory: View {
|
||||
.init(kind: .progressView)
|
||||
}
|
||||
|
||||
static func selection(isSelected: Bool) -> Self {
|
||||
.init(kind: .selection(isSelected: isSelected))
|
||||
static func singleSelection(isSelected: Bool) -> Self {
|
||||
.init(kind: .singleSelection(isSelected: isSelected))
|
||||
}
|
||||
|
||||
static func multipleSelection(isSelected: Bool) -> Self {
|
||||
.init(kind: .multipleSelection(isSelected: isSelected))
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -48,7 +53,13 @@ struct FormRowAccessory: View {
|
||||
.foregroundColor(.compound.iconQuaternary)
|
||||
case .progressView:
|
||||
ProgressView()
|
||||
case .selection(let isSelected):
|
||||
case .singleSelection(let isSelected):
|
||||
if isSelected {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.compound.bodyLG)
|
||||
.foregroundColor(isSelected && isEnabled ? .compound.iconPrimary : .compound.iconTertiary)
|
||||
}
|
||||
case .multipleSelection(let isSelected):
|
||||
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
|
||||
.font(.compound.bodyLG)
|
||||
.foregroundColor(isSelected && isEnabled ? .compound.iconPrimary : .compound.iconTertiary)
|
||||
@@ -94,7 +105,16 @@ struct FormButtonStyle: PrimitiveButtonStyle {
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.padding(FormRow.insets) // Re-apply the normal insets using padding.
|
||||
.background(configuration.isPressed ? Color.compound.bgSubtlePrimary : .clear)
|
||||
.background(backgroundColor(for: configuration))
|
||||
}
|
||||
|
||||
private func backgroundColor(for configuration: Configuration) -> Color {
|
||||
switch accessory?.kind {
|
||||
case .none, .navigationLink, .progressView:
|
||||
return configuration.isPressed ? .compound.bgSubtlePrimary : .element.formRowBackground
|
||||
default:
|
||||
return .element.formRowBackground
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,7 +167,7 @@ struct FormButtonStyles_Previews: PreviewProvider {
|
||||
}
|
||||
.formSectionStyle()
|
||||
|
||||
Section {
|
||||
Section("Single selection") {
|
||||
Button { } label: {
|
||||
Text("Hello world")
|
||||
}
|
||||
@@ -156,18 +176,42 @@ struct FormButtonStyles_Previews: PreviewProvider {
|
||||
Button { } label: {
|
||||
Text("Selected")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: true)))
|
||||
.buttonStyle(FormButtonStyle(accessory: .singleSelection(isSelected: true)))
|
||||
|
||||
Button { } label: {
|
||||
Text("Selected (disabled)")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: true)))
|
||||
.buttonStyle(FormButtonStyle(accessory: .singleSelection(isSelected: true)))
|
||||
.disabled(true)
|
||||
|
||||
Button { } label: {
|
||||
Text("Unselected")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: false)))
|
||||
.buttonStyle(FormButtonStyle(accessory: .singleSelection(isSelected: false)))
|
||||
}
|
||||
.formSectionStyle()
|
||||
|
||||
Section("Multiple selection") {
|
||||
Button { } label: {
|
||||
Text("Hello world")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle())
|
||||
|
||||
Button { } label: {
|
||||
Text("Selected")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .multipleSelection(isSelected: true)))
|
||||
|
||||
Button { } label: {
|
||||
Text("Selected (disabled)")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .multipleSelection(isSelected: true)))
|
||||
.disabled(true)
|
||||
|
||||
Button { } label: {
|
||||
Text("Unselected")
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .multipleSelection(isSelected: false)))
|
||||
}
|
||||
.formSectionStyle()
|
||||
|
||||
|
||||
@@ -97,33 +97,33 @@ struct UserProfileCell_Previews: PreviewProvider {
|
||||
Button(action: action) {
|
||||
UserProfileCell(user: .mockAlice, membership: nil, imageProvider: MockMediaProvider())
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: true)))
|
||||
.buttonStyle(FormButtonStyle(accessory: .multipleSelection(isSelected: true)))
|
||||
.previewDisplayName("Selected user")
|
||||
|
||||
Button(action: action) {
|
||||
UserProfileCell(user: .mockBob, membership: nil, imageProvider: MockMediaProvider())
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: false)))
|
||||
.buttonStyle(FormButtonStyle(accessory: .multipleSelection(isSelected: false)))
|
||||
.previewDisplayName("Unselected user")
|
||||
|
||||
Button(action: action) {
|
||||
UserProfileCell(user: .mockCharlie, membership: .join, imageProvider: MockMediaProvider())
|
||||
}
|
||||
.disabled(true)
|
||||
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: true)))
|
||||
.buttonStyle(FormButtonStyle(accessory: .multipleSelection(isSelected: true)))
|
||||
.previewDisplayName("Selected disabled user")
|
||||
|
||||
Button(action: action) {
|
||||
UserProfileCell(user: .init(userID: "@someone:matrix.org"), membership: .join, imageProvider: MockMediaProvider())
|
||||
}
|
||||
.disabled(true)
|
||||
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: true)))
|
||||
.buttonStyle(FormButtonStyle(accessory: .multipleSelection(isSelected: true)))
|
||||
.previewDisplayName("Unverified joined user")
|
||||
|
||||
Button(action: action) {
|
||||
UserProfileCell(user: .init(userID: "@someone:matrix.org"), membership: nil, imageProvider: MockMediaProvider())
|
||||
}
|
||||
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: false)))
|
||||
.buttonStyle(FormButtonStyle(accessory: .multipleSelection(isSelected: false)))
|
||||
.previewDisplayName("Unverified user")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ struct AnalyticsPromptScreenStrings {
|
||||
let point3 = L10n.screenAnalyticsPromptSettings
|
||||
|
||||
init(termsURL: URL) {
|
||||
let content = AttributedString(L10n.screenAnalyticsPromptHelpUsImprove(InfoPlistReader.main.bundleDisplayName))
|
||||
let content = AttributedString(L10n.screenAnalyticsPromptHelpUsImprove)
|
||||
|
||||
// Create the 'read terms' with a placeholder.
|
||||
let linkPlaceholder = "{link}"
|
||||
|
||||
@@ -45,7 +45,7 @@ struct EmojiPickerScreen: View {
|
||||
.navigationTitle(L10n.commonReactions)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar { toolbar }
|
||||
.searchable(text: $searchString)
|
||||
.searchable(text: $searchString, placement: .navigationBarDrawer(displayMode: .always))
|
||||
.compoundSearchField()
|
||||
.onChange(of: searchString) { _ in
|
||||
context.send(viewAction: .search(searchString: searchString))
|
||||
|
||||
@@ -53,7 +53,7 @@ struct HomeScreenRoomCell: View {
|
||||
.accessibilityIdentifier(A11yIdentifiers.homeScreen.roomName(room.name))
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
@ViewBuilder @MainActor
|
||||
var avatar: some View {
|
||||
if dynamicTypeSize < .accessibility3 {
|
||||
LoadableAvatarImage(url: room.avatarURL,
|
||||
|
||||
@@ -79,7 +79,7 @@ struct InviteUsersScreen: View {
|
||||
imageProvider: context.imageProvider)
|
||||
}
|
||||
.disabled(context.viewState.isUserDisabled(user))
|
||||
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: context.viewState.isUserSelected(user))))
|
||||
.buttonStyle(FormButtonStyle(accessory: .multipleSelection(isSelected: context.viewState.isUserSelected(user))))
|
||||
}
|
||||
} header: {
|
||||
if let title = context.viewState.usersSection.title {
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct MessageForwardingScreenCoordinatorParameters {
|
||||
let roomSummaryProvider: RoomSummaryProviderProtocol
|
||||
let sourceRoomID: String
|
||||
}
|
||||
|
||||
enum MessageForwardingScreenCoordinatorAction {
|
||||
case dismiss
|
||||
case send(roomID: String)
|
||||
}
|
||||
|
||||
final class MessageForwardingScreenCoordinator: CoordinatorProtocol {
|
||||
private let parameters: MessageForwardingScreenCoordinatorParameters
|
||||
private var viewModel: MessageForwardingScreenViewModelProtocol
|
||||
private let actionsSubject: PassthroughSubject<MessageForwardingScreenCoordinatorAction, Never> = .init()
|
||||
private var cancellables: Set<AnyCancellable> = .init()
|
||||
|
||||
var actions: AnyPublisher<MessageForwardingScreenCoordinatorAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(parameters: MessageForwardingScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
|
||||
viewModel = MessageForwardingScreenViewModel(roomSummaryProvider: parameters.roomSummaryProvider,
|
||||
sourceRoomID: parameters.sourceRoomID)
|
||||
}
|
||||
|
||||
func start() {
|
||||
viewModel.actions.sink { [weak self] action in
|
||||
switch action {
|
||||
case .dismiss:
|
||||
self?.actionsSubject.send(.dismiss)
|
||||
case .send(let roomID):
|
||||
self?.actionsSubject.send(.send(roomID: roomID))
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(MessageForwardingScreen(context: viewModel.context))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum MessageForwardingScreenViewModelAction {
|
||||
case dismiss
|
||||
case send(roomID: String)
|
||||
}
|
||||
|
||||
struct MessageForwardingScreenViewState: BindableState {
|
||||
var rooms: [MessageForwardingRoom] = []
|
||||
var selectedRoomID: String?
|
||||
|
||||
var visibleRooms: [MessageForwardingRoom] {
|
||||
if bindings.searchQuery.isEmpty {
|
||||
return rooms
|
||||
}
|
||||
|
||||
return rooms.filter { $0.name.localizedStandardContains(bindings.searchQuery) }
|
||||
}
|
||||
|
||||
var bindings = MessageForwardingScreenViewStateBindings()
|
||||
}
|
||||
|
||||
struct MessageForwardingScreenViewStateBindings {
|
||||
var searchQuery = ""
|
||||
}
|
||||
|
||||
enum MessageForwardingScreenViewAction {
|
||||
case cancel
|
||||
case send
|
||||
case selectRoom(roomID: String)
|
||||
}
|
||||
|
||||
struct MessageForwardingRoom: Identifiable, Equatable {
|
||||
let id: String
|
||||
let name: String
|
||||
let alias: String?
|
||||
let avatarURL: URL?
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias MessageForwardingScreenViewModelType = StateStoreViewModel<MessageForwardingScreenViewState, MessageForwardingScreenViewAction>
|
||||
|
||||
class MessageForwardingScreenViewModel: MessageForwardingScreenViewModelType, MessageForwardingScreenViewModelProtocol {
|
||||
private let roomSummaryProvider: RoomSummaryProviderProtocol?
|
||||
private let sourceRoomID: String
|
||||
|
||||
private var actionsSubject: PassthroughSubject<MessageForwardingScreenViewModelAction, Never> = .init()
|
||||
|
||||
var actions: AnyPublisher<MessageForwardingScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(roomSummaryProvider: RoomSummaryProviderProtocol,
|
||||
sourceRoomID: String) {
|
||||
self.roomSummaryProvider = roomSummaryProvider
|
||||
self.sourceRoomID = sourceRoomID
|
||||
|
||||
super.init(initialViewState: MessageForwardingScreenViewState())
|
||||
|
||||
roomSummaryProvider.roomListPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
self?.updateRooms()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
updateRooms()
|
||||
}
|
||||
|
||||
override func process(viewAction: MessageForwardingScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .cancel:
|
||||
actionsSubject.send(.dismiss)
|
||||
case .send:
|
||||
guard let roomID = state.selectedRoomID else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
actionsSubject.send(.send(roomID: roomID))
|
||||
case .selectRoom(let roomID):
|
||||
state.selectedRoomID = roomID
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func updateRooms() {
|
||||
guard let roomSummaryProvider else {
|
||||
MXLog.error("Room summary provider unavailable")
|
||||
return
|
||||
}
|
||||
|
||||
MXLog.info("Updating rooms")
|
||||
|
||||
var rooms = [MessageForwardingRoom]()
|
||||
|
||||
for summary in roomSummaryProvider.roomListPublisher.value {
|
||||
switch summary {
|
||||
case .empty, .invalidated:
|
||||
continue
|
||||
case .filled(let details):
|
||||
if details.id == sourceRoomID {
|
||||
continue
|
||||
}
|
||||
|
||||
let room = MessageForwardingRoom(id: details.id, name: details.name, alias: details.canonicalAlias, avatarURL: details.avatarURL)
|
||||
rooms.append(room)
|
||||
}
|
||||
}
|
||||
|
||||
state.rooms = rooms
|
||||
|
||||
MXLog.info("Finished updating rooms")
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol MessageForwardingScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<MessageForwardingScreenViewModelAction, Never> { get }
|
||||
var context: MessageForwardingScreenViewModelType.Context { get }
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct MessageForwardingScreen: View {
|
||||
@ObservedObject var context: MessageForwardingScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
ForEach(context.viewState.visibleRooms) { room in
|
||||
MessageForwardingRoomCell(room: room, context: context)
|
||||
.buttonStyle(FormButtonStyle(accessory: .singleSelection(isSelected: context.viewState.selectedRoomID == room.id)))
|
||||
}
|
||||
}
|
||||
.formSectionStyle()
|
||||
}
|
||||
.listRowSeparator(.automatic)
|
||||
.elementFormStyle()
|
||||
.navigationTitle(L10n.commonForwardMessage)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button(L10n.actionCancel) {
|
||||
context.send(viewAction: .cancel)
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(L10n.actionSend) {
|
||||
context.send(viewAction: .send)
|
||||
}
|
||||
.disabled(context.viewState.selectedRoomID == nil)
|
||||
}
|
||||
}
|
||||
.searchable(text: $context.searchQuery, placement: .navigationBarDrawer(displayMode: .always))
|
||||
.compoundSearchField()
|
||||
.disableAutocorrection(true)
|
||||
}
|
||||
}
|
||||
|
||||
private struct MessageForwardingRoomCell: View {
|
||||
@Environment(\.dynamicTypeSize) var dynamicTypeSize
|
||||
|
||||
let room: MessageForwardingRoom
|
||||
let context: MessageForwardingScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
context.send(viewAction: .selectRoom(roomID: room.id))
|
||||
} label: {
|
||||
HStack(spacing: 16.0) {
|
||||
avatar
|
||||
|
||||
VStack(alignment: .leading, spacing: 2.0) {
|
||||
Text(room.name)
|
||||
.font(.compound.bodyMD)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.lineLimit(1)
|
||||
Text(room.alias ?? room.id)
|
||||
.font(.compound.bodySM)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder @MainActor
|
||||
var avatar: some View {
|
||||
if dynamicTypeSize < .accessibility3 {
|
||||
LoadableAvatarImage(url: room.avatarURL,
|
||||
name: room.name,
|
||||
contentID: room.id,
|
||||
avatarSize: .room(on: .messageForwarding),
|
||||
imageProvider: context.imageProvider)
|
||||
.dynamicTypeSize(dynamicTypeSize < .accessibility1 ? dynamicTypeSize : .accessibility1)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct MessageForwardingScreen_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let summaryProvider = MockRoomSummaryProvider(state: .loaded(.mockRooms))
|
||||
let viewModel = MessageForwardingScreenViewModel(roomSummaryProvider: summaryProvider, sourceRoomID: "")
|
||||
|
||||
NavigationStack {
|
||||
MessageForwardingScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,13 +89,6 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
|
||||
// MARK: - Private
|
||||
|
||||
private func setupRoomSubscription() {
|
||||
switch roomProxy.registerTimelineListenerIfNeeded() {
|
||||
case .success, .failure(.roomListenerAlreadyRegistered):
|
||||
break
|
||||
case .failure:
|
||||
MXLog.error("Failed to register a room listener in room's details for the room \(roomProxy.id)")
|
||||
}
|
||||
|
||||
roomProxy.updatesPublisher
|
||||
.throttle(for: .milliseconds(200), scheduler: DispatchQueue.main, latest: true)
|
||||
.sink { [weak self] _ in
|
||||
|
||||
@@ -32,6 +32,7 @@ enum RoomScreenCoordinatorAction {
|
||||
case presentRoomDetails
|
||||
case presentEmojiPicker(itemID: String)
|
||||
case presentRoomMemberDetails(member: RoomMemberProxyProtocol)
|
||||
case presentMessageForwarding(itemID: String)
|
||||
}
|
||||
|
||||
final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
@@ -54,6 +55,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
func start() {
|
||||
viewModel.callback = { [weak self] action in
|
||||
guard let self else { return }
|
||||
@@ -77,6 +79,8 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
|
||||
actionsSubject.send(.presentMediaUploadPreviewScreen(url))
|
||||
case .displayRoomMemberDetails(let member):
|
||||
actionsSubject.send(.presentRoomMemberDetails(member: member))
|
||||
case .displayMessageForwarding(let itemID):
|
||||
actionsSubject.send(.presentMessageForwarding(itemID: itemID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ enum RoomScreenViewModelAction {
|
||||
case displayDocumentPicker
|
||||
case displayMediaUploadPreviewScreen(url: URL)
|
||||
case displayRoomMemberDetails(member: RoomMemberProxyProtocol)
|
||||
case displayMessageForwarding(itemID: String)
|
||||
}
|
||||
|
||||
enum RoomScreenComposerMode: Equatable {
|
||||
|
||||
@@ -330,12 +330,14 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
}
|
||||
|
||||
var actions: [TimelineItemMenuAction] = [
|
||||
.reply, .copyPermalink
|
||||
.reply
|
||||
]
|
||||
|
||||
if timelineItem is EventBasedMessageTimelineItemProtocol {
|
||||
actions.append(contentsOf: [.copy, .quote])
|
||||
actions.append(contentsOf: [.forward(itemID: itemId), .copy, .quote])
|
||||
}
|
||||
|
||||
actions.append(.copyPermalink)
|
||||
|
||||
if item.isEditable {
|
||||
actions.append(.edit)
|
||||
@@ -403,6 +405,8 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
let replyDetails = TimelineItemReplyDetails.loaded(sender: eventTimelineItem.sender, contentType: buildReplyContent(for: eventTimelineItem))
|
||||
|
||||
state.composerMode = .reply(itemID: eventTimelineItem.id, replyDetails: replyDetails)
|
||||
case .forward(let itemID):
|
||||
callback?(.displayMessageForwarding(itemID: itemID))
|
||||
case .viewSource:
|
||||
let debugInfo = timelineController.debugInfo(for: eventTimelineItem.id)
|
||||
MXLog.info(debugInfo)
|
||||
|
||||
@@ -37,6 +37,7 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
|
||||
case copyPermalink
|
||||
case redact
|
||||
case reply
|
||||
case forward(itemID: String)
|
||||
case viewSource
|
||||
case retryDecryption(sessionID: String)
|
||||
case report
|
||||
@@ -193,6 +194,10 @@ public struct TimelineItemMenu: View {
|
||||
Button { send(action) } label: {
|
||||
MenuLabel(title: L10n.actionReply, systemImageName: "arrowshape.turn.up.left")
|
||||
}
|
||||
case .forward:
|
||||
Button { send(action) } label: {
|
||||
MenuLabel(title: L10n.actionForward, systemImageName: "arrowshape.turn.up.right")
|
||||
}
|
||||
case .redact:
|
||||
Button(role: .destructive) { send(action) } label: {
|
||||
MenuLabel(title: L10n.actionRemove, systemImageName: "trash")
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ struct AnalyticsSettingsScreenStrings {
|
||||
let sectionFooter: AttributedString
|
||||
|
||||
init(termsURL: URL) {
|
||||
let content = AttributedString(L10n.screenAnalyticsSettingsHelpUsImprove(InfoPlistReader.main.bundleDisplayName))
|
||||
let content = AttributedString(L10n.screenAnalyticsPromptHelpUsImprove)
|
||||
|
||||
// Create the 'read terms' with a placeholder.
|
||||
let linkPlaceholder = "{link}"
|
||||
|
||||
@@ -46,6 +46,11 @@ class RoomProxy: RoomProxyProtocol {
|
||||
updatesSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
var innerTimelineProvider: RoomTimelineProviderProtocol!
|
||||
var timelineProvider: RoomTimelineProviderProtocol {
|
||||
innerTimelineProvider
|
||||
}
|
||||
|
||||
deinit {
|
||||
Task { @MainActor [roomTimelineObservationToken, roomListItem] in
|
||||
roomTimelineObservationToken?.cancel()
|
||||
@@ -59,6 +64,30 @@ class RoomProxy: RoomProxyProtocol {
|
||||
self.roomListItem = roomListItem
|
||||
self.room = room
|
||||
self.backgroundTaskService = backgroundTaskService
|
||||
|
||||
let settings = RoomSubscription(requiredState: [RequiredState(key: "m.room.name", value: ""),
|
||||
RequiredState(key: "m.room.topic", value: ""),
|
||||
RequiredState(key: "m.room.avatar", value: ""),
|
||||
RequiredState(key: "m.room.canonical_alias", value: ""),
|
||||
RequiredState(key: "m.room.join_rules", value: "")],
|
||||
timelineLimit: UInt32(SlidingSyncConstants.defaultTimelineLimit))
|
||||
roomListItem.subscribe(settings: settings)
|
||||
|
||||
let timelineListener = RoomTimelineListener { [weak self] timelineDiff in
|
||||
self?.updatesSubject.send(timelineDiff)
|
||||
}
|
||||
|
||||
self.timelineListener = timelineListener
|
||||
|
||||
let result = room.addTimelineListener(listener: timelineListener)
|
||||
roomTimelineObservationToken = result.itemsStream
|
||||
|
||||
innerTimelineProvider = RoomTimelineProvider(currentItems: result.items, updatePublisher: updatesPublisher)
|
||||
|
||||
Task {
|
||||
await fetchMembers()
|
||||
await updateMembers()
|
||||
}
|
||||
}
|
||||
|
||||
lazy var id: String = room.id()
|
||||
@@ -162,36 +191,6 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func registerTimelineListenerIfNeeded() -> Result<[TimelineItem], RoomProxyError> {
|
||||
guard timelineListener == nil else {
|
||||
return .failure(.roomListenerAlreadyRegistered)
|
||||
}
|
||||
|
||||
let settings = RoomSubscription(requiredState: [RequiredState(key: "m.room.name", value: ""),
|
||||
RequiredState(key: "m.room.topic", value: ""),
|
||||
RequiredState(key: "m.room.avatar", value: ""),
|
||||
RequiredState(key: "m.room.canonical_alias", value: ""),
|
||||
RequiredState(key: "m.room.join_rules", value: "")],
|
||||
timelineLimit: UInt32(SlidingSyncConstants.defaultTimelineLimit))
|
||||
roomListItem.subscribe(settings: settings)
|
||||
|
||||
let timelineListener = RoomTimelineListener { [weak self] timelineDiff in
|
||||
self?.updatesSubject.send(timelineDiff)
|
||||
}
|
||||
|
||||
self.timelineListener = timelineListener
|
||||
|
||||
let result = room.addTimelineListener(listener: timelineListener)
|
||||
roomTimelineObservationToken = result.itemsStream
|
||||
|
||||
Task {
|
||||
await fetchMembers()
|
||||
await updateMembers()
|
||||
}
|
||||
|
||||
return .success(result.items)
|
||||
}
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomProxyError> {
|
||||
do {
|
||||
try await Task.dispatch(on: .global()) {
|
||||
@@ -219,6 +218,24 @@ class RoomProxy: RoomProxyProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func messageEventContent(for eventID: String) -> RoomMessageEventContent? {
|
||||
try? room.getTimelineEventContentByEventId(eventId: eventID)
|
||||
}
|
||||
|
||||
func sendMessageEventContent(_ messageContent: RoomMessageEventContent) async -> Result<Void, RoomProxyError> {
|
||||
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
|
||||
defer {
|
||||
sendMessageBackgroundTask?.stop()
|
||||
}
|
||||
|
||||
let transactionId = genTransactionId()
|
||||
|
||||
return await Task.dispatch(on: userInitiatedDispatchQueue) {
|
||||
self.room.send(msg: messageContent, txnId: transactionId)
|
||||
return .success(())
|
||||
}
|
||||
}
|
||||
|
||||
func sendMessage(_ message: String, inReplyTo eventID: String? = nil) async -> Result<Void, RoomProxyError> {
|
||||
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
|
||||
|
||||
@@ -20,7 +20,6 @@ import MatrixRustSDK
|
||||
|
||||
enum RoomProxyError: Error {
|
||||
case noMoreMessagesToBackPaginate
|
||||
case roomListenerAlreadyRegistered
|
||||
case failedPaginatingBackwards
|
||||
case failedRetrievingMemberAvatarURL
|
||||
case failedRetrievingMemberDisplayName
|
||||
@@ -73,22 +72,23 @@ protocol RoomProxyProtocol {
|
||||
var activeMembersCount: Int { get }
|
||||
|
||||
/// Publishes the room's updates.
|
||||
/// The publisher starts publishing after the first call to `registerTimelineListenerIfNeeded()`
|
||||
/// The thread on which this publisher sends the output isn't defined.
|
||||
var updatesPublisher: AnyPublisher<TimelineDiff, Never> { get }
|
||||
|
||||
var timelineProvider: RoomTimelineProviderProtocol { get }
|
||||
|
||||
func loadAvatarURLForUserId(_ userId: String) async -> Result<URL?, RoomProxyError>
|
||||
|
||||
func loadDisplayNameForUserId(_ userId: String) async -> Result<String?, RoomProxyError>
|
||||
|
||||
/// Registers a timeline listener if not registered already.
|
||||
/// Updates for this object will be published on the `updatesPublisher` publisher.
|
||||
func registerTimelineListenerIfNeeded() -> Result<[TimelineItem], RoomProxyError>
|
||||
|
||||
func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func sendReadReceipt(for eventID: String) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func messageEventContent(for eventID: String) -> RoomMessageEventContent?
|
||||
|
||||
func sendMessageEventContent(_ messageContent: RoomMessageEventContent) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func sendMessage(_ message: String, inReplyTo eventID: String?) async -> Result<Void, RoomProxyError>
|
||||
|
||||
func sendReaction(_ reaction: String, to eventID: String) async -> Result<Void, RoomProxyError>
|
||||
|
||||
@@ -19,7 +19,6 @@ import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
private let roomProxy: RoomProxyProtocol
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private let serialDispatchQueue: DispatchQueue
|
||||
|
||||
@@ -35,27 +34,15 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
init(roomProxy: RoomProxyProtocol) {
|
||||
self.roomProxy = roomProxy
|
||||
init(currentItems: [TimelineItem], updatePublisher: AnyPublisher<TimelineDiff, Never>) {
|
||||
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
|
||||
itemProxies = []
|
||||
|
||||
itemProxies = currentItems.map(TimelineItemProxy.init)
|
||||
|
||||
roomProxy
|
||||
.updatesPublisher
|
||||
updatePublisher
|
||||
.collect(.byTime(serialDispatchQueue, 0.1))
|
||||
.sink { [weak self] in self?.updateItemsWithDiffs($0) }
|
||||
.store(in: &cancellables)
|
||||
|
||||
switch roomProxy.registerTimelineListenerIfNeeded() {
|
||||
case let .success(items):
|
||||
itemProxies = items.map(TimelineItemProxy.init)
|
||||
MXLog.verbose("Added timeline listener, current items (\(items.count)) : \(items.map(\.debugIdentifier))")
|
||||
case .failure(.roomListenerAlreadyRegistered):
|
||||
MXLog.verbose("Listener already registered for the room: \(roomProxy.id)")
|
||||
case .failure:
|
||||
let roomID = roomProxy.id
|
||||
MXLog.error("Failed adding timeline listener on room with identifier: \(roomID)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
func debugInfo(for itemID: String) -> TimelineItemDebugInfo {
|
||||
.init(model: "Mock debug description", originalJSON: nil, latestEditJSON: nil)
|
||||
}
|
||||
|
||||
|
||||
func retryDecryption(for sessionID: String) async { }
|
||||
|
||||
// MARK: - UI Test signalling
|
||||
|
||||
@@ -43,17 +43,16 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
init(userId: String,
|
||||
roomProxy: RoomProxyProtocol,
|
||||
timelineProvider: RoomTimelineProviderProtocol,
|
||||
timelineItemFactory: RoomTimelineItemFactoryProtocol,
|
||||
mediaProvider: MediaProviderProtocol) {
|
||||
self.userId = userId
|
||||
self.timelineProvider = timelineProvider
|
||||
self.roomProxy = roomProxy
|
||||
timelineProvider = roomProxy.timelineProvider
|
||||
self.timelineItemFactory = timelineItemFactory
|
||||
self.mediaProvider = mediaProvider
|
||||
self.roomProxy = roomProxy
|
||||
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomtimelineprovider", qos: .utility)
|
||||
|
||||
self.timelineProvider
|
||||
timelineProvider
|
||||
.itemsPublisher
|
||||
.receive(on: serialDispatchQueue)
|
||||
.sink { [weak self] _ in
|
||||
|
||||
-1
@@ -23,7 +23,6 @@ struct RoomTimelineControllerFactory: RoomTimelineControllerFactoryProtocol {
|
||||
mediaProvider: MediaProviderProtocol) -> RoomTimelineControllerProtocol {
|
||||
RoomTimelineController(userId: userId,
|
||||
roomProxy: roomProxy,
|
||||
timelineProvider: RoomTimelineProvider(roomProxy: roomProxy),
|
||||
timelineItemFactory: timelineItemFactory,
|
||||
mediaProvider: mediaProvider)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import ElementX
|
||||
import XCTest
|
||||
|
||||
@MainActor
|
||||
class MessageForwardingScreenUITests: XCTestCase { }
|
||||
@@ -0,0 +1,69 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import XCTest
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
@MainActor
|
||||
class MessageForwardingScreenViewModelTests: XCTestCase {
|
||||
var viewModel: MessageForwardingScreenViewModelProtocol!
|
||||
var context: MessageForwardingScreenViewModelType.Context!
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
override func setUpWithError() throws {
|
||||
viewModel = MessageForwardingScreenViewModel(roomSummaryProvider: MockRoomSummaryProvider(state: .loaded(.mockRooms)), sourceRoomID: "1")
|
||||
context = viewModel.context
|
||||
}
|
||||
|
||||
func testInitialState() {
|
||||
XCTAssertNil(context.viewState.rooms.first(where: { $0.id == "1" }), "The source room ID shouldn't be shown")
|
||||
}
|
||||
|
||||
func testRoomSelection() {
|
||||
context.send(viewAction: .selectRoom(roomID: "2"))
|
||||
XCTAssertEqual(context.viewState.selectedRoomID, "2")
|
||||
}
|
||||
|
||||
func testSearching() {
|
||||
context.searchQuery = "Second"
|
||||
XCTAssertEqual(context.viewState.visibleRooms.count, 1)
|
||||
}
|
||||
|
||||
func testForwarding() {
|
||||
context.send(viewAction: .selectRoom(roomID: "2"))
|
||||
XCTAssertEqual(context.viewState.selectedRoomID, "2")
|
||||
|
||||
let expectation = expectation(description: "Wait for confirmation")
|
||||
|
||||
viewModel.actions
|
||||
.sink { action in
|
||||
switch action {
|
||||
case .send(let roomID):
|
||||
XCTAssertEqual(roomID, "2")
|
||||
expectation.fulfill()
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
context.send(viewAction: .send)
|
||||
|
||||
waitForExpectations(timeout: 5.0)
|
||||
}
|
||||
}
|
||||
@@ -221,11 +221,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
await Task.yield()
|
||||
XCTAssertTrue(callbackCorrectlyCalled)
|
||||
}
|
||||
|
||||
func testRoomSubscription() async {
|
||||
XCTAssertEqual(roomProxyMock.registerTimelineListenerIfNeededCallsCount, 1)
|
||||
}
|
||||
|
||||
|
||||
func testCanEditAvatar() async {
|
||||
let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: [.roomAvatar])
|
||||
let mockedMembers: [RoomMemberProxyMock] = [owner, .mockBob, .mockAlice]
|
||||
|
||||
@@ -38,7 +38,8 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
roomTimelineControllerFactory: MockRoomTimelineControllerFactory(),
|
||||
navigationStackCoordinator: navigationStackCoordinator,
|
||||
navigationSplitCoordinator: navigationSplitCoordinator,
|
||||
emojiProvider: EmojiProvider())
|
||||
emojiProvider: EmojiProvider(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
}
|
||||
|
||||
func testRoomPresentation() async {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Added support for message forwarding
|
||||
Reference in New Issue
Block a user