From fdee5aceccaeefd35f7d584db0e8109c8c971412 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 23 Jan 2024 10:13:48 +0200 Subject: [PATCH] Fixes #734 - Add a "View logs" option to the report a problem screen Move bug report and log viewer responsibility to a separate flow coordinator. Reuse it as a child coordinator in the authentication, userSessionFlow and settings flow coordinators. --- ElementX.xcodeproj/project.pbxproj | 40 ++++++ .../en.lproj/Localizable.strings | 1 + .../Application/FlowCoordinatorProtocol.swift | 1 + .../BugReportFlowCoordinator.swift | 127 ++++++++++++++++++ .../RoomFlowCoordinator.swift | 4 + .../SettingsFlowCoordinator.swift | 56 +++----- .../UserSessionFlowCoordinator.swift | 35 ++--- ElementX/Sources/Generated/Strings.swift | 2 + .../AuthenticationCoordinator.swift | 28 ++-- .../BugReportScreenCoordinator.swift | 23 ++-- .../BugReportScreenModels.swift | 2 + .../BugReportScreenViewModel.swift | 2 + .../View/BugReportScreen.swift | 3 + .../LogViewerScreenCoordinator.swift | 60 +++++++++ .../LogViewerScreenModels.swift | 29 ++++ .../LogViewerScreenViewModel.swift | 43 ++++++ .../LogViewerScreenViewModelProtocol.swift | 23 ++++ .../View/LogViewerScreen.swift | 85 ++++++++++++ .../UITests/UITestsAppCoordinator.swift | 47 ++++--- .../en-GB-iPad-9th-generation.bugReport-0.png | 4 +- .../en-GB-iPad-9th-generation.bugReport-2.png | 4 +- .../en-GB-iPad-9th-generation.bugReport-3.png | 4 +- ...9th-generation.bugReportWithScreenshot.png | 4 +- .../en-GB-iPhone-14.bugReport-0.png | 4 +- .../en-GB-iPhone-14.bugReport-2.png | 4 +- .../en-GB-iPhone-14.bugReport-3.png | 4 +- ...n-GB-iPhone-14.bugReportWithScreenshot.png | 4 +- ...pseudo-iPad-9th-generation.bugReport-0.png | 4 +- ...pseudo-iPad-9th-generation.bugReport-2.png | 4 +- ...pseudo-iPad-9th-generation.bugReport-3.png | 4 +- ...9th-generation.bugReportWithScreenshot.png | 4 +- .../pseudo-iPhone-14.bugReport-0.png | 4 +- .../pseudo-iPhone-14.bugReport-2.png | 4 +- .../pseudo-iPhone-14.bugReport-3.png | 4 +- ...eudo-iPhone-14.bugReportWithScreenshot.png | 4 +- .../PreviewTests/test_bugReport.1.png | 4 +- .../PreviewTests/test_bugReport.2.png | 4 +- changelog.d/734.feature | 1 + 38 files changed, 541 insertions(+), 143 deletions(-) create mode 100644 ElementX/Sources/FlowCoordinators/BugReportFlowCoordinator.swift create mode 100644 ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenCoordinator.swift create mode 100644 ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenModels.swift create mode 100644 ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenViewModel.swift create mode 100644 ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenViewModelProtocol.swift create mode 100644 ElementX/Sources/Screens/LogViewerScreen/View/LogViewerScreen.swift create mode 100644 changelog.d/734.feature diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 93d5c84d2..39c803532 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -219,6 +219,7 @@ 36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; }; 36AD4DD4C798E22584ED3200 /* Emojibase in Frameworks */ = {isa = PBXBuildFile; productRef = C05729B1684C331F5FFE9232 /* Emojibase */; }; 36CD6E11B37396E14F032CB6 /* WysiwygComposer in Frameworks */ = {isa = PBXBuildFile; productRef = CA07D57389DACE18AEB6A5E2 /* WysiwygComposer */; }; + 36DE961B784087D5E18EF9BA /* LogViewerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A07692536D66E3DA32C4964 /* LogViewerScreen.swift */; }; 370AF5BFCD4384DD455479B6 /* ElementCallWidgetDriverProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */; }; 377980ABF16525114E72DDE2 /* Version in Frameworks */ = {isa = PBXBuildFile; productRef = 2B9ACE4FCACB5A8812154424 /* Version */; }; 37906355E207DB5703754675 /* AppLockSetupBiometricsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F893F4A111CB7BA5C96949 /* AppLockSetupBiometricsScreenViewModel.swift */; }; @@ -332,6 +333,8 @@ 5455147CAC63F71E48F7D699 /* NSELogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3D455BC2423D911A62ACFB2 /* NSELogger.swift */; }; 54AE8860D668AFD96E7E177B /* UITestsScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CEBE5EA91E8691EDF364EC2 /* UITestsScreenIdentifier.swift */; }; 54C774874BED4A8FAD1F22FE /* AnalyticsConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77B3D4950F1707E66E4A45A /* AnalyticsConfiguration.swift */; }; + 5518DA4A6C9B4FC4B497EA9A /* LogViewerScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B795AAAB7B8747FE2FF311 /* LogViewerScreenModels.swift */; }; + 558E2673B04FDD06A1A12DD3 /* LogViewerScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7463464054DDF194C54F0B04 /* LogViewerScreenViewModelProtocol.swift */; }; 55CDD3968D95D1A820B5491E /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */; }; 562EFB9AB62B38830D9AA778 /* TimelineMediaFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 933B074F006F8E930DB98B4E /* TimelineMediaFrame.swift */; }; 564BF06B3E93D6DD55F903B2 /* CreateRoomCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C618CA2B6C8758B06C88013C /* CreateRoomCoordinator.swift */; }; @@ -654,6 +657,7 @@ A6B83EB78F025D21B6EBA90C /* CompoundIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 044E501B8331B339874D1B96 /* CompoundIcon.swift */; }; A6D4C5EEA85A6A0ABA1559D6 /* RoomDetailsEditScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 16D09C79746BDCD9173EB3A7 /* RoomDetailsEditScreenModels.swift */; }; A6DEC1ADEC8FEEC206A0FA37 /* AttributedStringBuilderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */; }; + A6F345328CCC5C9B0DAE2257 /* LogViewerScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BB05221D7D941CC82DC8480 /* LogViewerScreenViewModel.swift */; }; A722F426FD81FC67706BB1E0 /* CustomLayoutLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42236480CF0431535EBE8387 /* CustomLayoutLabelStyle.swift */; }; A743841F91B62B0E56217B04 /* SecureBackupKeyBackupScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58DCB219D7B7B0299358FF81 /* SecureBackupKeyBackupScreenUITests.swift */; }; A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; }; @@ -663,6 +667,7 @@ A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; }; A8771F5975A82759FA5138AE /* RoomMemberDetailsScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F19DBE940499D3E3DD405D8 /* RoomMemberDetailsScreenUITests.swift */; }; A896998A6784DB6F16E912F4 /* MockMediaLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AB7D7DAAAF662DED9D02379 /* MockMediaLoader.swift */; }; + A8FA7671948E3DF27F320026 /* BugReportFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */; }; A93661C962B12942C08864B6 /* DSWaveformImageViews in Frameworks */ = {isa = PBXBuildFile; productRef = 2A4106A0A96DC4C273128AA5 /* DSWaveformImageViews */; }; A9482B967FC85DA611514D35 /* VoiceMessageRoomPlaybackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCD41CD67DB5DA0D436BFE9 /* VoiceMessageRoomPlaybackView.swift */; }; A969147E0EEE0E27EE226570 /* MediaUploadPreviewScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47F29139BC2A804CE5E0757E /* MediaUploadPreviewScreenViewModel.swift */; }; @@ -817,6 +822,7 @@ CE9530A4CA661E090635C2F2 /* NotificationItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 25F7FE40EF7490A7E09D7BE6 /* NotificationItemProxy.swift */; }; CEB8FB1269DE20536608B957 /* LoginMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B41FABA2B0AEF4389986495 /* LoginMode.swift */; }; CF3827071B0BC9638BD44F5D /* WaitlistScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB58EF0176D4CFB1040DA22 /* WaitlistScreenViewModel.swift */; }; + CF38B70D8C6DD42C00A56A27 /* LogViewerScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A84D413BF49F0E980F010A6B /* LogViewerScreenCoordinator.swift */; }; CF4044A8EED5C41BC0ED6ABE /* SoftLogoutScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D316BB02636AF2174F2580E6 /* SoftLogoutScreenViewModelProtocol.swift */; }; CFEC53440C572CEEABC4A6A0 /* ElementXAttributeScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */; }; D02AA6208C7ACB9BE6332394 /* UNNotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */; }; @@ -1054,6 +1060,7 @@ 00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreen.swift; sourceTree = ""; }; 00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomDetailsEditScreenViewModelTests.swift; sourceTree = ""; }; 0135A608FFAD86E6674EE730 /* RoomScreenInteractionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenInteractionHandler.swift; sourceTree = ""; }; + 01B795AAAB7B8747FE2FF311 /* LogViewerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenModels.swift; sourceTree = ""; }; 01C4C7DB37597D7D8379511A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 022E6BD64CB4610B9C95FC02 /* UserDetailsEditScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetailsEditScreenViewModel.swift; sourceTree = ""; }; 024F7398C5FC12586FB10E9D /* EffectsScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EffectsScene.swift; sourceTree = ""; }; @@ -1091,6 +1098,7 @@ 0A634D8DD1E10D858CF7995D /* VoiceMessageRecordingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageRecordingView.swift; sourceTree = ""; }; 0AE449DFBA7CC863EEB2FD2A /* FormattingToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattingToolbar.swift; sourceTree = ""; }; 0B987FC3FDBAA0E1C5AA235C /* PaginationIndicatorRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginationIndicatorRoomTimelineItem.swift; sourceTree = ""; }; + 0BB05221D7D941CC82DC8480 /* LogViewerScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenViewModel.swift; sourceTree = ""; }; 0BC588051E6572A1AF51D738 /* TimelineSenderAvatarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineSenderAvatarView.swift; sourceTree = ""; }; 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModel.swift; sourceTree = ""; }; 0C34667458773B02AB5FB0B2 /* LegalInformationScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegalInformationScreenViewModel.swift; sourceTree = ""; }; @@ -1387,6 +1395,7 @@ 592A35163B0749C66BFD6186 /* MapLibreStaticMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapLibreStaticMapView.swift; sourceTree = ""; }; 596AA8843AC1A234F3387767 /* SecureBackupRecoveryKeyScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupRecoveryKeyScreenCoordinator.swift; sourceTree = ""; }; 59846FA04E1DBBFDD8829C2A /* MessageForwardingScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenUITests.swift; sourceTree = ""; }; + 5A07692536D66E3DA32C4964 /* LogViewerScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreen.swift; sourceTree = ""; }; 5A1119E9C63AE530252640D2 /* SecureBackupController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupController.swift; sourceTree = ""; }; 5A2FCA3D0F239B9E911B966B /* SecureBackupRecoveryKeyScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupRecoveryKeyScreen.swift; sourceTree = ""; }; 5A37E2FACFD041CE466223CD /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -1467,8 +1476,10 @@ 71D52BAA5BADB06E5E8C295D /* Assets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Assets.swift; sourceTree = ""; }; 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilderProtocol.swift; sourceTree = ""; }; 7310D8DFE01AF45F0689C3AA /* Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = ""; }; + 7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportFlowCoordinator.swift; sourceTree = ""; }; 745323FCF9AF21A117252C53 /* RoundedLabelItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedLabelItem.swift; sourceTree = ""; }; 74611A4182DCF5F4D42696EC /* XCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTestCase.swift; sourceTree = ""; }; + 7463464054DDF194C54F0B04 /* LogViewerScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenViewModelProtocol.swift; sourceTree = ""; }; 7475C5AE20BA896930907EA8 /* AudioRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRoomTimelineItemContent.swift; sourceTree = ""; }; 748AE77AC3B0A01223033B87 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 74C6F3DAD167F972702C8893 /* TimelineItemAccessibilityModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemAccessibilityModifier.swift; sourceTree = ""; }; @@ -1635,6 +1646,7 @@ A6C11AD9813045E44F950410 /* ElementCallWidgetDriverProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementCallWidgetDriverProtocol.swift; sourceTree = ""; }; A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = ""; }; A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = ""; }; + A84D413BF49F0E980F010A6B /* LogViewerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewerScreenCoordinator.swift; sourceTree = ""; }; A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = ""; }; A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = ""; }; A9FAFE1C2149E6AC8156ED2B /* Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = ""; }; @@ -2996,6 +3008,7 @@ children = ( FCE93F0CBF0D96B77111C413 /* AppLockFlowCoordinator.swift */, 0DBB08A95EFA668F2CF27211 /* AppLockSetupFlowCoordinator.swift */, + 7367B3B9A8CAF902220F31D1 /* BugReportFlowCoordinator.swift */, 9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */, D28F7A6CEEA4A2815B0F0F55 /* SettingsFlowCoordinator.swift */, C99FDEEB71173C4C6FA2734C /* UserSessionFlowCoordinator.swift */, @@ -3336,6 +3349,18 @@ path = Sources; sourceTree = ""; }; + 73E032ADD008D63812791D97 /* LogViewerScreen */ = { + isa = PBXGroup; + children = ( + A84D413BF49F0E980F010A6B /* LogViewerScreenCoordinator.swift */, + 01B795AAAB7B8747FE2FF311 /* LogViewerScreenModels.swift */, + 0BB05221D7D941CC82DC8480 /* LogViewerScreenViewModel.swift */, + 7463464054DDF194C54F0B04 /* LogViewerScreenViewModelProtocol.swift */, + 786BF682AB6BA65DC8F53D3D /* View */, + ); + path = LogViewerScreen; + sourceTree = ""; + }; 7431C962E314ADAE38B6D708 /* Analytics */ = { isa = PBXGroup; children = ( @@ -3408,6 +3433,14 @@ path = Navigation; sourceTree = ""; }; + 786BF682AB6BA65DC8F53D3D /* View */ = { + isa = PBXGroup; + children = ( + 5A07692536D66E3DA32C4964 /* LogViewerScreen.swift */, + ); + path = View; + sourceTree = ""; + }; 78915D878159D302395D57BF /* SupportingFiles */ = { isa = PBXGroup; children = ( @@ -4490,6 +4523,7 @@ E3EA13D6E41AD76151C2D100 /* InvitesScreen */, F12966DF3DA87FEF21348D60 /* InviteUsersScreen */, 948DD12A5533BE1BC260E437 /* LocationSharing */, + 73E032ADD008D63812791D97 /* LogViewerScreen */, 87E2774157D9C4894BCFF3F8 /* MediaPickerScreen */, 23605DD08620BE6558242469 /* MediaUploadPreviewScreen */, 3348D14DBDB54E72FC67E2F3 /* MessageForwardingScreen */, @@ -5404,6 +5438,7 @@ 5EE1D4E316D66943E97FDCF2 /* BloomView.swift in Sources */, B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */, E794AB6ABE1FF5AF0573FEA1 /* BlurHashEncode.swift in Sources */, + A8FA7671948E3DF27F320026 /* BugReportFlowCoordinator.swift in Sources */, 6AD722DD92E465E56D2885AB /* BugReportScreen.swift in Sources */, B98A20A093A4FB785BFCCA53 /* BugReportScreenCoordinator.swift in Sources */, 4FFDC274824F7CC0BBDF581E /* BugReportScreenModels.swift in Sources */, @@ -5558,6 +5593,11 @@ 854E82E064BA53CD0BC45600 /* LocationRoomTimelineItemContent.swift in Sources */, D9473FC9B077A6EDB7A12001 /* LocationRoomTimelineView.swift in Sources */, 29491EE7AE37E239E839C5A3 /* LocationSharingScreenModels.swift in Sources */, + 36DE961B784087D5E18EF9BA /* LogViewerScreen.swift in Sources */, + CF38B70D8C6DD42C00A56A27 /* LogViewerScreenCoordinator.swift in Sources */, + 5518DA4A6C9B4FC4B497EA9A /* LogViewerScreenModels.swift in Sources */, + A6F345328CCC5C9B0DAE2257 /* LogViewerScreenViewModel.swift in Sources */, + 558E2673B04FDD06A1A12DD3 /* LogViewerScreenViewModelProtocol.swift in Sources */, 872A6457DF573AF8CEAE927A /* LoginHomeserver.swift in Sources */, CEB8FB1269DE20536608B957 /* LoginMode.swift in Sources */, 5375902175B2FEA2949D7D74 /* LoginScreen.swift in Sources */, diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index dd2d37df3..28198c20a 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -342,6 +342,7 @@ "screen_bug_report_include_logs" = "Allow logs"; "screen_bug_report_include_screenshot" = "Send screenshot"; "screen_bug_report_logs_description" = "Logs will be included with your message to make sure that everything is working properly. To send your message without logs, turn off this setting."; +"screen_bug_report_view_logs" = "View logs"; "screen_change_account_provider_matrix_org_subtitle" = "Matrix.org is a large, free server on the public Matrix network for secure, decentralised communication, run by the Matrix.org Foundation."; "screen_change_account_provider_other" = "Other"; "screen_change_account_provider_subtitle" = "Use a different account provider, such as your own private server or a work account."; diff --git a/ElementX/Sources/Application/FlowCoordinatorProtocol.swift b/ElementX/Sources/Application/FlowCoordinatorProtocol.swift index e3e3e9161..aae334246 100644 --- a/ElementX/Sources/Application/FlowCoordinatorProtocol.swift +++ b/ElementX/Sources/Application/FlowCoordinatorProtocol.swift @@ -19,6 +19,7 @@ import Foundation // periphery:ignore - markdown protocol @MainActor protocol FlowCoordinatorProtocol { + func start() func handleAppRoute(_ appRoute: AppRoute, animated: Bool) func clearRoute(animated: Bool) } diff --git a/ElementX/Sources/FlowCoordinators/BugReportFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/BugReportFlowCoordinator.swift new file mode 100644 index 000000000..1b70c6afe --- /dev/null +++ b/ElementX/Sources/FlowCoordinators/BugReportFlowCoordinator.swift @@ -0,0 +1,127 @@ +// +// Copyright 2024 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 + +struct BugReportFlowCoordinatorParameters { + enum PresentationMode { + case sheet(NavigationStackCoordinator) + case push(NavigationStackCoordinator) + } + + let presentationMode: PresentationMode + let userIndicatorController: UserIndicatorControllerProtocol + let bugReportService: BugReportServiceProtocol + let userID: String? + let deviceID: String? +} + +class BugReportFlowCoordinator: FlowCoordinatorProtocol { + private let parameters: BugReportFlowCoordinatorParameters + private var cancellables = Set() + + private var internalNavigationStackCoordinator: NavigationStackCoordinator? + + private var isModallyPresented: Bool { + switch parameters.presentationMode { + case .sheet: + return true + case .push: + return false + } + } + + init(parameters: BugReportFlowCoordinatorParameters) { + self.parameters = parameters + } + + func start() { + presentBugReportScreen() + } + + func handleAppRoute(_ appRoute: AppRoute, animated: Bool) { + fatalError() + } + + func clearRoute(animated: Bool) { + fatalError() + } + + // MARK: - Private + + private func presentBugReportScreen() { + let params = BugReportScreenCoordinatorParameters(bugReportService: parameters.bugReportService, + userID: parameters.userID, + deviceID: parameters.deviceID, + userIndicatorController: parameters.userIndicatorController, + screenshot: nil, + isModallyPresented: isModallyPresented) + let coordinator = BugReportScreenCoordinator(parameters: params) + coordinator.actions.sink { [weak self] action in + guard let self else { return } + + switch action { + case .cancel: + dismiss() + case .viewLogs: + presentLogViewerScreen() + case .finish: + showSuccess(label: L10n.actionDone) + dismiss() + } + } + .store(in: &cancellables) + + switch parameters.presentationMode { + case .sheet(let navigationStackCoordinator): + let internalNavigationStackCoordinator = NavigationStackCoordinator() + internalNavigationStackCoordinator.setRootCoordinator(coordinator) + navigationStackCoordinator.setSheetCoordinator(internalNavigationStackCoordinator) + self.internalNavigationStackCoordinator = internalNavigationStackCoordinator + case .push(let navigationStackCoordinator): + internalNavigationStackCoordinator = navigationStackCoordinator + navigationStackCoordinator.push(coordinator) + } + } + + private func presentLogViewerScreen() { + let coordinator = LogViewerScreenCoordinator(parameters: .init()) + coordinator.actions.sink { [weak self] action in + guard let self else { return } + + switch action { + case .done: + internalNavigationStackCoordinator?.pop() + } + } + .store(in: &cancellables) + + internalNavigationStackCoordinator?.push(coordinator) + } + + private func dismiss() { + switch parameters.presentationMode { + case .push(let navigationStackCoordinator): + navigationStackCoordinator.pop() + case .sheet(let navigationStackCoordinator): + navigationStackCoordinator.setSheetCoordinator(nil) + } + } + + private func showSuccess(label: String) { + parameters.userIndicatorController.submitIndicator(UserIndicator(title: label)) + } +} diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 6142520a8..59043c67c 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -91,6 +91,10 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { // MARK: - FlowCoordinatorProtocol + func start() { + fatalError("This flow coordinator expect a route") + } + func handleAppRoute(_ appRoute: AppRoute, animated: Bool) { switch appRoute { case .room(let roomID): diff --git a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift index 076718d3f..0878bec67 100644 --- a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift @@ -48,6 +48,9 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { // periphery:ignore - retaining purpose private var appLockSetupFlowCoordinator: AppLockSetupFlowCoordinator? + // periphery:ignore - retaining purpose + private var bugReportFlowCoordinator: BugReportFlowCoordinator? + private let actionsSubject: PassthroughSubject = .init() var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() @@ -57,6 +60,10 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { self.parameters = parameters } + func start() { + fatalError("Unavailable") + } + func handleAppRoute(_ appRoute: AppRoute, animated: Bool) { switch appRoute { case .settings: @@ -76,17 +83,17 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { } } - func clearRoute(animated: Bool) { } + func clearRoute(animated: Bool) { + fatalError("Unavailable") + } // MARK: - Private private func presentSettingsScreen(animated: Bool) { navigationStackCoordinator = NavigationStackCoordinator() - let parameters = SettingsScreenCoordinatorParameters(userSession: parameters.userSession, - appSettings: parameters.appSettings) - - let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: parameters) + let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: .init(userSession: parameters.userSession, + appSettings: parameters.appSettings)) settingsScreenCoordinator.actions .sink { [weak self] action in @@ -94,9 +101,9 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { switch action { case .dismiss: - self.parameters.navigationSplitCoordinator.setSheetCoordinator(nil) + parameters.navigationSplitCoordinator.setSheetCoordinator(nil) case .logout: - self.parameters.navigationSplitCoordinator.setSheetCoordinator(nil) + parameters.navigationSplitCoordinator.setSheetCoordinator(nil) // The settings sheet needs to be dismissed before the alert can be shown DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { @@ -113,7 +120,12 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { case .appLock: presentAppLockSetupFlow() case .bugReport: - presentBugReportScreen() + bugReportFlowCoordinator = BugReportFlowCoordinator(parameters: .init(presentationMode: .push(navigationStackCoordinator), + userIndicatorController: parameters.userIndicatorController, + bugReportService: parameters.bugReportService, + userID: parameters.userSession.userID, + deviceID: parameters.userSession.deviceID)) + bugReportFlowCoordinator?.start() case .about: presentLegalInformationScreen() case .sessionVerification: @@ -132,7 +144,7 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { navigationStackCoordinator.setRootCoordinator(settingsScreenCoordinator, animated: animated) - self.parameters.navigationSplitCoordinator.setSheetCoordinator(navigationStackCoordinator) { [weak self] in + parameters.navigationSplitCoordinator.setSheetCoordinator(navigationStackCoordinator) { [weak self] in guard let self else { return } navigationStackCoordinator = nil @@ -187,28 +199,6 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { coordinator.start() } - private func presentBugReportScreen() { - let params = BugReportScreenCoordinatorParameters(bugReportService: parameters.bugReportService, - userID: parameters.userSession.userID, - deviceID: parameters.userSession.deviceID, - userIndicatorController: parameters.userIndicatorController, - screenshot: nil, - isModallyPresented: false) - let coordinator = BugReportScreenCoordinator(parameters: params) - coordinator.completion = { [weak self] result in - switch result { - case .finish: - self?.showSuccess(label: L10n.actionDone) - default: - break - } - - self?.navigationStackCoordinator.pop() - } - - navigationStackCoordinator.push(coordinator) - } - private func presentLegalInformationScreen() { navigationStackCoordinator.push(LegalInformationScreenCoordinator(appSettings: parameters.appSettings)) } @@ -269,10 +259,6 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { navigationStackCoordinator.push(coordinator) } - private func showSuccess(label: String) { - parameters.userIndicatorController.submitIndicator(UserIndicator(title: label)) - } - // MARK: OIDC Account Management private func presentAccountProfileURL() { diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index 88fa162fe..a7025dc32 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -33,9 +33,14 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { private let actionsSubject: PassthroughSubject = .init() private let stateMachine: UserSessionFlowCoordinatorStateMachine + private let roomFlowCoordinator: RoomFlowCoordinator + private let settingsFlowCoordinator: SettingsFlowCoordinator + // periphery:ignore - retaining purpose + private var bugReportFlowCoordinator: BugReportFlowCoordinator? + private var cancellables = Set() private var migrationCancellable: AnyCancellable? @@ -250,7 +255,12 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { break case (.roomList, .feedbackScreen, .feedbackScreen): - presentFeedbackScreen(animated: animated) + bugReportFlowCoordinator = BugReportFlowCoordinator(parameters: .init(presentationMode: .sheet(sidebarNavigationStackCoordinator), + userIndicatorController: ServiceLocator.shared.userIndicatorController, + bugReportService: bugReportService, + userID: userSession.userID, + deviceID: userSession.deviceID)) + bugReportFlowCoordinator?.start() case (.feedbackScreen, .dismissedFeedbackScreen, .roomList): break @@ -483,30 +493,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { self?.stateMachine.processEvent(.dismissedStartChatScreen) } } - - // MARK: Bug reporting - - private func presentFeedbackScreen(animated: Bool, for image: UIImage? = nil) { - let feedbackNavigationStackCoordinator = NavigationStackCoordinator() - let parameters = BugReportScreenCoordinatorParameters(bugReportService: bugReportService, - userID: userSession.userID, - deviceID: userSession.deviceID, - userIndicatorController: ServiceLocator.shared.userIndicatorController, - screenshot: image, - isModallyPresented: true) - let coordinator = BugReportScreenCoordinator(parameters: parameters) - coordinator.completion = { [weak self] _ in - self?.navigationSplitCoordinator.setSheetCoordinator(nil) - } - - feedbackNavigationStackCoordinator.setRootCoordinator(coordinator) - - navigationSplitCoordinator.setSheetCoordinator(feedbackNavigationStackCoordinator, animated: animated) { [weak self] in - self?.stateMachine.processEvent(.dismissedFeedbackScreen) - } - } - // MARK: Invites list private func presentInvitesList(animated: Bool) { diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index 47b793a93..57672c75b 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -838,6 +838,8 @@ public enum L10n { public static func screenBugReportRashLogsAlertTitle(_ p1: Any) -> String { return L10n.tr("Localizable", "screen_bug_report_rash_logs_alert_title", String(describing: p1)) } + /// View logs + public static var screenBugReportViewLogs: String { return L10n.tr("Localizable", "screen_bug_report_view_logs") } /// Matrix.org is a large, free server on the public Matrix network for secure, decentralised communication, run by the Matrix.org Foundation. public static var screenChangeAccountProviderMatrixOrgSubtitle: String { return L10n.tr("Localizable", "screen_change_account_provider_matrix_org_subtitle") } /// Other diff --git a/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift b/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift index a8a682c9a..e5c872b5f 100644 --- a/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/AuthenticationCoordinator.swift @@ -34,9 +34,13 @@ class AuthenticationCoordinator: CoordinatorProtocol { private var cancellables = Set() private var oidcPresenter: OIDCAuthenticationPresenter? + // periphery: ignore - used to store the coordinator to avoid deallocation private var appLockFlowCoordinator: AppLockSetupFlowCoordinator? + // periphery:ignore - retaining purpose + private var bugReportFlowCoordinator: BugReportFlowCoordinator? + weak var delegate: AuthenticationCoordinatorDelegate? init(authenticationService: AuthenticationServiceProxyProtocol, @@ -94,24 +98,12 @@ class AuthenticationCoordinator: CoordinatorProtocol { } private func showReportProblemScreen() { - let feedbackNavigationStackCoordinator = NavigationStackCoordinator() - - let parameters = BugReportScreenCoordinatorParameters(bugReportService: bugReportService, - userID: nil, - deviceID: nil, - userIndicatorController: ServiceLocator.shared.userIndicatorController, - screenshot: nil, - isModallyPresented: true) - - let coordinator = BugReportScreenCoordinator(parameters: parameters) - - coordinator.completion = { [weak self] _ in - self?.navigationStackCoordinator.setSheetCoordinator(nil) - } - - feedbackNavigationStackCoordinator.setRootCoordinator(coordinator) - - navigationStackCoordinator.setSheetCoordinator(feedbackNavigationStackCoordinator, animated: true) + bugReportFlowCoordinator = BugReportFlowCoordinator(parameters: .init(presentationMode: .sheet(navigationStackCoordinator), + userIndicatorController: ServiceLocator.shared.userIndicatorController, + bugReportService: bugReportService, + userID: nil, + deviceID: nil)) + bugReportFlowCoordinator?.start() } private func startAuthentication() async { diff --git a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenCoordinator.swift b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenCoordinator.swift index c50a7d5fa..0ed65537e 100644 --- a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenCoordinator.swift +++ b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenCoordinator.swift @@ -17,8 +17,9 @@ import Combine import SwiftUI -enum BugReportScreenCoordinatorResult { +enum BugReportScreenCoordinatorAction { case cancel + case viewLogs case finish } @@ -37,7 +38,10 @@ final class BugReportScreenCoordinator: CoordinatorProtocol { private var viewModel: BugReportScreenViewModelProtocol private var cancellables = Set() - var completion: ((BugReportScreenCoordinatorResult) -> Void)? + private let actionsSubject: PassthroughSubject = .init() + var actions: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } init(parameters: BugReportScreenCoordinatorParameters) { self.parameters = parameters @@ -56,18 +60,21 @@ final class BugReportScreenCoordinator: CoordinatorProtocol { .actions .sink { [weak self] action in guard let self else { return } + MXLog.info("BugReportViewModel did complete with result: \(action).") switch action { case .cancel: - self.completion?(.cancel) + actionsSubject.send(.cancel) + case .viewLogs: + actionsSubject.send(.viewLogs) case let .submitStarted(progressPublisher): - self.startLoading(label: L10n.commonSending, progressPublisher: progressPublisher) + startLoading(label: L10n.commonSending, progressPublisher: progressPublisher) case .submitFinished: - self.stopLoading() - self.completion?(.finish) + stopLoading() + actionsSubject.send(.finish) case .submitFailed(let error): - self.stopLoading() - self.showError(label: error.localizedDescription) + stopLoading() + showError(label: error.localizedDescription) } } .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenModels.swift b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenModels.swift index 3d483f9f3..df14fddb5 100644 --- a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenModels.swift +++ b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenModels.swift @@ -19,6 +19,7 @@ import UIKit enum BugReportScreenViewModelAction { case cancel + case viewLogs case submitStarted(progressPublisher: CurrentValuePublisher) case submitFinished case submitFailed(error: Error) @@ -42,4 +43,5 @@ enum BugReportScreenViewAction { case submit case removeScreenshot case attachScreenshot(UIImage) + case viewLogs } diff --git a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenViewModel.swift b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenViewModel.swift index 27f12000f..f4e11fd53 100644 --- a/ElementX/Sources/Screens/BugReportScreen/BugReportScreenViewModel.swift +++ b/ElementX/Sources/Screens/BugReportScreen/BugReportScreenViewModel.swift @@ -53,6 +53,8 @@ class BugReportScreenViewModel: BugReportScreenViewModelType, BugReportScreenVie case .cancel: uploadTask = nil actionsSubject.send(.cancel) + case .viewLogs: + actionsSubject.send(.viewLogs) case .submit: state.shouldDisableInteraction = true uploadTask = Task { await submitBugReport() } diff --git a/ElementX/Sources/Screens/BugReportScreen/View/BugReportScreen.swift b/ElementX/Sources/Screens/BugReportScreen/View/BugReportScreen.swift index 6780620e7..de4b2aaaf 100644 --- a/ElementX/Sources/Screens/BugReportScreen/View/BugReportScreen.swift +++ b/ElementX/Sources/Screens/BugReportScreen/View/BugReportScreen.swift @@ -68,6 +68,9 @@ struct BugReportScreen: View { ListRow(label: .plain(title: L10n.screenBugReportIncludeLogs), kind: .toggle($context.sendingLogsEnabled)) .accessibilityIdentifier(A11yIdentifiers.bugReportScreen.sendLogs) + ListRow(label: .plain(title: L10n.screenBugReportViewLogs), + kind: .navigationLink { context.send(viewAction: .viewLogs) }) + .accessibilityIdentifier(A11yIdentifiers.bugReportScreen.sendLogs) } footer: { Text(L10n.screenBugReportLogsDescription) .compoundListSectionFooter() diff --git a/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenCoordinator.swift b/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenCoordinator.swift new file mode 100644 index 000000000..e7dbbf6b4 --- /dev/null +++ b/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenCoordinator.swift @@ -0,0 +1,60 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// periphery:ignore:all - this is just a logViewer remove this comment once generating the final file + +import Combine +import SwiftUI + +struct LogViewerScreenCoordinatorParameters { } + +enum LogViewerScreenCoordinatorAction { + case done +} + +final class LogViewerScreenCoordinator: CoordinatorProtocol { + private let parameters: LogViewerScreenCoordinatorParameters + private var viewModel: LogViewerScreenViewModelProtocol + private var cancellables = Set() + + private let actionsSubject: PassthroughSubject = .init() + var actions: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init(parameters: LogViewerScreenCoordinatorParameters) { + self.parameters = parameters + + viewModel = LogViewerScreenViewModel() + } + + func start() { + viewModel.actions.sink { [weak self] action in + MXLog.info("Coordinator: received view model action: \(action)") + + guard let self else { return } + switch action { + case .done: + self.actionsSubject.send(.done) + } + } + .store(in: &cancellables) + } + + func toPresentable() -> AnyView { + AnyView(LogViewerScreen(context: viewModel.context)) + } +} diff --git a/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenModels.swift b/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenModels.swift new file mode 100644 index 000000000..814a8f761 --- /dev/null +++ b/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenModels.swift @@ -0,0 +1,29 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +enum LogViewerScreenViewModelAction { + case done +} + +struct LogViewerScreenViewState: BindableState { + let urls: [URL] +} + +enum LogViewerScreenViewAction { + case done +} diff --git a/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenViewModel.swift b/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenViewModel.swift new file mode 100644 index 000000000..990cc475d --- /dev/null +++ b/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenViewModel.swift @@ -0,0 +1,43 @@ +// +// 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 LogViewerScreenViewModelType = StateStoreViewModel + +class LogViewerScreenViewModel: LogViewerScreenViewModelType, LogViewerScreenViewModelProtocol { + private var actionsSubject: PassthroughSubject = .init() + + var actions: AnyPublisher { + actionsSubject.eraseToAnyPublisher() + } + + init() { + super.init(initialViewState: LogViewerScreenViewState(urls: MXLogger.logFiles)) + } + + // MARK: - Public + + override func process(viewAction: LogViewerScreenViewAction) { + MXLog.info("View model: received view action: \(viewAction)") + + switch viewAction { + case .done: + actionsSubject.send(.done) + } + } +} diff --git a/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenViewModelProtocol.swift b/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenViewModelProtocol.swift new file mode 100644 index 000000000..b808b2a56 --- /dev/null +++ b/ElementX/Sources/Screens/LogViewerScreen/LogViewerScreenViewModelProtocol.swift @@ -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 LogViewerScreenViewModelProtocol { + var actions: AnyPublisher { get } + var context: LogViewerScreenViewModelType.Context { get } +} diff --git a/ElementX/Sources/Screens/LogViewerScreen/View/LogViewerScreen.swift b/ElementX/Sources/Screens/LogViewerScreen/View/LogViewerScreen.swift new file mode 100644 index 000000000..ee0aac003 --- /dev/null +++ b/ElementX/Sources/Screens/LogViewerScreen/View/LogViewerScreen.swift @@ -0,0 +1,85 @@ +// +// 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 Compound +import QuickLook +import SwiftUI + +struct LogViewerScreen: View { + @ObservedObject var context: LogViewerScreenViewModel.Context + + var body: some View { + PreviewView(urls: context.viewState.urls) + } +} + +private struct PreviewView: UIViewControllerRepresentable { + let urls: [URL] + + func makeUIViewController(context: Context) -> UIViewController { + let previewController = QLPreviewController() + previewController.dataSource = context.coordinator + previewController.delegate = context.coordinator + + if ProcessInfo.processInfo.isiOSAppOnMac { + return previewController + } else { + return UINavigationController(rootViewController: previewController) + } + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } + + func makeCoordinator() -> Coordinator { + Coordinator(view: self) + } + + class Coordinator: NSObject, QLPreviewControllerDataSource, QLPreviewControllerDelegate { + let view: PreviewView + + init(view: PreviewView) { + self.view = view + } + + // MARK: - QLPreviewControllerDataSource + + func numberOfPreviewItems(in controller: QLPreviewController) -> Int { + view.urls.count + } + + func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { + let url = view.urls[index] + + return PreviewItem(previewItemURL: url, previewItemTitle: url.lastPathComponent) + } + + // MARK: - QLPreviewControllerDelegate + + func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode { + .disabled + } + } +} + +private class PreviewItem: NSObject, QLPreviewItem { + var previewItemURL: URL? + var previewItemTitle: String? + + init(previewItemURL: URL?, previewItemTitle: String?) { + self.previewItemURL = previewItemURL + self.previewItemTitle = previewItemTitle + } +} diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index 564012289..edc707c7c 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -206,12 +206,12 @@ class MockScreen: Identifiable { fatalError("Failed to start listening for notifications.") } - let coordinator = AppLockFlowCoordinator(initialState: .unlocked, - appLockService: appLockService, - navigationCoordinator: navigationCoordinator, - notificationCenter: notificationCenter) + let flowCoordinator = AppLockFlowCoordinator(initialState: .unlocked, + appLockService: appLockService, + navigationCoordinator: navigationCoordinator, + notificationCenter: notificationCenter) - coordinator.actions + flowCoordinator.actions .sink { [weak self] action in guard let self else { return } @@ -226,7 +226,9 @@ class MockScreen: Identifiable { } .store(in: &cancellables) - return coordinator + retainedState.append(flowCoordinator) + + return navigationCoordinator case .appLockSetupFlow, .appLockSetupFlowUnlock, .appLockSetupFlowMandatory: let navigationStackCoordinator = NavigationStackCoordinator() // The flow expects an existing root coordinator, use a blank form as a placeholder. @@ -248,11 +250,12 @@ class MockScreen: Identifiable { } let flow: AppLockSetupFlowCoordinator.PresentationFlow = id == .appLockSetupFlowMandatory ? .onboarding : .settings - let coordinator = AppLockSetupFlowCoordinator(presentingFlow: flow, - appLockService: appLockService, - navigationStackCoordinator: navigationStackCoordinator) - coordinator.start() - retainedState.append(coordinator) + let flowCoordinator = AppLockSetupFlowCoordinator(presentingFlow: flow, + appLockService: appLockService, + navigationStackCoordinator: navigationStackCoordinator) + flowCoordinator.start() + + retainedState.append(flowCoordinator) return navigationStackCoordinator case .home: @@ -559,19 +562,19 @@ class MockScreen: Identifiable { ServiceLocator.shared.settings.migratedAccounts[clientProxy.userID] = true ServiceLocator.shared.settings.hasShownWelcomeScreen = true - let coordinator = UserSessionFlowCoordinator(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider(), voiceMessageMediaManager: VoiceMessageMediaManagerMock()), - navigationSplitCoordinator: navigationSplitCoordinator, - windowManager: windowManager, - appLockService: AppLockService(keychainController: KeychainControllerMock(), - appSettings: ServiceLocator.shared.settings), - bugReportService: BugReportServiceMock(), - roomTimelineControllerFactory: MockRoomTimelineControllerFactory(), - appSettings: appSettings, - analytics: ServiceLocator.shared.analytics) + let flowCoordinator = UserSessionFlowCoordinator(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider(), voiceMessageMediaManager: VoiceMessageMediaManagerMock()), + navigationSplitCoordinator: navigationSplitCoordinator, + windowManager: windowManager, + appLockService: AppLockService(keychainController: KeychainControllerMock(), + appSettings: ServiceLocator.shared.settings), + bugReportService: BugReportServiceMock(), + roomTimelineControllerFactory: MockRoomTimelineControllerFactory(), + appSettings: appSettings, + analytics: ServiceLocator.shared.analytics) - coordinator.start() + flowCoordinator.start() - retainedState.append(coordinator) + retainedState.append(flowCoordinator) return navigationSplitCoordinator case .roomDetailsScreen: diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReport-0.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReport-0.png index 13e84def2..d2a6cbd29 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReport-0.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReport-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a030e3bae9b20079e98682c35a8fb18a2016c57e6fc2541e808408b4eee56346 -size 126288 +oid sha256:d29521672f8d636c5c3f44424d72f93f2e827360d14ec6500e881c885e91a7ed +size 132888 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReport-2.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReport-2.png index 504f2e0dd..8ca010e6c 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReport-2.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReport-2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:646ab427a87537496aca2498a646c554779aafe402fbf68ee7836edd8737e39f -size 192564 +oid sha256:36c87320366601556a66eb27330a4a0318394717a4498b2d583258777df4868b +size 197037 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReport-3.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReport-3.png index 6a2c10bf8..a132cc2dc 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReport-3.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReport-3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a17e180923e77c0a8e156f4c5410833f2c5d449d53e4589d4940d67bf8d27fb7 -size 198747 +oid sha256:f3669c7652d24c0397d9b2954fa44114b280d44afebc5bf94f948349572500c7 +size 204790 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReportWithScreenshot.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReportWithScreenshot.png index 0f4ffdf3a..377ca0d5a 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReportWithScreenshot.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReportWithScreenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:529a0ff29bda2dfa01be74109e2bfa3d69ec6fa9941db0c4fc24d4396cd6e482 -size 168109 +oid sha256:8278f60d55de91a6380989a5fc6c7286f005e28ae0652a3b9a1ec1447fcdf562 +size 172368 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReport-0.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReport-0.png index e72a9b49d..1fb6033ec 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReport-0.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReport-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:41ea4ea40a7130c845aaa750bf0fd5622a3c6f7196a4e750196be07159df161c -size 167036 +oid sha256:f3bc350a472366c1b5d0ee816760d4d5796f8a38a70f1cb0126c8929c5cbaf5b +size 173275 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReport-2.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReport-2.png index 5701d1c80..3de3c05c1 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReport-2.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReport-2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56e8df51695c554dbc19bc80e2f004a584451927ce0f9c38847c706946ff79c7 -size 207411 +oid sha256:836341a4506378018e653208c02fd35477aaa1fe86f4c7965f911f5f3004a4d4 +size 190903 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReport-3.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReport-3.png index 8cff2f2a9..93359edcb 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReport-3.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReport-3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9505d4872ab00c16afdbd91245bd8ed3ba43b928080a0fa9ca467ef64356c689 -size 214258 +oid sha256:0340b1504e92350ffcee66aa393057a211b47f9a012fd2f2fbd63a8b849121d8 +size 196998 diff --git a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReportWithScreenshot.png b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReportWithScreenshot.png index 9f01c68c7..2cea8dd7d 100644 --- a/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReportWithScreenshot.png +++ b/UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReportWithScreenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f5afc62779254c8432eefa058690e00ce1709507a3b2d5b19574b574d2ae53f -size 241787 +oid sha256:bc57a02c6148f559e3cf7e18840d4f4e9a06bc96753f2d5f632c162965e0795b +size 249420 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReport-0.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReport-0.png index 8e307724e..57b564bc0 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReport-0.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReport-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:96793f47feb3746e6630dc1fe387574a020a091ff2d8482202300083897cdb62 -size 164437 +oid sha256:509d3539b0ff0c9a46b5756605f61160ce1becb06b8e69eca3d8b5fbf43e86e4 +size 168405 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReport-2.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReport-2.png index 56cc2c152..26826c480 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReport-2.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReport-2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0962939d9e4e25e58de95247810e1e392f1793733104822c7fa10aaa93ea47ce -size 228989 +oid sha256:69c6584e25a26adee6f23b4d68ef75ab29a99d4196322b39bd024d738511e8ac +size 234152 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReport-3.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReport-3.png index 960b8a74f..0826b4563 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReport-3.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReport-3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0dfcaf6c963c8ddf79d3f40906b0e499a0aab4f1efacb96ab21f904688f73b69 -size 234890 +oid sha256:d33c61f7e7cf6a1b17019967364cc65f3cd152c6d7f67c19744b9f8ab96523da +size 239805 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReportWithScreenshot.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReportWithScreenshot.png index 2ab2febaf..8e2c5e0d5 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReportWithScreenshot.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReportWithScreenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3f73d9fcff69d33d95e645fee81328b9981cdb8eca44ae9c178b3df15a24966e -size 202922 +oid sha256:93e656155b8811e71e332fb357e934c54afe8fcdba7bfad440f5fb0d800027a3 +size 207913 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReport-0.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReport-0.png index 1ef1434ee..eb2465d6b 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReport-0.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReport-0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9193d085cf66079ca1db4e67cdc2f74c1884102a6957cac4f0f50c5e78166d49 -size 236203 +oid sha256:fe78b8687073c5909126ada93ae8dea86417baa84de897fb6bf0a8b622737a1b +size 242210 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReport-2.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReport-2.png index 3ae2183d7..2be2453f5 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReport-2.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReport-2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e22ac126938c1924a1027559a6656e454d0223aea326548fefa210f1b31161e4 -size 214293 +oid sha256:e795ccedbac57cc9beeed357268e54ba57266edeb4f7e40e555f6678c009432b +size 219856 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReport-3.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReport-3.png index 157d0e829..adecfeb00 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReport-3.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReport-3.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a7546c4e537da63b57b5afe1ef527a62ab9de67a636c74857152798f6f8a09c -size 220086 +oid sha256:3b6f3d8a34cfe9f3767cadc7cba72d4c4d52447916d45d637863fee88198ec85 +size 227116 diff --git a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReportWithScreenshot.png b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReportWithScreenshot.png index d85df7a0b..c8dc2e645 100644 --- a/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReportWithScreenshot.png +++ b/UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReportWithScreenshot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a5d47938ca196eb361632bf3956797c18f5ad4ddeb277eb915712e778f630a6 -size 294756 +oid sha256:f34624eec2f6c815176dd140f1a84c0faa4d2982fe29f2f44977f25145301d3f +size 288460 diff --git a/UnitTests/__Snapshots__/PreviewTests/test_bugReport.1.png b/UnitTests/__Snapshots__/PreviewTests/test_bugReport.1.png index c0aa78164..03518c03a 100644 --- a/UnitTests/__Snapshots__/PreviewTests/test_bugReport.1.png +++ b/UnitTests/__Snapshots__/PreviewTests/test_bugReport.1.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29d00818b332c8c1c33c8540099fe41ffaa45a70c3df28959819f841f005d6c7 -size 176727 +oid sha256:bb8091f6bc6eb52326bdf97886fe65b5ee05a79dbcdcd9a932fe376b0daa2d18 +size 183696 diff --git a/UnitTests/__Snapshots__/PreviewTests/test_bugReport.2.png b/UnitTests/__Snapshots__/PreviewTests/test_bugReport.2.png index e58a2985d..d888f98a2 100644 --- a/UnitTests/__Snapshots__/PreviewTests/test_bugReport.2.png +++ b/UnitTests/__Snapshots__/PreviewTests/test_bugReport.2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:99253b776ad880042ed56f785b4112ebbe00cc8c6ee12758416a127d19385aeb -size 268418 +oid sha256:3702bb587932d15ade8a69fb01eb24a7a8e4603b58b16eec628abca50c069902 +size 274343 diff --git a/changelog.d/734.feature b/changelog.d/734.feature new file mode 100644 index 000000000..e3e563fbd --- /dev/null +++ b/changelog.d/734.feature @@ -0,0 +1 @@ +Enable log previewing before reporting a problem \ No newline at end of file