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
This commit is contained in:
ismailgulek
2022-06-21 20:28:42 +03:00
committed by GitHub
parent 163b0b2aa7
commit e9593630dc
32 changed files with 347 additions and 51 deletions

View File

@@ -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 = "<group>"; };
4110685D9CA159F3FD2D6BA1 /* TextRoomMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomMessage.swift; sourceTree = "<group>"; };
4112D04077F6709C5CA0A13E /* FullscreenLoadingViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullscreenLoadingViewPresenter.swift; sourceTree = "<group>"; };
422724361B6555364C43281E /* RoomHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomHeaderView.swift; sourceTree = "<group>"; };
434522ED2BDED08759048077 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Localizable.strings; sourceTree = "<group>"; };
4411C0DA0087A1CB143E96FA /* EventBrief.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventBrief.swift; sourceTree = "<group>"; };
4470B8CB654B097D807AA713 /* ToastViewPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastViewPresenter.swift; sourceTree = "<group>"; };
@@ -384,6 +389,7 @@
5F12E996BFBEB43815189ABF /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = "<group>"; };
5F77E8010D41AA3F5F9A1FCA /* NavigationModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationModule.swift; sourceTree = "<group>"; };
5FD9D66B75292F2CC11AA4D2 /* UITestScreenIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestScreenIdentifier.swift; sourceTree = "<group>"; };
5FF214969B25BFCBF87B908B /* bn-BD */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "bn-BD"; path = "bn-BD.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
6033779EB37259F27F938937 /* ClientProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxyProtocol.swift; sourceTree = "<group>"; };
6045E825AE900A92D61FEFF0 /* ImageAnonymizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAnonymizerTests.swift; sourceTree = "<group>"; };
@@ -539,6 +545,7 @@
CED34C87277BA3CCC6B6EC7A /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = "<group>"; };
CF3EDF23226895776553F04A /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = "<group>"; };
CF47564C584F614B7287F3EB /* RootRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootRouter.swift; sourceTree = "<group>"; };
CF4B39D52CAE7D21D276ABEE /* ElementNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementNavigationController.swift; sourceTree = "<group>"; };
CF847B3C1873B8E81CEE7FAC /* SplashScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashScreenViewModel.swift; sourceTree = "<group>"; };
D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineView.swift; sourceTree = "<group>"; };
D29EBCBFEC6FD0941749404D /* NavigationRouterStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationRouterStore.swift; sourceTree = "<group>"; };
@@ -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;
};

View File

@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "encryption_normal.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.09949 11.4627C6.06108 11.4727 6.02775 11.481 6 11.4876C5.97225 11.481 5.93892 11.4727 5.90051 11.4627C5.76801 11.4282 5.57572 11.3731 5.34366 11.2926C4.87791 11.1309 4.2608 10.8694 3.64749 10.4708C2.42721 9.67759 1.25 8.3665 1.25 6.2025V1.91207L6 0.521L10.75 1.91207V6.2025C10.75 8.3665 9.57279 9.67759 8.35251 10.4708C7.7392 10.8694 7.12209 11.1309 6.65634 11.2926C6.42428 11.3731 6.23199 11.4282 6.09949 11.4627Z" fill="#17191C" stroke="white"/>
</svg>

After

Width:  |  Height:  |  Size: 563 B

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "encryption_trusted.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,4 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.09949 11.4627C6.06108 11.4727 6.02775 11.481 6 11.4876C5.97225 11.481 5.93892 11.4727 5.90051 11.4627C5.76801 11.4282 5.57572 11.3731 5.34366 11.2926C4.87791 11.1309 4.2608 10.8694 3.64749 10.4708C2.42721 9.67759 1.25 8.3665 1.25 6.2025V1.91207L6 0.521L10.75 1.91207V6.2025C10.75 8.3665 9.57279 9.67759 8.35251 10.4708C7.7392 10.8694 7.12209 11.1309 6.65634 11.2926C6.42428 11.3731 6.23199 11.4282 6.09949 11.4627Z" fill="#0DBD8B" stroke="white"/>
<path d="M8.70785 3.4047C8.57285 3.2622 8.34785 3.2547 8.20535 3.3897L5.00285 6.3897L3.82535 5.5647C3.66785 5.4597 3.45035 5.4597 3.30035 5.5947C3.12035 5.7447 3.10535 6.0147 3.25535 6.1947L4.61285 7.7547C4.63535 7.7772 4.65785 7.8072 4.68785 7.8222C4.94285 8.0322 5.32535 7.9947 5.53535 7.7397L5.55785 7.7097L8.72285 3.8772C8.82785 3.7422 8.82785 3.5397 8.70785 3.4047Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 950 B

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "encryption_warning.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,4 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.09949 11.4627C6.06108 11.4727 6.02775 11.481 6 11.4876C5.97225 11.481 5.93892 11.4727 5.90051 11.4627C5.76801 11.4282 5.57572 11.3731 5.34366 11.2926C4.87791 11.1309 4.2608 10.8694 3.64749 10.4708C2.42721 9.67759 1.25 8.3665 1.25 6.2025V1.91207L6 0.521L10.75 1.91207V6.2025C10.75 8.3665 9.57279 9.67759 8.35251 10.4708C7.7392 10.8694 7.12209 11.1309 6.65634 11.2926C6.42428 11.3731 6.23199 11.4282 6.09949 11.4627Z" fill="#FF4B55" stroke="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.42634 4.03626C5.39822 3.71563 5.63447 3.43438 5.95509 3.41188C6.27009 3.38938 6.55134 3.62563 6.58509 3.94626V4.03626L6.40509 6.28626C6.38822 6.49438 6.21384 6.65188 6.00572 6.65188H5.97197C5.77509 6.63501 5.62322 6.48313 5.60634 6.28626L5.42634 4.03626ZM6.49439 7.75379C6.49439 8.02717 6.27278 8.24879 5.99939 8.24879C5.72601 8.24879 5.50439 8.02717 5.50439 7.75379C5.50439 7.48041 5.72601 7.25879 5.99939 7.25879C6.27278 7.25879 6.49439 7.48041 6.49439 7.75379Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -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))

View File

@@ -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")

View File

@@ -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
}
}

View File

@@ -44,7 +44,7 @@ class NavigationRouterStore: NavigationRouterStoreProtocol {
return existingNavigationRouter
}
let navigationRouter = NavigationRouter(navigationController: UINavigationController())
let navigationRouter = NavigationRouter(navigationController: ElementNavigationController())
return navigationRouter
}

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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()
}
}

View File

@@ -28,8 +28,12 @@ struct RoomScreen: View {
}
.padding()
}
.navigationTitle(context.viewState.roomTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
RoomHeaderView(context: context)
}
}
}
private func sendMessage() {

View File

@@ -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<String?, RoomProxyError> {
await Task.detached { () -> Result<String?, RoomProxyError> in

View File

@@ -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
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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."]

View File

@@ -41,3 +41,4 @@ targets:
- path: ../SupportingFiles
- path: ../../Tools/Scripts/Templates/SimpleScreenExample/Tests/UI
- path: ../../ElementX/Sources/BuildSettings.swift
- path: ../../ElementX/Sources/UITestScreenIdentifier.swift

View File

@@ -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

View File

@@ -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 {

1
changelog.d/35.change Normal file
View File

@@ -0,0 +1 @@
Room: Add header view containing room avatar and encryption badge.