From 7dcd494de8102fdb642d7c105c7dbb86c1a3f6a0 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Tue, 21 Jun 2022 20:28:42 +0300 Subject: [PATCH] Room screen header (#86) * #35 Create `ElementNavigationController` subclass * #35 Add encryption icons * #35 Add avatar and encryption badge image to the room screen view model * #35 Create `RoomHeaderView` class * #35 Replace room title with a RoomHeaderView instance in the toolbar * #35 Add changelog * #35 Introduce `UITestScreenIdentifier` and refactor ui tests * #35 Fix old tests * #35 add some tests for room screen * #35 Use svgs instead of pngs * #35 Fix PR remarks --- ElementX.xcodeproj/project.pbxproj | 14 +++ .../Images/Encryption/Contents.json | 6 ++ .../encryption_normal.imageset/Contents.json | 12 +++ .../encryption_normal.svg | 3 + .../encryption_trusted.imageset/Contents.json | 12 +++ .../encryption_trusted.svg | 4 + .../encryption_warning.imageset/Contents.json | 12 +++ .../encryption_warning.svg | 4 + ElementX/Sources/AppCoordinator.swift | 8 +- ElementX/Sources/Generated/Assets.swift | 3 + .../Other/ElementNavigationController.swift | 18 ++++ .../Other/Routers/NavigationRouterStore.swift | 2 +- .../RoomScreen/RoomScreenCoordinator.swift | 6 +- .../Screens/RoomScreen/RoomScreenModels.swift | 3 + .../RoomScreen/RoomScreenViewModel.swift | 9 +- .../RoomScreen/View/RoomHeaderView.swift | 89 +++++++++++++++++++ .../Screens/RoomScreen/View/RoomScreen.swift | 6 +- .../Sources/Services/Room/RoomProxy.swift | 9 ++ ElementX/Sources/UITestScreenIdentifier.swift | 40 +++++++++ ElementX/Sources/UITestsAppCoordinator.swift | 48 +++++++--- ElementX/Sources/UITestsRootView.swift | 5 +- .../UI/TemplateSimpleScreenUITests.swift | 4 +- UITests/Sources/Application.swift | 4 +- UITests/Sources/BugReportUITests.swift | 8 +- UITests/Sources/LoginScreenUITests.swift | 2 +- UITests/Sources/RoomScreenUITests.swift | 27 ++++++ UITests/Sources/SettingsUITests.swift | 2 +- UITests/Sources/SplashScreenUITests.swift | 4 +- UITests/SupportingFiles/target.yml | 1 + .../Sources/HomeScreenViewModelTests.swift | 16 ---- .../Sources/SettingsViewModelTests.swift | 16 +++- changelog.d/35.change | 1 + 32 files changed, 347 insertions(+), 51 deletions(-) create mode 100644 ElementX/Resources/Assets.xcassets/Images/Encryption/Contents.json create mode 100644 ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_normal.imageset/Contents.json create mode 100644 ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_normal.imageset/encryption_normal.svg create mode 100644 ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_trusted.imageset/Contents.json create mode 100644 ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_trusted.imageset/encryption_trusted.svg create mode 100644 ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_warning.imageset/Contents.json create mode 100644 ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_warning.imageset/encryption_warning.svg create mode 100644 ElementX/Sources/Other/ElementNavigationController.swift create mode 100644 ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift create mode 100644 ElementX/Sources/UITestScreenIdentifier.swift create mode 100644 changelog.d/35.change diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 4426238a8b..74f4a2e82c 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C070FD43DC6BF4E50217965A /* LocalizationTests.swift */; }; + 004561D297DC8B9786AE136F /* UITestScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FD9D66B75292F2CC11AA4D2 /* UITestScreenIdentifier.swift */; }; 00AC53151BA23A90FAAE9FBF /* BugReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D607F47FDEF16CC63684BE0 /* BugReport.swift */; }; 00F3059B1E0CFCA019710C3E /* BugReportModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B516212D9FE785DDD5E490D1 /* BugReportModels.swift */; }; 01CB8ACFA5E143E89C168CA8 /* TimelineItemContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B43AF03660F5FD4FFFA7F1CE /* TimelineItemContextMenu.swift */; }; @@ -15,10 +16,12 @@ 02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */; }; 03B8FEA668A5B76A93113BB1 /* MemberDetailProviderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C2ABC1A9B62BDB3D216E7FD /* MemberDetailProviderManager.swift */; }; 03CB204C52F18E24A5C3D219 /* UITestsAppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 967873B9E11828B67F64C89A /* UITestsAppCoordinator.swift */; }; + 04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422724361B6555364C43281E /* RoomHeaderView.swift */; }; 05776B005C57E92582F0CF08 /* BuildSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F87116470221880017CF522 /* BuildSettings.swift */; }; 059173B3C77056C406906B6D /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = D4DA544B2520BFA65D6DB4BB /* target.yml */; }; 0602FA07557F580086782A9E /* UserIndicatorPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FA072E995316CD18BC29313 /* UserIndicatorPresentationContext.swift */; }; 066A1E9B94723EE9F3038044 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; }; + 06E93B2E3B32740B40F47CC5 /* ElementNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CF4B39D52CAE7D21D276ABEE /* ElementNavigationController.swift */; }; 072BA9DBA932374CCA300125 /* MessageComposerTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */; }; 0B1F80C2BF7D223159FBA82C /* ImageAnonymizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */; }; 0E8C480700870BB34A2A360F /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 78A5A8DE1E2B09C978C7F3B0 /* KeychainAccess */; }; @@ -121,6 +124,7 @@ 7002C55A4C917F3715765127 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C888BCD78E2A55DCE364F160 /* MediaProviderProtocol.swift */; }; 7405B4824D45BA7C3D943E76 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D0CBC76C80E04345E11F2DB /* Application.swift */; }; 7434A7F02D587A920B376A9A /* LoginScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A43964330459965AF048A8C /* LoginScreenViewModelTests.swift */; }; + 75D98001C5AC38B6A5CA897C /* UITestScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FD9D66B75292F2CC11AA4D2 /* UITestScreenIdentifier.swift */; }; 7756C4E90CABE6F14F7920A0 /* BugReportUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6FEA87EA3752203065ECE27 /* BugReportUITests.swift */; }; 77D7DAA41AAB36800C1F2E2D /* RoomTimelineProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095AED4CF56DFF3EB7BB84C8 /* RoomTimelineProviderProtocol.swift */; }; 77E192BA943B90F9F310CA23 /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFCC48E7F701B6C24484593 /* WeakDictionaryKeyReference.swift */; }; @@ -336,6 +340,7 @@ 40B21E611DADDEF00307E7AC /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; 4110685D9CA159F3FD2D6BA1 /* TextRoomMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomMessage.swift; sourceTree = ""; }; 4112D04077F6709C5CA0A13E /* FullscreenLoadingViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullscreenLoadingViewPresenter.swift; sourceTree = ""; }; + 422724361B6555364C43281E /* RoomHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomHeaderView.swift; sourceTree = ""; }; 434522ED2BDED08759048077 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = ""; }; 4411C0DA0087A1CB143E96FA /* EventBrief.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBrief.swift; sourceTree = ""; }; 4470B8CB654B097D807AA713 /* ToastViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastViewPresenter.swift; sourceTree = ""; }; @@ -384,6 +389,7 @@ 5F12E996BFBEB43815189ABF /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = ""; }; 5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = ""; }; 5F77E8010D41AA3F5F9A1FCA /* NavigationModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationModule.swift; sourceTree = ""; }; + 5FD9D66B75292F2CC11AA4D2 /* UITestScreenIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestScreenIdentifier.swift; sourceTree = ""; }; 5FF214969B25BFCBF87B908B /* bn-BD */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "bn-BD"; path = "bn-BD.lproj/Localizable.stringsdict"; sourceTree = ""; }; 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxyProtocol.swift; sourceTree = ""; }; 6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAnonymizerTests.swift; sourceTree = ""; }; @@ -539,6 +545,7 @@ CED34C87277BA3CCC6B6EC7A /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = ""; }; CF3EDF23226895776553F04A /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; CF47564C584F614B7287F3EB /* RootRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouter.swift; sourceTree = ""; }; + CF4B39D52CAE7D21D276ABEE /* ElementNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementNavigationController.swift; sourceTree = ""; }; CF847B3C1873B8E81CEE7FAC /* SplashScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenViewModel.swift; sourceTree = ""; }; D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineView.swift; sourceTree = ""; }; D29EBCBFEC6FD0941749404D /* NavigationRouterStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouterStore.swift; sourceTree = ""; }; @@ -1016,6 +1023,7 @@ 4E854E7CF531DAC5CBEBDC75 /* ListTableViewAdapter.swift */, E18CF12478983A5EB390FB26 /* MessageComposer.swift */, BE6C10032A77AE7DC5AA4C50 /* MessageComposerTextField.swift */, + 422724361B6555364C43281E /* RoomHeaderView.swift */, 5221DFDF809142A2D6AC82B9 /* RoomScreen.swift */, B43AF03660F5FD4FFFA7F1CE /* TimelineItemContextMenu.swift */, 804F9B0FABE093C7284CD09B /* TimelineItemList.swift */, @@ -1248,6 +1256,7 @@ 49EAD710A2C16EFF7C3EA16F /* Benchmark.swift */, E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */, 95CC95CD75B688E946438165 /* Coordinator.swift */, + CF4B39D52CAE7D21D276ABEE /* ElementNavigationController.swift */, 12A626D74BBE9F4A60763B45 /* ImageAnonymizer.swift */, F7B81C8227BBEA95CCE86037 /* MatrixEntitityRegex.swift */, 44BBB96FAA2F0D53C507396B /* Extensions */, @@ -1301,6 +1310,7 @@ EFFA5FD06AAAC4AF544B594E /* AppDelegate.swift */, 3F87116470221880017CF522 /* BuildSettings.swift */, 967873B9E11828B67F64C89A /* UITestsAppCoordinator.swift */, + 5FD9D66B75292F2CC11AA4D2 /* UITestScreenIdentifier.swift */, CCA431E6EDD71F7067B5F9E7 /* UITestsRootView.swift */, 0787F81684E503024BD0C051 /* Services */, E59565F441830B19DBAE567C /* Screens */, @@ -1705,6 +1715,7 @@ DCB781BD227CA958809AFADF /* Coordinator.swift in Sources */, C4F69156C31A447FEFF2A47C /* DTHTMLElement+AttributedStringBuilder.swift in Sources */, EE8491AD81F47DF3C192497B /* DecorationTimelineItemProtocol.swift in Sources */, + 06E93B2E3B32740B40F47CC5 /* ElementNavigationController.swift in Sources */, D8CFF02C2730EE5BC4F17ABF /* ElementToggleStyle.swift in Sources */, 7C1A7B594B2F8143F0DD0005 /* ElementXAttributeScope.swift in Sources */, 224A55EEAEECF5336B14A4A5 /* EmoteRoomMessage.swift in Sources */, @@ -1764,6 +1775,7 @@ 7D1DAAA364A9A29D554BD24E /* PlaceholderAvatarImage.swift in Sources */, BF35062D06888FA80BD139FF /* Presentable.swift in Sources */, 53B9C2240C2F5533246EE230 /* RectangleToastView.swift in Sources */, + 04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */, FE79E2BCCF69E8BF4D21E15A /* RoomMessageFactory.swift in Sources */, 8D9F646387DF656EF91EE4CB /* RoomMessageFactoryProtocol.swift in Sources */, D0619D2E6B9C511190FBEB95 /* RoomMessageProtocol.swift in Sources */, @@ -1824,6 +1836,7 @@ 9CB5129C83F75921E5E28028 /* ToastViewState.swift in Sources */, 36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */, 0EE5EBA18BA1FE10254BB489 /* UIFont+AttributedStringBuilder.m in Sources */, + 004561D297DC8B9786AE136F /* UITestScreenIdentifier.swift in Sources */, 03CB204C52F18E24A5C3D219 /* UITestsAppCoordinator.swift in Sources */, 17CC4FB64F3A670F43ECBE5F /* UITestsRootView.swift in Sources */, 8775F46AE3234A5A5688C19D /* UserIndicator.swift in Sources */, @@ -1859,6 +1872,7 @@ 490E606044B18985055FF690 /* SettingsUITests.swift in Sources */, A00DFC1DD3567B1EDC9F8D16 /* SplashScreenUITests.swift in Sources */, 2E68C57E7D644E94778743D5 /* TemplateSimpleScreenUITests.swift in Sources */, + 75D98001C5AC38B6A5CA897C /* UITestScreenIdentifier.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ElementX/Resources/Assets.xcassets/Images/Encryption/Contents.json b/ElementX/Resources/Assets.xcassets/Images/Encryption/Contents.json new file mode 100644 index 0000000000..da4a164c91 --- /dev/null +++ b/ElementX/Resources/Assets.xcassets/Images/Encryption/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_normal.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_normal.imageset/Contents.json new file mode 100644 index 0000000000..c45edf4c9b --- /dev/null +++ b/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_normal.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "encryption_normal.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_normal.imageset/encryption_normal.svg b/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_normal.imageset/encryption_normal.svg new file mode 100644 index 0000000000..2a33a82121 --- /dev/null +++ b/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_normal.imageset/encryption_normal.svg @@ -0,0 +1,3 @@ + + + diff --git a/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_trusted.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_trusted.imageset/Contents.json new file mode 100644 index 0000000000..14f07e127a --- /dev/null +++ b/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_trusted.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "encryption_trusted.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_trusted.imageset/encryption_trusted.svg b/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_trusted.imageset/encryption_trusted.svg new file mode 100644 index 0000000000..3e84700ad0 --- /dev/null +++ b/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_trusted.imageset/encryption_trusted.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_warning.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_warning.imageset/Contents.json new file mode 100644 index 0000000000..321bea10ff --- /dev/null +++ b/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_warning.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "encryption_warning.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_warning.imageset/encryption_warning.svg b/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_warning.imageset/encryption_warning.svg new file mode 100644 index 0000000000..29600e8334 --- /dev/null +++ b/ElementX/Resources/Assets.xcassets/Images/Encryption/encryption_warning.imageset/encryption_warning.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ElementX/Sources/AppCoordinator.swift b/ElementX/Sources/AppCoordinator.swift index 0a50a6c9f6..5e337e5163 100644 --- a/ElementX/Sources/AppCoordinator.swift +++ b/ElementX/Sources/AppCoordinator.swift @@ -44,7 +44,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { } splashViewController = SplashViewController() - mainNavigationController = UINavigationController(rootViewController: splashViewController) + mainNavigationController = ElementNavigationController(rootViewController: splashViewController) window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = mainNavigationController window.tintColor = .element.accent @@ -251,7 +251,9 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { memberDetailProvider: memberDetailProvider) let parameters = RoomScreenCoordinatorParameters(timelineController: timelineController, - roomName: roomProxy.displayName ?? roomProxy.name) + roomName: roomProxy.displayName ?? roomProxy.name, + roomAvatar: userSession.mediaProvider.imageFromURLString(roomProxy.avatarURL), + roomEncryptionBadge: roomProxy.encryptionBadgeImage) let coordinator = RoomScreenCoordinator(parameters: parameters) add(childCoordinator: coordinator) @@ -333,7 +335,7 @@ class AppCoordinator: AuthenticationCoordinatorDelegate, Coordinator { add(childCoordinator: coordinator) coordinator.start() - let navController = UINavigationController(rootViewController: coordinator.toPresentable()) + let navController = ElementNavigationController(rootViewController: coordinator.toPresentable()) navController.navigationBar.topItem?.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(dismissBugReportScreen)) diff --git a/ElementX/Sources/Generated/Assets.swift b/ElementX/Sources/Generated/Assets.swift index 72324c3f71..fc57915994 100644 --- a/ElementX/Sources/Generated/Assets.swift +++ b/ElementX/Sources/Generated/Assets.swift @@ -20,6 +20,9 @@ internal typealias AssetImageTypeAlias = ImageAsset.Image // swiftlint:disable identifier_name line_length nesting type_body_length type_name internal enum Asset { internal enum Images { + internal static let encryptionNormal = ImageAsset(name: "Images/encryption_normal") + internal static let encryptionTrusted = ImageAsset(name: "Images/encryption_trusted") + internal static let encryptionWarning = ImageAsset(name: "Images/encryption_warning") internal static let splashScreenPage1 = ImageAsset(name: "Images/Splash Screen Page 1") internal static let splashScreenPage2 = ImageAsset(name: "Images/Splash Screen Page 2") internal static let splashScreenPage3 = ImageAsset(name: "Images/Splash Screen Page 3") diff --git a/ElementX/Sources/Other/ElementNavigationController.swift b/ElementX/Sources/Other/ElementNavigationController.swift new file mode 100644 index 0000000000..2dbdab5be0 --- /dev/null +++ b/ElementX/Sources/Other/ElementNavigationController.swift @@ -0,0 +1,18 @@ +// +// ElementNavigationController.swift +// ElementX +// +// Created by Ismail on 20.06.2022. +// Copyright © 2022 Element. All rights reserved. +// + +import UIKit + +class ElementNavigationController: UINavigationController { + + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + navigationBar.topItem?.backButtonDisplayMode = .minimal + } + +} diff --git a/ElementX/Sources/Other/Routers/NavigationRouterStore.swift b/ElementX/Sources/Other/Routers/NavigationRouterStore.swift index e847a0ebc5..8b25814c7d 100644 --- a/ElementX/Sources/Other/Routers/NavigationRouterStore.swift +++ b/ElementX/Sources/Other/Routers/NavigationRouterStore.swift @@ -44,7 +44,7 @@ class NavigationRouterStore: NavigationRouterStoreProtocol { return existingNavigationRouter } - let navigationRouter = NavigationRouter(navigationController: UINavigationController()) + let navigationRouter = NavigationRouter(navigationController: ElementNavigationController()) return navigationRouter } diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift index e7b1317d8e..af7c7aea69 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenCoordinator.swift @@ -19,6 +19,8 @@ import SwiftUI struct RoomScreenCoordinatorParameters { let timelineController: RoomTimelineControllerProtocol let roomName: String? + let roomAvatar: UIImage? + let roomEncryptionBadge: UIImage? } final class RoomScreenCoordinator: Coordinator, Presentable { @@ -43,7 +45,9 @@ final class RoomScreenCoordinator: Coordinator, Presentable { let viewModel = RoomScreenViewModel(timelineController: parameters.timelineController, timelineViewFactory: RoomTimelineViewFactory(), - roomName: parameters.roomName) + roomName: parameters.roomName, + roomAvatar: parameters.roomAvatar, + roomEncryptionBadge: parameters.roomEncryptionBadge) let view = RoomScreen(context: viewModel.context) roomScreenViewModel = viewModel diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift index a252fe8e45..0d32c68d48 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -15,6 +15,7 @@ // import Foundation +import UIKit enum RoomScreenViewModelAction { @@ -35,6 +36,8 @@ enum RoomScreenViewAction { struct RoomScreenViewState: BindableState { var roomTitle: String = "" + var roomAvatar: UIImage? + var roomEncryptionBadge: UIImage? var items: [RoomTimelineViewProvider] = [] var isBackPaginating = false var bindings: RoomScreenViewStateBindings diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 93bdb1edd6..5a52045501 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -31,11 +31,16 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol init(timelineController: RoomTimelineControllerProtocol, timelineViewFactory: RoomTimelineViewFactoryProtocol, - roomName: String?) { + roomName: String?, + roomAvatar: UIImage? = nil, + roomEncryptionBadge: UIImage? = nil) { self.timelineController = timelineController self.timelineViewFactory = timelineViewFactory - super.init(initialViewState: RoomScreenViewState(roomTitle: roomName ?? "Unknown room 💥", bindings: RoomScreenViewStateBindings(composerText: ""))) + super.init(initialViewState: RoomScreenViewState(roomTitle: roomName ?? "Unknown room 💥", + roomAvatar: roomAvatar, + roomEncryptionBadge: roomEncryptionBadge, + bindings: .init(composerText: ""))) timelineController.callbacks .receive(on: DispatchQueue.main) diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift new file mode 100644 index 0000000000..ff4642d14a --- /dev/null +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift @@ -0,0 +1,89 @@ +// +// RoomHeaderView.swift +// ElementX +// +// Created by Ismail on 21.06.2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation +import SwiftUI +import Combine + +import Introspect + +struct RoomHeaderView: View { + + @ObservedObject var context: RoomScreenViewModel.Context + + var body: some View { + HStack(spacing: 8) { + roomAvatar + Text(context.viewState.roomTitle) + .font(.element.headline) + .accessibilityIdentifier("roomNameLabel") + } + } + + @ViewBuilder private var roomAvatar: some View { + ZStack(alignment: .bottomTrailing) { + roomAvatarImage + .clipShape(Circle()) + + if let encryptionBadge = context.viewState.roomEncryptionBadge { + Image(uiImage: encryptionBadge) + .accessibilityIdentifier("encryptionBadgeIcon") + } + } + .frame(width: 32.0, height: 32.0) + } + + @ViewBuilder private var roomAvatarImage: some View { + if let avatar = context.viewState.roomAvatar { + Image(uiImage: avatar) + .resizable() + .scaledToFill() + .accessibilityIdentifier("roomAvatarImage") + } else { + PlaceholderAvatarImage(firstCharacter: String(context.viewState.roomTitle.first ?? Character(""))) + .accessibilityIdentifier("roomAvatarPlaceholderImage") + } + } + +} + +struct RoomHeaderView_Previews: PreviewProvider { + static var previews: some View { + bodyPlain.preferredColorScheme(.light) + bodyPlain.preferredColorScheme(.dark) + bodyEncrypted.preferredColorScheme(.light) + bodyEncrypted.preferredColorScheme(.dark) + } + + @ViewBuilder + static var bodyPlain: some View { + let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(), + timelineViewFactory: RoomTimelineViewFactory(), + roomName: "Some Room name", + roomAvatar: Asset.Images.appLogo.image + ) + + RoomHeaderView(context: viewModel.context) + .previewLayout(.sizeThatFits) + .padding() + } + + @ViewBuilder + static var bodyEncrypted: some View { + let viewModel = RoomScreenViewModel(timelineController: MockRoomTimelineController(), + timelineViewFactory: RoomTimelineViewFactory(), + roomName: "Some Room name", + roomAvatar: Asset.Images.appLogo.image, + roomEncryptionBadge: Asset.Images.encryptionTrusted.image + ) + + RoomHeaderView(context: viewModel.context) + .previewLayout(.sizeThatFits) + .padding() + } +} diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift index 1f9b6a016d..e96b0ceaad 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomScreen.swift @@ -28,8 +28,12 @@ struct RoomScreen: View { } .padding() } - .navigationTitle(context.viewState.roomTitle) .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + RoomHeaderView(context: context) + } + } } private func sendMessage() { diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index d88200b0bd..cc483b21a2 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -83,6 +83,15 @@ class RoomProxy: RoomProxyProtocol { var avatarURL: String? { room.avatarUrl() } + + var encryptionBadgeImage: UIImage? { + guard isEncrypted else { + return nil + } + + // return trusted image for now, should be updated after verification status known + return Asset.Images.encryptionTrusted.image + } func loadAvatarURLForUserId(_ userId: String) async -> Result { await Task.detached { () -> Result in diff --git a/ElementX/Sources/UITestScreenIdentifier.swift b/ElementX/Sources/UITestScreenIdentifier.swift new file mode 100644 index 0000000000..a7357855aa --- /dev/null +++ b/ElementX/Sources/UITestScreenIdentifier.swift @@ -0,0 +1,40 @@ +// +// ScreenIdentifier.swift +// ElementX +// +// Created by Ismail on 21.06.2022. +// Copyright © 2022 Element. All rights reserved. +// + +import Foundation + +enum UITestScreenIdentifier: String { + case login + case simpleRegular + case simpleUpgrade + case settings + case bugReport + case bugReportWithScreenshot + case splash + case roomPlainNoAvatar + case roomEncryptedWithAvatar +} + +extension UITestScreenIdentifier: CustomStringConvertible { + var description: String { + return rawValue.titlecased() + } +} + +extension UITestScreenIdentifier: CaseIterable { } + +private extension String { + func titlecased() -> String { + replacingOccurrences(of: "([A-Z])", + with: " $1", + options: .regularExpression, + range: range(of: self)) + .trimmingCharacters(in: .whitespacesAndNewlines) + .capitalized + } +} diff --git a/ElementX/Sources/UITestsAppCoordinator.swift b/ElementX/Sources/UITestsAppCoordinator.swift index 7062cd31ec..69065bb7f7 100644 --- a/ElementX/Sources/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITestsAppCoordinator.swift @@ -40,19 +40,45 @@ class UITestsAppCoordinator: Coordinator { } private func mockScreens() -> [MockScreen] { - [ - MockScreen(id: "Login screen", coordinator: LoginScreenCoordinator(parameters: .init())), - MockScreen(id: "Simple Screen - Regular", coordinator: TemplateSimpleScreenCoordinator(parameters: .init(promptType: .regular))), - MockScreen(id: "Simple Screen - Upgrade", coordinator: TemplateSimpleScreenCoordinator(parameters: .init(promptType: .upgrade))), - MockScreen(id: "Settings screen", coordinator: SettingsCoordinator(parameters: .init(navigationRouter: NavigationRouter(navigationController: UINavigationController()), bugReportService: MockBugReportService()))), - MockScreen(id: "Bug report screen", coordinator: BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(), screenshot: nil))), - MockScreen(id: "Bug report screen with screenshot", coordinator: BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(), screenshot: Asset.Images.appLogo.image))), - MockScreen(id: "Splash Screen", coordinator: SplashScreenCoordinator()) - ] + UITestScreenIdentifier.allCases.map { MockScreen(id: $0) } } } +@MainActor struct MockScreen: Identifiable { - let id: String - let coordinator: Coordinator & Presentable + let id: UITestScreenIdentifier + var coordinator: Coordinator & Presentable { + switch id { + case .login: + return LoginScreenCoordinator(parameters: .init()) + case .simpleRegular: + return TemplateSimpleScreenCoordinator(parameters: .init(promptType: .regular)) + case .simpleUpgrade: + return TemplateSimpleScreenCoordinator(parameters: .init(promptType: .upgrade)) + case .settings: + let router = NavigationRouter(navigationController: ElementNavigationController()) + return SettingsCoordinator(parameters: .init(navigationRouter: router, + bugReportService: MockBugReportService())) + case .bugReport: + return BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(), + screenshot: nil)) + case .bugReportWithScreenshot: + return BugReportCoordinator(parameters: .init(bugReportService: MockBugReportService(), + screenshot: Asset.Images.appLogo.image)) + case .splash: + return SplashScreenCoordinator() + case .roomPlainNoAvatar: + let params = RoomScreenCoordinatorParameters(timelineController: MockRoomTimelineController(), + roomName: "Some room name", + roomAvatar: nil, + roomEncryptionBadge: nil) + return RoomScreenCoordinator(parameters: params) + case .roomEncryptedWithAvatar: + let params = RoomScreenCoordinatorParameters(timelineController: MockRoomTimelineController(), + roomName: "Some room name", + roomAvatar: Asset.Images.appLogo.image, + roomEncryptionBadge: Asset.Images.encryptionTrusted.image) + return RoomScreenCoordinator(parameters: params) + } + } } diff --git a/ElementX/Sources/UITestsRootView.swift b/ElementX/Sources/UITestsRootView.swift index 41c2e9bd6c..c1370c7dfe 100644 --- a/ElementX/Sources/UITestsRootView.swift +++ b/ElementX/Sources/UITestsRootView.swift @@ -11,14 +11,15 @@ import SwiftUI struct UITestsRootView: View { let mockScreens: [MockScreen] - var selectionCallback: ((String) -> Void)? + var selectionCallback: ((UITestScreenIdentifier) -> Void)? var body: some View { NavigationView { List(mockScreens) { coordinator in - Button(coordinator.id) { + Button(coordinator.id.description) { selectionCallback?(coordinator.id) } + .accessibilityIdentifier(coordinator.id.rawValue) } .listStyle(.plain) } diff --git a/Tools/Scripts/Templates/SimpleScreenExample/Tests/UI/TemplateSimpleScreenUITests.swift b/Tools/Scripts/Templates/SimpleScreenExample/Tests/UI/TemplateSimpleScreenUITests.swift index eb71b4cd52..1e01078c50 100644 --- a/Tools/Scripts/Templates/SimpleScreenExample/Tests/UI/TemplateSimpleScreenUITests.swift +++ b/Tools/Scripts/Templates/SimpleScreenExample/Tests/UI/TemplateSimpleScreenUITests.swift @@ -20,7 +20,7 @@ import ElementX class TemplateSimpleScreenUITests: XCTestCase { func testRegularScreen() { let app = Application.launch() - app.goToScreenWithIdentifier("Simple Screen - Regular") + app.goToScreenWithIdentifier(.simpleRegular) let title = app.staticTexts["title"] XCTAssert(title.exists) @@ -30,7 +30,7 @@ class TemplateSimpleScreenUITests: XCTestCase { func testUpgradeScreen() { let app = Application.launch() - app.goToScreenWithIdentifier("Simple Screen - Upgrade") + app.goToScreenWithIdentifier(.simpleUpgrade) let title = app.staticTexts["title"] XCTAssert(title.exists) diff --git a/UITests/Sources/Application.swift b/UITests/Sources/Application.swift index 75b25edc95..056467184b 100644 --- a/UITests/Sources/Application.swift +++ b/UITests/Sources/Application.swift @@ -18,8 +18,8 @@ struct Application { } extension XCUIApplication { - func goToScreenWithIdentifier(_ identifier: String) { - let button = self.buttons[identifier] + func goToScreenWithIdentifier(_ identifier: UITestScreenIdentifier) { + let button = self.buttons[identifier.rawValue] let lastLabel = staticTexts["lastItem"] while !button.isHittable && !lastLabel.isHittable { diff --git a/UITests/Sources/BugReportUITests.swift b/UITests/Sources/BugReportUITests.swift index c8868e29e8..e1fc50ba7f 100644 --- a/UITests/Sources/BugReportUITests.swift +++ b/UITests/Sources/BugReportUITests.swift @@ -21,7 +21,7 @@ class BugReportUITests: XCTestCase { func testInitialStateComponents() { let app = Application.launch() - app.goToScreenWithIdentifier("Bug report screen") + app.goToScreenWithIdentifier(.bugReport) XCTAssert(app.navigationBars["Bug report"].exists) XCTAssert(app.staticTexts["reportBugDescription"].exists) @@ -40,7 +40,7 @@ class BugReportUITests: XCTestCase { func testToggleSendingLogs() { let app = Application.launch() - app.goToScreenWithIdentifier("Bug report screen") + app.goToScreenWithIdentifier(.bugReport) app.switches["sendLogsToggle"].tap() @@ -51,7 +51,7 @@ class BugReportUITests: XCTestCase { func testReportText() { let app = Application.launch() - app.goToScreenWithIdentifier("Bug report screen") + app.goToScreenWithIdentifier(.bugReport) // type 4 chars app.textViews["reportTextView"].tap() @@ -66,7 +66,7 @@ class BugReportUITests: XCTestCase { func testInitialStateComponentsWithScreenshot() { let app = Application.launch() - app.goToScreenWithIdentifier("Bug report screen with screenshot") + app.goToScreenWithIdentifier(.bugReportWithScreenshot) XCTAssert(app.navigationBars["Bug report"].exists) XCTAssert(app.staticTexts["reportBugDescription"].exists) diff --git a/UITests/Sources/LoginScreenUITests.swift b/UITests/Sources/LoginScreenUITests.swift index 50bf4c9d4e..e30e59a1f3 100644 --- a/UITests/Sources/LoginScreenUITests.swift +++ b/UITests/Sources/LoginScreenUITests.swift @@ -19,7 +19,7 @@ import XCTest class LoginScreenUITests: XCTestCase { func testInitialStateComponents() { let app = Application.launch() - app.goToScreenWithIdentifier("Login screen") + app.goToScreenWithIdentifier(.login) XCTAssert(app.buttons["Login"].exists) XCTAssert(app.textFields["Username"].exists) diff --git a/UITests/Sources/RoomScreenUITests.swift b/UITests/Sources/RoomScreenUITests.swift index e6085fff9f..de53380ef1 100644 --- a/UITests/Sources/RoomScreenUITests.swift +++ b/UITests/Sources/RoomScreenUITests.swift @@ -15,3 +15,30 @@ // import XCTest +import ElementX + +@MainActor +class RoomScreenUITests: XCTestCase { + + func testPlainNoAvatar() async throws { + let app = Application.launch() + app.goToScreenWithIdentifier(.roomPlainNoAvatar) + + try await Task.sleep(nanoseconds: 400_000_000) + + XCTAssert(app.staticTexts["roomNameLabel"].exists) + XCTAssert(app.staticTexts["roomAvatarPlaceholderImage"].exists) + XCTAssertFalse(app.images["encryptionBadgeIcon"].exists) + } + + func testEncryptedWithAvatar() async throws { + let app = Application.launch() + app.goToScreenWithIdentifier(.roomEncryptedWithAvatar) + + try await Task.sleep(nanoseconds: 400_000_000) + + XCTAssert(app.staticTexts["roomNameLabel"].exists) + XCTAssert(app.images["roomAvatarImage"].exists) + XCTAssert(app.images["encryptionBadgeIcon"].exists) + } +} diff --git a/UITests/Sources/SettingsUITests.swift b/UITests/Sources/SettingsUITests.swift index a72149d4c9..5bc1b37ea5 100644 --- a/UITests/Sources/SettingsUITests.swift +++ b/UITests/Sources/SettingsUITests.swift @@ -21,7 +21,7 @@ class SettingsUITests: XCTestCase { func testInitialStateComponents() { let app = Application.launch() - app.goToScreenWithIdentifier("Settings screen") + app.goToScreenWithIdentifier(.settings) XCTAssert(app.navigationBars["Settings"].exists) XCTAssert(app.buttons["reportBugButton"].exists) diff --git a/UITests/Sources/SplashScreenUITests.swift b/UITests/Sources/SplashScreenUITests.swift index 044c5a9c15..0f021744a2 100644 --- a/UITests/Sources/SplashScreenUITests.swift +++ b/UITests/Sources/SplashScreenUITests.swift @@ -20,7 +20,7 @@ import XCTest class SplashScreenUITests: XCTestCase { func testInitialStateComponents() { let app = Application.launch() - app.goToScreenWithIdentifier("Splash Screen") + app.goToScreenWithIdentifier(.splash) let getStartedButton = app.buttons["Get started"] XCTAssertTrue(getStartedButton.exists, "The primary action button should be shown.") @@ -28,7 +28,7 @@ class SplashScreenUITests: XCTestCase { func testSwipingBetweenPages() async throws { let app = Application.launch() - app.goToScreenWithIdentifier("Splash Screen") + app.goToScreenWithIdentifier(.splash) // Given the splash screen in its initial state. let page1TitleText = app.staticTexts["Own your conversations."] diff --git a/UITests/SupportingFiles/target.yml b/UITests/SupportingFiles/target.yml index 8df14491ee..88d0a01dae 100644 --- a/UITests/SupportingFiles/target.yml +++ b/UITests/SupportingFiles/target.yml @@ -41,3 +41,4 @@ targets: - path: ../SupportingFiles - path: ../../Tools/Scripts/Templates/SimpleScreenExample/Tests/UI - path: ../../ElementX/Sources/BuildSettings.swift + - path: ../../ElementX/Sources/UITestScreenIdentifier.swift diff --git a/UnitTests/Sources/HomeScreenViewModelTests.swift b/UnitTests/Sources/HomeScreenViewModelTests.swift index 7de397fb16..93b5292b0a 100644 --- a/UnitTests/Sources/HomeScreenViewModelTests.swift +++ b/UnitTests/Sources/HomeScreenViewModelTests.swift @@ -28,22 +28,6 @@ class HomeScreenViewModelTests: XCTestCase { context = viewModel.context } - @MainActor func testLogout() async throws { - var correctResult = false - viewModel.callback = { result in - switch result { - case .logout: - correctResult = true - default: - break - } - } - - context.send(viewAction: .logout) - await Task.yield() - XCTAssert(correctResult) - } - @MainActor func testSelectRoom() async throws { let mockRoomId = "mock_room_id" var correctResult = false diff --git a/UnitTests/Sources/SettingsViewModelTests.swift b/UnitTests/Sources/SettingsViewModelTests.swift index 981d857100..d867988c8c 100644 --- a/UnitTests/Sources/SettingsViewModelTests.swift +++ b/UnitTests/Sources/SettingsViewModelTests.swift @@ -29,8 +29,20 @@ class SettingsViewModelTests: XCTestCase { context = viewModel.context } - func testInitialState() { - XCTAssert(context.viewState.crashButtonVisible) + @MainActor func testLogout() async throws { + var correctResult = false + viewModel.callback = { result in + switch result { + case .logout: + correctResult = true + default: + break + } + } + + context.send(viewAction: .logout) + await Task.yield() + XCTAssert(correctResult) } func testReportBug() async throws { diff --git a/changelog.d/35.change b/changelog.d/35.change new file mode 100644 index 0000000000..032e9dcdb8 --- /dev/null +++ b/changelog.d/35.change @@ -0,0 +1 @@ +Room: Add header view containing room avatar and encryption badge.