Create & Join rooms UI/UX tweaks (#1005)

* Fix people icon size

* Disable dismiss when loading in StartChatScreen

* Reduce debounce on search

* Add padding to invite button

* Add show/hide bottom bar behaviour

* Fix bg color in StartChatScreen form

* Update localisations

* Update search users results to 10

* Fix form background in InviteUsersScreen

* Fix invites list bg color

* Add mxid in InvitesScreenCell

* Refactor InvitesScreenCell design

* Fix separator spacing

* Add more design fixes

* Refine InvitesScreenCell layout

* Refactor UserProfileCell design

* Refactor FormRowAccessory

* Update localisations

* Fix camera size and security section padding

* Change create button style

* Refine InviteUsersScreenSelectedItem

* Refine CreateRoomScreen

* Refine UserProfileCell padding

* Amend preview name

* delete test code

* Add separator in security section

* Fix spacing in InviteUsersScreenSelectedItem

* Revert confirmationAction in CreateRoomScreen

* Refine HomeScreenInvitesButton

* Add dismiss keyboard in InviteUsersScreen

* Refactor FormRowAccessory

* Remove isDisabled from FormButtonStyles

* Update localisations

* Cleanup RoomDetailsEditScreen

* UI fixes on horizontal user carousel

* Refactor InviteUsersScreenSelectedItem

* Try fix flaky tests

* Fix typo

* Update snapshot tests

* Fix flaky UTs

* Fix HomeScreen ui tests

* Fix scroll on selection

* Refactor RoomFlowCoordinatorTests
This commit is contained in:
Alfonso Grillo
2023-06-06 10:03:38 +02:00
committed by GitHub
parent fedd401a56
commit 4e926c3334
87 changed files with 484 additions and 315 deletions
+4
View File
@@ -125,6 +125,7 @@
30CC1DB7CE357659C82AA115 /* MediaProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EB16E7FE59A947CA441531 /* MediaProviderProtocol.swift */; };
30CC4F796B27BE8B1DFDBF5A /* NSEUserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAA2832D93EC7D2608703FB /* NSEUserSession.swift */; };
3113065AABBC14CEAE6843FA /* UserSessionFlowCoordinatorStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8774CF614849664B5B3C2A1 /* UserSessionFlowCoordinatorStateMachine.swift */; };
339BC18777912E1989F2F17D /* Section.swift in Sources */ = {isa = PBXBuildFile; fileRef = 584A61D9C459FAFEF038A7C0 /* Section.swift */; };
33CAC1226DFB8B5D8447D286 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; };
340D39DB87F3800D53A6A621 /* EmojiPickerScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00245D40CD90FD71D6A05239 /* EmojiPickerScreen.swift */; };
3471204F2CC05D4821C35F23 /* landscape_test_image.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 7A5D2323D7B6BF4913EB7EED /* landscape_test_image.jpg */; };
@@ -906,6 +907,7 @@
57B6B383F1FD04CC0E7B60C6 /* AnalyticsConsentState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsConsentState.swift; sourceTree = "<group>"; };
57EAAF82432B0B53881CF826 /* AudioRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRoomTimelineItem.swift; sourceTree = "<group>"; };
57F95CADD0A5DBD76B990FCB /* ServiceLocator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceLocator.swift; sourceTree = "<group>"; };
584A61D9C459FAFEF038A7C0 /* Section.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Section.swift; sourceTree = "<group>"; };
58C2527813FDAE23E72A9063 /* AnalyticsSettingsScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelTests.swift; sourceTree = "<group>"; };
5935565E2CB2F34FE055DD72 /* SettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsScreenCoordinator.swift; sourceTree = "<group>"; };
5AEA0B743847CFA5B3C38EE4 /* RoomMembersListScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenCoordinator.swift; sourceTree = "<group>"; };
@@ -1843,6 +1845,7 @@
F72EFC8C634469F9262659C7 /* NSItemProvider.swift */,
95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */,
7310D8DFE01AF45F0689C3AA /* Publisher.swift */,
584A61D9C459FAFEF038A7C0 /* Section.swift */,
40B21E611DADDEF00307E7AC /* String.swift */,
A9FDA5344F7C4C6E4E863E13 /* Swipe.swift */,
A40C19719687984FD9478FBE /* Task.swift */,
@@ -4015,6 +4018,7 @@
D43F0503EF2CBC55272538FE /* SDKGeneratedMocks.swift in Sources */,
0437765FF480249486893CC7 /* ScreenTrackerViewModifier.swift in Sources */,
0BFA67AFD757EE2BA569836A /* ScrollViewAdapter.swift in Sources */,
339BC18777912E1989F2F17D /* Section.swift in Sources */,
14E99D27628B1A6F0CB46FEA /* SeparatorRoomTimelineItem.swift in Sources */,
49F2E7DD8CAACE09CEECE3E6 /* SeparatorRoomTimelineView.swift in Sources */,
2E8C6672D0EE7D5B1BEDB8E2 /* ServerConfirmationScreen.swift in Sources */,
@@ -88,6 +88,7 @@
"common_report_a_bug" = "Report a bug";
"common_report_submitted" = "Report submitted";
"common_room_name" = "Room name";
"common_room_name_placeholder" = "e.g. your project name";
"common_search_for_someone" = "Search for someone";
"common_search_results" = "Search results";
"common_security" = "Security";
@@ -101,6 +102,7 @@
"common_success" = "Success";
"common_suggestions" = "Suggestions";
"common_topic" = "Topic";
"common_topic_placeholder" = "What is this room about?";
"common_unable_to_decrypt" = "Unable to decrypt";
"common_unable_to_invite_message" = "We were unable to successfully send invites to one or more users.";
"common_unable_to_invite_title" = "Unable to send invite(s)";
@@ -193,22 +195,21 @@
"screen_change_server_form_notice" = "You can only connect to an existing server that supports sliding sync. Your homeserver admin will need to configure it. %1$@";
"screen_change_server_subtitle" = "What is the address of your server?";
"screen_create_room_action_create_room" = "New room";
"screen_create_room_action_invite_people" = "Invite people";
"screen_create_room_add_people_title" = "Add people";
"screen_create_room_action_invite_people" = "Invite friends to Element";
"screen_create_room_add_people_title" = "Invite people";
"screen_create_room_error_creating_room" = "An error occurred when creating the room";
"screen_create_room_private_option_description" = "Messages in this room are encrypted. Encryption can’t be disabled afterwards.";
"screen_create_room_private_option_title" = "Private room (invite only)";
"screen_create_room_public_option_description" = "Messages are not encrypted and anyone can read them. You can enable encryption at a later date.";
"screen_create_room_public_option_title" = "Public room (anyone)";
"screen_create_room_room_name_label" = "Room name";
"screen_create_room_room_name_placeholder" = "e.g. Product Sprint";
"screen_create_room_topic_label" = "Topic (optional)";
"screen_create_room_topic_placeholder" = "What is this room about?";
"screen_invites_decline_chat_message" = "Are you sure you want to decline joining %1$@?";
"screen_invites_decline_chat_title" = "Decline invite";
"screen_invites_decline_direct_chat_message" = "Are you sure you want to decline to chat with %1$@?";
"screen_invites_decline_direct_chat_title" = "Decline chat";
"screen_invites_empty_list" = "No Invites";
"screen_invites_invited_you" = "%1$@ (%2$@) invited you";
"screen_login_error_deactivated_account" = "This account has been deactivated.";
"screen_login_error_invalid_credentials" = "Incorrect username and/or password";
"screen_login_error_invalid_user_id" = "This is not a valid user identifier. Expected format: ‘@user:homeserver.org’";
@@ -220,6 +221,10 @@
"screen_media_picker_error_failed_selection" = "Failed selecting media, please try again.";
"screen_media_upload_preview_error_failed_processing" = "Failed processing media to upload, please try again.";
"screen_media_upload_preview_error_failed_sending" = "Failed uploading media, please try again.";
"screen_onboarding_sign_in_manually" = "Sign in manually";
"screen_onboarding_sign_in_with_qr_code" = "Sign in with QR code";
"screen_onboarding_sign_up" = "Create account";
"screen_onboarding_subtitle" = "Communicate and collaborate securely";
"screen_onboarding_welcome_subtitle" = "Welcome to the %1$@ Beta. Supercharged, for speed and simplicity.";
"screen_onboarding_welcome_title" = "Be in your Element";
"screen_report_content_block_user_hint" = "Check if you want to hide all current and future messages from this user";
@@ -236,6 +241,8 @@
"screen_room_details_edition_error_title" = "Unable to update room";
"screen_room_details_encryption_enabled_subtitle" = "Messages are secured with locks. Only you and the recipients have the unique keys to unlock them.";
"screen_room_details_encryption_enabled_title" = "Message encryption enabled";
"screen_room_details_invite_people_title" = "Invite people";
"screen_room_details_room_name_label" = "Room name";
"screen_room_details_share_room_title" = "Share room";
"screen_room_details_updating_room" = "Updating room…";
"screen_room_member_details_block_alert_action" = "Block";
@@ -351,12 +358,10 @@
"screen_dm_details_unblock_alert_action" = "Unblock";
"screen_dm_details_unblock_alert_description" = "On unblocking the user, you will be able to see all messages by them again.";
"screen_dm_details_unblock_user" = "Unblock user";
"screen_invites_invited_you" = "%1$@ invited you";
"screen_login_password_hint" = "Password";
"screen_login_submit" = "Continue";
"screen_login_username_hint" = "Username";
"screen_report_content_block_user" = "Block user";
"screen_room_details_invite_people_title" = "Invite people";
"screen_room_details_leave_room_title" = "Leave room";
"screen_room_details_people_title" = "People";
"screen_room_details_security_title" = "Security";
+19 -9
View File
@@ -198,6 +198,8 @@ public enum L10n {
public static var commonReportSubmitted: String { return L10n.tr("Localizable", "common_report_submitted") }
/// Room name
public static var commonRoomName: String { return L10n.tr("Localizable", "common_room_name") }
/// e.g. your project name
public static var commonRoomNamePlaceholder: String { return L10n.tr("Localizable", "common_room_name_placeholder") }
/// Search for someone
public static var commonSearchForSomeone: String { return L10n.tr("Localizable", "common_search_for_someone") }
/// Search results
@@ -224,6 +226,8 @@ public enum L10n {
public static var commonSuggestions: String { return L10n.tr("Localizable", "common_suggestions") }
/// Topic
public static var commonTopic: String { return L10n.tr("Localizable", "common_topic") }
/// What is this room about?
public static var commonTopicPlaceholder: String { return L10n.tr("Localizable", "common_topic_placeholder") }
/// Unable to decrypt
public static var commonUnableToDecrypt: String { return L10n.tr("Localizable", "common_unable_to_decrypt") }
/// We were unable to successfully send invites to one or more users.
@@ -492,9 +496,9 @@ public enum L10n {
public static var screenChangeServerTitle: String { return L10n.tr("Localizable", "screen_change_server_title") }
/// New room
public static var screenCreateRoomActionCreateRoom: String { return L10n.tr("Localizable", "screen_create_room_action_create_room") }
/// Invite people
/// Invite friends to Element
public static var screenCreateRoomActionInvitePeople: String { return L10n.tr("Localizable", "screen_create_room_action_invite_people") }
/// Add people
/// Invite people
public static var screenCreateRoomAddPeopleTitle: String { return L10n.tr("Localizable", "screen_create_room_add_people_title") }
/// An error occurred when creating the room
public static var screenCreateRoomErrorCreatingRoom: String { return L10n.tr("Localizable", "screen_create_room_error_creating_room") }
@@ -508,14 +512,10 @@ public enum L10n {
public static var screenCreateRoomPublicOptionTitle: String { return L10n.tr("Localizable", "screen_create_room_public_option_title") }
/// Room name
public static var screenCreateRoomRoomNameLabel: String { return L10n.tr("Localizable", "screen_create_room_room_name_label") }
/// e.g. Product Sprint
public static var screenCreateRoomRoomNamePlaceholder: String { return L10n.tr("Localizable", "screen_create_room_room_name_placeholder") }
/// Create a room
public static var screenCreateRoomTitle: String { return L10n.tr("Localizable", "screen_create_room_title") }
/// Topic (optional)
public static var screenCreateRoomTopicLabel: String { return L10n.tr("Localizable", "screen_create_room_topic_label") }
/// What is this room about?
public static var screenCreateRoomTopicPlaceholder: String { return L10n.tr("Localizable", "screen_create_room_topic_placeholder") }
/// Block
public static var screenDmDetailsBlockAlertAction: String { return L10n.tr("Localizable", "screen_dm_details_block_alert_action") }
/// Blocked users will not be able to send you messages and all message by them will be hidden. You can reverse this action anytime.
@@ -542,9 +542,9 @@ public enum L10n {
public static var screenInvitesDeclineDirectChatTitle: String { return L10n.tr("Localizable", "screen_invites_decline_direct_chat_title") }
/// No Invites
public static var screenInvitesEmptyList: String { return L10n.tr("Localizable", "screen_invites_empty_list") }
/// %1$@ invited you
public static func screenInvitesInvitedYou(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_invites_invited_you", String(describing: p1))
/// %1$@ (%2$@) invited you
public static func screenInvitesInvitedYou(_ p1: Any, _ p2: Any) -> String {
return L10n.tr("Localizable", "screen_invites_invited_you", String(describing: p1), String(describing: p2))
}
/// This account has been deactivated.
public static var screenLoginErrorDeactivatedAccount: String { return L10n.tr("Localizable", "screen_login_error_deactivated_account") }
@@ -576,6 +576,14 @@ public enum L10n {
public static var screenMediaUploadPreviewErrorFailedProcessing: String { return L10n.tr("Localizable", "screen_media_upload_preview_error_failed_processing") }
/// Failed uploading media, please try again.
public static var screenMediaUploadPreviewErrorFailedSending: String { return L10n.tr("Localizable", "screen_media_upload_preview_error_failed_sending") }
/// Sign in manually
public static var screenOnboardingSignInManually: String { return L10n.tr("Localizable", "screen_onboarding_sign_in_manually") }
/// Sign in with QR code
public static var screenOnboardingSignInWithQrCode: String { return L10n.tr("Localizable", "screen_onboarding_sign_in_with_qr_code") }
/// Create account
public static var screenOnboardingSignUp: String { return L10n.tr("Localizable", "screen_onboarding_sign_up") }
/// Communicate and collaborate securely
public static var screenOnboardingSubtitle: String { return L10n.tr("Localizable", "screen_onboarding_subtitle") }
/// Welcome to the %1$@ Beta. Supercharged, for speed and simplicity.
public static func screenOnboardingWelcomeSubtitle(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_onboarding_welcome_subtitle", String(describing: p1))
@@ -618,6 +626,8 @@ public enum L10n {
public static var screenRoomDetailsLeaveRoomTitle: String { return L10n.tr("Localizable", "screen_room_details_leave_room_title") }
/// People
public static var screenRoomDetailsPeopleTitle: String { return L10n.tr("Localizable", "screen_room_details_people_title") }
/// Room name
public static var screenRoomDetailsRoomNameLabel: String { return L10n.tr("Localizable", "screen_room_details_room_name_label") }
/// Security
public static var screenRoomDetailsSecurityTitle: String { return L10n.tr("Localizable", "screen_room_details_security_title") }
/// Share room
@@ -33,4 +33,8 @@ extension UserProfileProxy {
static var mockCharlie: UserProfileProxy {
.init(userID: "@charlie:matrix.org", displayName: "Charlie", avatarURL: nil)
}
static var mockVerbose: UserProfileProxy {
.init(userID: "@charlie:matrix.org", displayName: "Charlie is the best display name", avatarURL: nil)
}
}
@@ -30,7 +30,7 @@ extension Publisher where Output == String, Failure == Never {
/// Clearing the text publishes the update immediately.
func debounceAndRemoveDuplicates() -> AnyPublisher<String, Never> {
map { query in
let milliseconds = query.isEmpty ? 0 : 500
let milliseconds = query.isEmpty ? 0 : 250
return Just(query).delay(for: .milliseconds(milliseconds), scheduler: DispatchQueue.main)
}
.switchToLatest()
@@ -0,0 +1,28 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import SwiftUI
extension Section where Parent == Color, Content == EmptyView, Footer == EmptyView {
// An empty section whose purpose is to keep Form's background color when there is no content into it.
static var empty: some View {
Section {
EmptyView()
} header: {
Color.clear
}
}
}
+38 -4
View File
@@ -18,6 +18,11 @@ import Combine
import UIKit
class ScrollViewAdapter: NSObject, UIScrollViewDelegate {
enum ScrollDirection {
case up
case down
}
var scrollView: UIScrollView? {
didSet {
oldValue?.delegate = nil
@@ -25,13 +30,28 @@ class ScrollViewAdapter: NSObject, UIScrollViewDelegate {
}
}
let didScroll = PassthroughSubject<Void, Never>()
let isScrolling = CurrentValueSubject<Bool, Never>(false)
private let didScrollSubject = PassthroughSubject<Void, Never>()
var didScroll: AnyPublisher<Void, Never> {
didScrollSubject.eraseToAnyPublisher()
}
private let isScrollingSubject = CurrentValueSubject<Bool, Never>(false)
var isScrolling: CurrentValuePublisher<Bool, Never> {
.init(isScrollingSubject)
}
private let scrollDirectionSubject: PassthroughSubject<ScrollDirection, Never> = .init()
var scrollDirection: AnyPublisher<ScrollDirection, Never> {
scrollDirectionSubject
.removeDuplicates()
.eraseToAnyPublisher()
}
// MARK: - UIScrollViewDelegate
func scrollViewDidScroll(_ scrollView: UIScrollView) {
didScroll.send(())
didScrollSubject.send(())
updateScrollDirection(scrollView)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
@@ -57,6 +77,20 @@ class ScrollViewAdapter: NSObject, UIScrollViewDelegate {
// MARK: - Private
private func updateDidScroll(_ scrollView: UIScrollView) {
isScrolling.send(scrollView.isDragging || scrollView.isDecelerating)
isScrollingSubject.send(scrollView.isDragging || scrollView.isDecelerating)
}
private func updateScrollDirection(_ scrollView: UIScrollView) {
let velocity = scrollView.panGestureRecognizer.velocity(in: nil)
if velocity.y > Constant.scrollDirectionThreshold {
scrollDirectionSubject.send(.down)
} else if velocity.y < -Constant.scrollDirectionThreshold {
scrollDirectionSubject.send(.up)
}
}
private enum Constant {
static let scrollDirectionThreshold: CGFloat = 200
}
}
@@ -17,13 +17,31 @@
import SwiftUI
/// A view to be added on the trailing edge of a form row.
enum FormRowAccessory: View {
case navigationLink
case progressView
case selection(isSelected: Bool)
struct FormRowAccessory: View {
@Environment(\.isEnabled) private var isEnabled
enum Kind {
case navigationLink
case progressView
case selection(isSelected: Bool)
}
let kind: Kind
static var navigationLink: Self {
.init(kind: .navigationLink)
}
static var progressView: Self {
.init(kind: .progressView)
}
static func selection(isSelected: Bool) -> Self {
.init(kind: .selection(isSelected: isSelected))
}
var body: some View {
switch self {
switch kind {
case .navigationLink:
Image(systemName: "chevron.forward")
.font(.compound.bodyMD.bold())
@@ -33,9 +51,13 @@ enum FormRowAccessory: View {
case .selection(let isSelected):
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
.font(.compound.bodyLG)
.foregroundColor(isSelected ? .element.primaryContent : .element.tertiaryContent)
.foregroundColor(isSelected && isEnabled ? .element.primaryContent : .element.tertiaryContent)
}
}
private init(kind: Kind) {
self.kind = kind
}
}
/// Default button styling for form rows.
@@ -44,8 +66,6 @@ enum FormRowAccessory: View {
/// to change the background colour depending on whether the button is currently pressed or not.
struct FormButtonStyle: PrimitiveButtonStyle {
var iconAlignment: VerticalAlignment = .firstTextBaseline
/// Disables the button while providing an entry point for UI customizations
var isDisabled = false
/// An accessory to be added on the trailing side of the row.
var accessory: FormRowAccessory?
@@ -55,14 +75,12 @@ struct FormButtonStyle: PrimitiveButtonStyle {
.labelStyle(FormRowLabelStyle(alignment: iconAlignment, role: configuration.role))
.frame(maxHeight: .infinity) // Make sure the label fills the cell vertically.
}
.buttonStyle(Style(isDisabled: isDisabled, accessory: accessory))
.buttonStyle(Style(accessory: accessory))
.listRowInsets(EdgeInsets()) // Remove insets so the background fills the cell.
.disabled(isDisabled)
}
/// Inner style used to set the pressed background colour.
struct Style: ButtonStyle {
var isDisabled: Bool
private struct Style: ButtonStyle {
var accessory: FormRowAccessory?
func makeBody(configuration: Configuration) -> some View {
@@ -73,7 +91,6 @@ struct FormButtonStyle: PrimitiveButtonStyle {
.frame(maxWidth: .infinity, alignment: .leading)
accessory
.opacity(isDisabled ? 0.54 : 1)
}
.contentShape(Rectangle())
.padding(FormRow.insets) // Re-apply the normal insets using padding.
@@ -135,14 +152,18 @@ struct FormButtonStyles_Previews: PreviewProvider {
Text("Hello world")
}
.buttonStyle(FormButtonStyle())
Button { } label: {
Text("Selected")
}
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: true)))
Button { } label: {
Text("Selected (disabled)")
}
.buttonStyle(FormButtonStyle(isDisabled: true, accessory: .selection(isSelected: true)))
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: true)))
.disabled(true)
Button { } label: {
Text("Unselected")
}
@@ -29,10 +29,9 @@ struct UserProfileCell: View {
contentID: user.userID,
avatarSize: .user(on: .startChat),
imageProvider: imageProvider)
.padding(.vertical, 10)
.accessibilityHidden(true)
VStack(alignment: .leading, spacing: 4) {
VStack(alignment: .leading, spacing: 0) {
Text(user.displayName ?? user.userID)
.font(.compound.headingSM)
.foregroundColor(.element.primaryContent)
@@ -40,6 +39,7 @@ struct UserProfileCell: View {
subtitleContent
warningContent
.padding(.top, 2)
}
.accessibilityElement(children: .combine)
}
@@ -63,15 +63,14 @@ struct UserProfileCell: View {
@ViewBuilder
private var warningContent: some View {
if !user.isVerified, membership == nil {
HStack(alignment: .firstTextBaseline, spacing: 4) {
HStack(alignment: .firstTextBaseline, spacing: 5) {
Text(Image(systemName: "exclamationmark.circle.fill"))
.foregroundColor(.compound.textCriticalPrimary)
Text(L10n.commonInviteUnknownProfile)
.foregroundColor(.secondary)
.foregroundColor(.element.tertiaryContent)
}
.font(.compound.bodyXS)
.padding(.top, 4)
}
}
}
@@ -109,13 +108,15 @@ struct UserProfileCell_Previews: PreviewProvider {
Button(action: action) {
UserProfileCell(user: .mockCharlie, membership: .join, imageProvider: MockMediaProvider())
}
.buttonStyle(FormButtonStyle(isDisabled: true, accessory: .selection(isSelected: true)))
.disabled(true)
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: true)))
.previewDisplayName("Selected disabled user")
Button(action: action) {
UserProfileCell(user: .init(userID: "@someone:matrix.org"), membership: .join, imageProvider: MockMediaProvider())
}
.buttonStyle(FormButtonStyle(isDisabled: true, accessory: .selection(isSelected: true)))
.disabled(true)
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: true)))
.previewDisplayName("Unverified joined user")
Button(action: action) {
@@ -26,32 +26,25 @@ struct CreateRoomScreen: View {
}
var body: some View {
mainContent
.scrollDismissesKeyboard(.immediately)
.elementFormStyle()
.navigationTitle(L10n.screenCreateRoomTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
createButton
}
}
.background(ViewFrameReader(frame: $frame))
.alert(item: $context.alertInfo) { $0.alert }
}
/// The main content of the view to be shown in a scroll view.
var mainContent: some View {
Form {
roomSection
topicSection
Spacer()
.listRowBackground(Color.clear)
securitySection
}
.elementFormStyle()
.scrollDismissesKeyboard(.immediately)
.navigationTitle(L10n.screenCreateRoomTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
createButton
}
}
.background(ViewFrameReader(frame: $frame))
.alert(item: $context.alertInfo) { $0.alert }
}
@ScaledMetric private var roomIconSize: CGFloat = 64
@ScaledMetric private var roomIconSize: CGFloat = 70
private var roomSection: some View {
Section {
HStack(alignment: .center, spacing: 16) {
@@ -94,7 +87,7 @@ struct CreateRoomScreen: View {
.formSectionHeader()
TextField(L10n.screenCreateRoomRoomNameLabel,
text: $context.roomName,
prompt: Text(L10n.screenCreateRoomRoomNamePlaceholder),
prompt: Text(L10n.commonRoomNamePlaceholder),
axis: .horizontal)
.focused($focus, equals: .name)
.accessibilityIdentifier(A11yIdentifiers.createRoomScreen.roomName)
@@ -111,6 +104,7 @@ struct CreateRoomScreen: View {
private var cameraImage: some View {
Image(systemName: "camera")
.font(.system(size: 28, weight: .semibold))
.foregroundColor(.element.secondaryContent)
.frame(width: roomIconSize, height: roomIconSize)
.background(Color.element.quinaryContent)
@@ -121,7 +115,7 @@ struct CreateRoomScreen: View {
Section {
TextField(L10n.screenCreateRoomTopicLabel,
text: $context.roomTopic,
prompt: Text(L10n.screenCreateRoomTopicPlaceholder),
prompt: Text(L10n.commonTopicPlaceholder),
axis: .vertical)
.focused($focus, equals: .topic)
.accessibilityIdentifier(A11yIdentifiers.createRoomScreen.roomTopic)
@@ -138,11 +132,11 @@ struct CreateRoomScreen: View {
}
@State private var frame: CGRect = .zero
@ScaledMetric private var invitedUserCellWidth: CGFloat = 64
@ScaledMetric private var invitedUserCellWidth: CGFloat = 72
private var selectedUsersSection: some View {
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 28) {
LazyHStack(spacing: 16) {
ForEach(context.viewState.selectedUsers, id: \.userID) { user in
InviteUsersScreenSelectedItem(user: user, imageProvider: context.imageProvider) {
context.send(viewAction: .deselectUser(user))
@@ -150,7 +144,7 @@ struct CreateRoomScreen: View {
.frame(width: invitedUserCellWidth)
}
}
.padding(.init(top: 16, leading: 32, bottom: 16, trailing: 32))
.padding(.init(top: 22, leading: 20, bottom: 0, trailing: 32))
}
.frame(width: frame.width)
}
@@ -159,23 +153,26 @@ struct CreateRoomScreen: View {
Section {
Picker(L10n.commonSecurity, selection: $context.isRoomPrivate) {
Label {
VStack(alignment: .leading, spacing: 8) {
VStack(alignment: .leading, spacing: 2) {
Text(L10n.screenCreateRoomPrivateOptionTitle)
.font(.compound.bodyLG)
Text(L10n.screenCreateRoomPrivateOptionDescription)
.font(.compound.bodyMD)
.foregroundColor(.element.secondaryContent)
.font(.compound.bodyXS)
.foregroundColor(.element.tertiaryContent)
}
} icon: {
Image(systemName: "lock.shield")
}
.tag(true)
.labelStyle(FormRowLabelStyle(alignment: .top))
Label {
VStack(alignment: .leading, spacing: 8) {
VStack(alignment: .leading, spacing: 2) {
Text(L10n.screenCreateRoomPublicOptionTitle)
.font(.compound.bodyLG)
Text(L10n.screenCreateRoomPublicOptionDescription)
.font(.compound.bodyMD)
.foregroundColor(.element.secondaryContent)
.font(.compound.bodyXS)
.foregroundColor(.element.tertiaryContent)
}
} icon: {
Image(systemName: "exclamationmark.shield")
@@ -188,8 +185,10 @@ struct CreateRoomScreen: View {
} header: {
Text(L10n.commonSecurity.uppercased())
.formSectionHeader()
.padding(.top, 40)
}
.formSectionStyle()
.listRowSeparatorTint(.element.quinaryContent)
.listRowBackground(Color.element.formRowBackground)
}
private var createButton: some View {
@@ -26,6 +26,7 @@ struct HomeScreen: View {
@State private var scrollViewAdapter = ScrollViewAdapter()
@State private var showingLogoutConfirmation = false
@State private var showingBottomToolbar = true
var body: some View {
ScrollView {
@@ -99,6 +100,11 @@ struct HomeScreen: View {
}
}
}
.onReceive(scrollViewAdapter.scrollDirection) { direction in
withAnimation(.elementDefault) {
showingBottomToolbar = (direction == .down)
}
}
.onChange(of: context.viewState.visibleRooms) { _ in
// Give the view a chance to update
DispatchQueue.main.async {
@@ -115,20 +121,27 @@ struct HomeScreen: View {
actions: leaveRoomAlertActions,
message: leaveRoomAlertMessage)
.navigationTitle(L10n.screenRoomlistMainSpaceTitle)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
userMenuButton
}
if context.viewState.startChatFlowEnabled {
ToolbarItemGroup(placement: .bottomBar) {
Spacer()
newRoomButton
}
}
}
.toolbar(showingBottomToolbar ? .automatic : .hidden, for: .bottomBar)
.toolbar { toolbar }
.background(Color.element.background.ignoresSafeArea())
.track(screen: .home)
}
// MARK: - Private
@ToolbarContentBuilder
private var toolbar: some ToolbarContent {
ToolbarItem(placement: .navigationBarLeading) {
userMenuButton
}
ToolbarItemGroup(placement: .bottomBar) {
if context.viewState.startChatFlowEnabled {
Spacer()
newRoomButton
}
}
}
@ViewBuilder
private var userMenuButton: some View {
@@ -34,6 +34,7 @@ struct HomeScreenInvitesButton: View {
badge
}
}
.padding(.vertical, 4)
}
}
@@ -46,7 +47,7 @@ struct HomeScreenInvitesButton: View {
}
}
struct InvitesButton_Previews: PreviewProvider {
struct HomeScreenInvitesButton_Previews: PreviewProvider {
static var previews: some View {
HomeScreenInvitesButton(title: "Invites", hasBadge: true, action: { })
.previewDisplayName("Badge on")
@@ -55,6 +55,8 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
actionsSubject.send(.invite(users: state.selectedUsers.map(\.userID)))
}
case .toggleUser(let user):
let willSelectUser = !state.selectedUsers.contains(user)
state.scrollToLastID = willSelectUser ? user.userID : nil
actionsSubject.send(.toggleUser(user))
}
}
@@ -23,6 +23,7 @@ struct InviteUsersScreen: View {
var body: some View {
mainContent
.elementFormStyle()
.scrollDismissesKeyboard(.immediately)
.navigationTitle(L10n.screenCreateRoomActionInvitePeople)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -69,33 +70,38 @@ struct InviteUsersScreen: View {
.accessibilityIdentifier(A11yIdentifiers.startChatScreen.searchNoResults)
}
@ViewBuilder
private var usersSection: some View {
Section {
ForEach(context.viewState.usersSection.users, id: \.userID) { user in
Button { context.send(viewAction: .toggleUser(user)) } label: {
UserProfileCell(user: user,
membership: context.viewState.membershipState(user),
imageProvider: context.imageProvider)
if !context.viewState.usersSection.users.isEmpty {
Section {
ForEach(context.viewState.usersSection.users, id: \.userID) { user in
Button { context.send(viewAction: .toggleUser(user)) } label: {
UserProfileCell(user: user,
membership: context.viewState.membershipState(user),
imageProvider: context.imageProvider)
}
.disabled(context.viewState.isUserDisabled(user))
.buttonStyle(FormButtonStyle(accessory: .selection(isSelected: context.viewState.isUserSelected(user))))
}
} header: {
if let title = context.viewState.usersSection.title {
Text(title)
}
.buttonStyle(FormButtonStyle(isDisabled: context.viewState.isUserDisabled(user),
accessory: .selection(isSelected: context.viewState.isUserSelected(user))))
}
} header: {
if let title = context.viewState.usersSection.title {
Text(title)
}
.listRowSeparator(.automatic)
.formSectionStyle()
} else {
Section.empty
}
.listRowSeparator(.automatic)
.formSectionStyle()
}
@State private var frame: CGRect = .zero
@ScaledMetric private var cellWidth: CGFloat = 64
@ScaledMetric private var cellWidth: CGFloat = 72
private var selectedUsersSection: some View {
ScrollView(.horizontal, showsIndicators: false) {
ScrollViewReader { scrollView in
HStack(spacing: 28) {
HStack(spacing: 16) {
ForEach(context.viewState.selectedUsers, id: \.userID) { user in
InviteUsersScreenSelectedItem(user: user, imageProvider: context.imageProvider) {
deselect(user)
@@ -109,7 +115,7 @@ struct InviteUsersScreen: View {
scrollView.scrollTo(id)
}
}
.padding(.horizontal, 18)
.padding(.horizontal, 14)
}
}
.frame(width: frame.width)
@@ -21,30 +21,50 @@ struct InviteUsersScreenSelectedItem: View {
let imageProvider: ImageProviderProtocol?
let dismissAction: () -> Void
@ScaledMetric private var buttonSize: CGFloat = 20
var body: some View {
VStack(spacing: 4) {
LoadableAvatarImage(url: user.avatarURL,
name: user.displayName,
contentID: user.userID,
avatarSize: .user(on: .inviteUsers),
imageProvider: imageProvider)
VStack(spacing: 0) {
avatar
Text(user.displayName ?? user.userID)
.font(.compound.headingSM)
.font(.compound.bodyMD)
.foregroundColor(.element.primaryContent)
.lineLimit(1)
}
.overlay(alignment: .topTrailing) {
Button(action: dismissAction) {
Image(systemName: "xmark.circle.fill")
.symbolRenderingMode(.palette)
.foregroundStyle(Color.element.systemPrimaryBackground, Color.element.primaryContent)
}
// MARK: - Private
var avatar: some View {
LoadableAvatarImage(url: user.avatarURL,
name: user.displayName,
contentID: user.userID,
avatarSize: .user(on: .inviteUsers),
imageProvider: imageProvider)
.overlay(alignment: .topTrailing) {
Button(action: dismissAction) {
Image(systemName: "xmark.circle.fill")
.resizable()
.frame(width: buttonSize, height: buttonSize)
.symbolRenderingMode(.palette)
.foregroundStyle(Color.element.systemPrimaryBackground, Color.element.primaryContent)
}
}
}
}
}
struct InviteUsersScreenSelectedItem_Previews: PreviewProvider {
static let people: [UserProfileProxy] = [.mockAlice, .mockVerbose]
static var previews: some View {
InviteUsersScreenSelectedItem(user: .mockAlice, imageProvider: MockMediaProvider(), dismissAction: { })
ScrollView(.horizontal) {
HStack(spacing: 28) {
ForEach(Self.people, id: \.userID) { user in
InviteUsersScreenSelectedItem(user: user, imageProvider: MockMediaProvider(), dismissAction: { })
.frame(width: 72)
}
}
}
}
}
@@ -22,7 +22,7 @@ struct InvitesScreen: View {
var body: some View {
ScrollView {
if let rooms = context.viewState.invites, !rooms.isEmpty {
LazyVStack {
LazyVStack(spacing: 0) {
ForEach(rooms, id: \.roomDetails.id) { invite in
InvitesScreenCell(invite: invite,
imageProvider: context.imageProvider,
@@ -34,6 +34,7 @@ struct InvitesScreen: View {
noInvitesContent
}
}
.background(Color.element.background.ignoresSafeArea())
.navigationTitle(L10n.actionInvitesList)
.alert(item: $context.alertInfo) { $0.alert }
}
@@ -23,7 +23,6 @@ struct InvitesScreenCell: View {
let acceptAction: () -> Void
let declineAction: () -> Void
private let verticalInsets = 16.0
@ScaledMetric private var badgeSize = 12.0
var body: some View {
@@ -37,21 +36,23 @@ struct InvitesScreenCell: View {
mainContent
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.bottom, verticalInsets)
.padding(.bottom, 16)
.padding(.trailing, 16)
.overlay(alignment: .bottom) {
separator
}
}
.padding(.top, verticalInsets)
.padding(.horizontal, 16)
.padding(.top, 12)
.padding(.leading, 16)
}
// MARK: - Private
private var mainContent: some View {
VStack(alignment: .leading, spacing: 6) {
HStack(alignment: .firstTextBaseline, spacing: 12) {
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .firstTextBaseline, spacing: 16) {
textualContent
.padding(.trailing, invite.isUnread ? 0 : 16)
if invite.isUnread {
badge
@@ -59,26 +60,28 @@ struct InvitesScreenCell: View {
}
inviterView
.padding(.top, 6)
.padding(.trailing, 16)
buttons
.padding(.trailing, 26)
.padding(.top, 8)
.padding(.top, 14)
.padding(.trailing, 22)
}
}
@ViewBuilder
private var inviterView: some View {
if let invitedText = attributedInviteText, let name = invite.inviter?.displayName {
HStack {
HStack(alignment: .firstTextBaseline) {
LoadableAvatarImage(url: invite.inviter?.avatarURL,
name: name,
contentID: name,
avatarSize: .custom(16),
imageProvider: imageProvider)
.alignmentGuide(.firstTextBaseline) { $0[.bottom] * 0.8 }
Text(invitedText)
}
.padding(.top, 4)
}
}
@@ -88,6 +91,7 @@ struct InvitesScreenCell: View {
Text(title)
.font(.compound.bodyLGSemibold)
.foregroundColor(.element.primaryContent)
.lineLimit(2)
if let subtitle {
Text(subtitle)
@@ -130,16 +134,21 @@ struct InvitesScreenCell: View {
}
private var attributedInviteText: AttributedString? {
guard invite.roomDetails.isDirect == false, let inviterName = invite.inviter?.displayName else {
guard
invite.roomDetails.isDirect == false,
let inviterName = invite.inviter?.displayName,
let inviterID = invite.inviter?.userID
else {
return nil
}
let text = L10n.screenInvitesInvitedYou(inviterName)
let text = L10n.screenInvitesInvitedYou(inviterName, inviterID)
var attributedString = AttributedString(text)
attributedString.font = .compound.bodyMD
attributedString.foregroundColor = .compound.textPlaceholder
if let range = attributedString.range(of: inviterName) {
attributedString[range].foregroundColor = .compound.textPrimary
attributedString[range].font = .compound.bodyMDSemibold
}
return attributedString
}
@@ -153,18 +162,21 @@ struct InvitesScreenCell: View {
struct InvitesScreenCell_Previews: PreviewProvider {
static var previews: some View {
InvitesScreenCell(invite: .dm, imageProvider: MockMediaProvider(), acceptAction: { }, declineAction: { })
.previewDisplayName("Direct room")
InvitesScreenCell(invite: .room(alias: nil), imageProvider: MockMediaProvider(), acceptAction: { }, declineAction: { })
.previewDisplayName("Default room")
InvitesScreenCell(invite: .room(alias: "#footest:somewhere.org"), imageProvider: MockMediaProvider(), acceptAction: { }, declineAction: { })
.previewDisplayName("Aliased room")
InvitesScreenCell(invite: .room(alias: "#footest:somewhere.org"), imageProvider: MockMediaProvider(), acceptAction: { }, declineAction: { })
.dynamicTypeSize(.accessibility1)
.previewDisplayName("Aliased room (AX1)")
ScrollView {
VStack(spacing: 0) {
InvitesScreenCell(invite: .dm, imageProvider: MockMediaProvider(), acceptAction: { }, declineAction: { })
InvitesScreenCell(invite: .room(), imageProvider: MockMediaProvider(), acceptAction: { }, declineAction: { })
InvitesScreenCell(invite: .room(isUnread: false), imageProvider: MockMediaProvider(), acceptAction: { }, declineAction: { })
InvitesScreenCell(invite: .room(alias: "#footest:somewhere.org", avatarURL: .picturesDirectory), imageProvider: MockMediaProvider(), acceptAction: { }, declineAction: { })
InvitesScreenCell(invite: .room(alias: "#footest:somewhere.org"), imageProvider: MockMediaProvider(), acceptAction: { }, declineAction: { })
.dynamicTypeSize(.accessibility1)
.previewDisplayName("Aliased room (AX1)")
}
}
}
}
@@ -186,20 +198,20 @@ private extension InvitesScreenRoomDetails {
return .init(roomDetails: dmRoom, inviter: inviter, isUnread: false)
}
static func room(alias: String?) -> InvitesScreenRoomDetails {
static func room(alias: String? = nil, avatarURL: URL? = nil, isUnread: Bool = true) -> InvitesScreenRoomDetails {
let dmRoom = RoomSummaryDetails(id: "@someone:somewhere.com",
name: "Awesome Room",
isDirect: false,
avatarURL: nil,
avatarURL: avatarURL,
lastMessage: nil,
lastMessageFormattedTimestamp: nil,
unreadNotificationCount: 0,
canonicalAlias: alias)
let inviter = RoomMemberProxyMock()
inviter.displayName = "Luca"
inviter.userID = "@jack:somewhere.com"
inviter.avatarURL = nil
inviter.userID = "@jack:somewhi.nl"
inviter.avatarURL = avatarURL
return .init(roomDetails: dmRoom, inviter: inviter, isUnread: true)
return .init(roomDetails: dmRoom, inviter: inviter, isUnread: isUnread)
}
}
@@ -26,36 +26,35 @@ struct RoomDetailsEditScreen: View {
}
var body: some View {
mainContent
.elementFormStyle()
.scrollDismissesKeyboard(.immediately)
.navigationTitle(L10n.screenRoomDetailsEditRoomTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) {
context.send(viewAction: .cancel)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(L10n.actionSave) {
context.send(viewAction: .save)
focus = nil
}
.disabled(!context.viewState.canSave)
}
}
}
// MARK: - Private
private var mainContent: some View {
Form {
avatar
nameSection
topicSection
}
.elementFormStyle()
.scrollDismissesKeyboard(.immediately)
.navigationTitle(L10n.screenRoomDetailsEditRoomTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
}
// MARK: - Private
@ToolbarContentBuilder
private var toolbar: some ToolbarContent {
ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) {
context.send(viewAction: .cancel)
}
}
ToolbarItem(placement: .confirmationAction) {
Button(L10n.actionSave) {
context.send(viewAction: .save)
focus = nil
}
.disabled(!context.viewState.canSave)
}
}
private var avatar: some View {
@@ -89,7 +88,7 @@ struct RoomDetailsEditScreen: View {
TextField(L10n.commonRoomName,
text: $context.name,
prompt: canEditName ? Text(L10n.screenCreateRoomRoomNamePlaceholder) : nil,
prompt: canEditName ? Text(L10n.commonRoomNamePlaceholder) : nil,
axis: .horizontal)
.focused($focus, equals: .name)
.font(.compound.bodyLG)
@@ -110,7 +109,7 @@ struct RoomDetailsEditScreen: View {
TextField(L10n.commonTopic,
text: $context.topic,
prompt: canEditTopic ? Text(L10n.screenCreateRoomTopicPlaceholder) : nil,
prompt: canEditTopic ? Text(L10n.commonTopicPlaceholder) : nil,
axis: .vertical)
.focused($focus, equals: .topic)
.font(.compound.bodyLG)
@@ -127,7 +126,8 @@ struct RoomDetailsEditScreen: View {
private var avatarOverlayIcon: some View {
Image(systemName: "camera")
.padding(2)
.font(.system(size: 14, weight: .semibold))
.padding(3)
.imageScale(.small)
.foregroundColor(.white)
.background {
@@ -153,7 +153,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie
private func showLoadingIndicator() {
userIndicatorController?.submitIndicator(UserIndicator(id: Self.loadingIndicatorIdentifier,
type: .modal,
type: .modal(interactiveDismissDisabled: true),
title: L10n.commonLoading,
persistent: true))
}
@@ -29,6 +29,7 @@ struct StartChatScreen: View {
}
}
.elementFormStyle()
.scrollDismissesKeyboard(.immediately)
.navigationTitle(L10n.actionStartChat)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -64,7 +65,8 @@ struct StartChatScreen: View {
private var createRoomSection: some View {
Section {
Button(action: createRoom) {
Label(L10n.actionCreateARoom, systemImage: "person.3")
Label(L10n.screenCreateRoomActionCreateRoom, systemImage: "person.3")
.imageScale(.small)
}
.buttonStyle(FormButtonStyle(accessory: .navigationLink))
}
@@ -99,6 +101,8 @@ struct StartChatScreen: View {
}
.listRowSeparator(.automatic)
.formSectionStyle()
} else {
Section.empty
}
}
@@ -234,7 +234,7 @@ extension NotificationItemProxyProtocol {
senderID: event.senderID,
senderName: senderDisplayName ?? event.senderID,
iconType: iconType)
notification.body = L10n.screenInvitesInvitedYou("")
notification.body = L10n.stateEventRoomInviteYou("")
return notification
}
@@ -30,7 +30,7 @@ final class UserDiscoveryService: UserDiscoveryServiceProtocol {
func searchProfiles(with searchQuery: String) async -> Result<[UserProfileProxy], UserDiscoveryErrorType> {
do {
async let queriedProfile = try? profileIfPossible(with: searchQuery).get()
async let searchedUsers = clientProxy.searchUsers(searchTerm: searchQuery, limit: 5)
async let searchedUsers = clientProxy.searchUsers(searchTerm: searchQuery, limit: 10)
let users = try await merge(searchQuery: searchQuery, queriedProfile: queriedProfile, searchResults: searchedUsers.get())
return .success(users)
} catch {
@@ -17,7 +17,7 @@
import Foundation
import MatrixRustSDK
struct UserProfileProxy {
struct UserProfileProxy: Equatable {
let userID: String
let displayName: String?
let avatarURL: URL?
@@ -36,6 +36,7 @@ class CreateRoomScreenUITests: XCTestCase {
// repeat the same letter enough times to avoid that but also to work on iPads
app.textFields[A11yIdentifiers.createRoomScreen.roomName].tap()
app.textFields[A11yIdentifiers.createRoomScreen.roomName].typeText(.init(repeating: "x", count: 200))
app.textFields[A11yIdentifiers.createRoomScreen.roomName].typeText("\n")
try await app.assertScreenshot(.createRoom, step: 1)
}
+1 -1
View File
@@ -20,7 +20,7 @@ import XCTest
class HomeScreenUITests: XCTestCase {
func testInitialStateComponents() async throws {
let app = Application.launch(.home)
// The gradient of the skeleton canges dynamically over time so the time may influence the match, better to have a lower precision for this one
// The gradient of the skeleton changes dynamically over time so the time may influence the match, better to have a lower precision for this one
try await app.assertScreenshot(.home)
}
}
+2 -2
View File
@@ -27,7 +27,7 @@ class StartChatScreenUITests: XCTestCase {
func testSearchWithNoResults() async throws {
let app = Application.launch(.startChat)
let searchField = app.searchFields.firstMatch
searchField.clearAndTypeText("None")
searchField.clearAndTypeText("None\n")
XCTAssert(app.staticTexts[A11yIdentifiers.startChatScreen.searchNoResults].waitForExistence(timeout: 1.0))
try await app.assertScreenshot(.startChat, step: 1)
}
@@ -35,7 +35,7 @@ class StartChatScreenUITests: XCTestCase {
func testSearchWithResults() async throws {
let app = Application.launch(.startChatWithSearchResults)
let searchField = app.searchFields.firstMatch
searchField.clearAndTypeText("Bob")
searchField.clearAndTypeText("Bob\n")
XCTAssertFalse(app.staticTexts[A11yIdentifiers.startChatScreen.searchNoResults].waitForExistence(timeout: 1.0))
XCTAssertEqual(app.collectionViews.firstMatch.cells.count, 2)
try await app.assertScreenshot(.startChat, step: 2)
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:baaa8f3c90cfd2c3fee4aaf643c5d878ac7ad0985f69b26f6f4d4626f721dc0f
size 209068
oid sha256:b98c8f285e897db8750b1a9c2623dff83657ccac78c92026694ab951fe97370d
size 132704
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:263b659ad21fc364a91162eb0aea523a1cc0db497580ec10377b78f3b9425ed6
size 142198
oid sha256:7bbe9ac53bda18e3ddf5d78719e05dc96e45b7ec9e7647a39af8e0f195df22d6
size 134878
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b3bcc5ad6895274acbc571fed907c519adb3fbf8af9d24483e5e7ab787ceafee
size 119395
oid sha256:a1fc3bedc93e98a70960100bed7b7e42d75f527bac704a9ab2b911a706b2f306
size 114079
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c3909142865f334588ac1b1e9dfeb6e3b9ef44c8ad715dfab676f2cb4ea20a7c
size 108716
oid sha256:165c81b9279ee7bead62d0d4ffe0bad27a35ffc6ff9d5222315a6ffd6ecafe61
size 109543
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0126c1cec30da2fb2dd10aa03713ae73cc65e19a14482ce2bd3f6ce51f74ab9c
size 98972
oid sha256:8ad83846a13b9f605e7d356b04c12e17a68ea0d82bfb734f6c5b6e62dd06c25e
size 99723
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:12053c27263fb18bdacfa18a6950ed14f94db58ad54cff05c4853d84e092a929
size 108772
oid sha256:f682e61fc6d92c8e552616f51b324472fca2cc63592279c21a6d1a7bfd710eda
size 109561
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6047011ae61868f6d4ddce792dcdc41ed98ea22f277823de9e65a7f3336481d5
size 97139
oid sha256:4422f5921e33b1934089c713a7ba01f9f1df6f47e49b35f4e79727c0f95fc8c3
size 98044
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:eceabc10203dde514204abb2e9f842ec10f18ca0216c5932bda372ec8ee727a5
size 188129
oid sha256:8a3aaffcbf38b6ddcde6142634a2803705a2d8438a8e2cceb2c8f567ecc1ca9f
size 191803
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11505b76e0455ca673ccd716893e2fe771e34666dcedacbf998bc5c3d0a550ec
size 98629
oid sha256:3bddd4f90e40d59491c39cbc38c6231d65ac245dc82b3de5c9103c5e631018a5
size 102257
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0f23e94a8e39bc7adaa06136aea223b6112626f77425ff3db021c5496bff3f28
size 98983
oid sha256:f11dd1f578ae2940dae840b3960ad0f67e512f93529270836555935559046720
size 102629
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3f1c4b2bd06eca5a87809115c557c2bd56aa932d0d3beeadabea1151ca908a49
size 78981
oid sha256:26fd68190524bd5783049cc5e2bde16b447e66aa385d3a2d8515f9191413d0b6
size 78813
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ae566dbc1b03a1cfdb1b3b1412c79c2b0801b3f60f89180d60be7f8819782449
size 75125
oid sha256:0075eca03efdacb62b8531ad03987ce3ce967ece23ff44a2254ba91f700968bd
size 75117
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2576f456ae6a5e10d7df17031c1487a3607492533d4e9a64620fe186e8eacb8d
size 136466
oid sha256:bed7e7bb223fe28b12880d4b630e1e0226d75a1fcc36cd0d9bdb9c64b940bf7e
size 76764
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4cc5da4209e2b62cfd938b8a9c47c3bf59fe07eb8dcde1ad73b7f7ea9498f56c
size 151050
oid sha256:5b82364f1fdda5b86cdea790ae4a05dfd5f03a72f2ab462f78020db43e39615f
size 88969
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0ab1b76232611d7689f41a08377cab9602f358107922d36618fe547ddb34f523
size 109825
oid sha256:fcfa75205684ebf75efa0a6c2a18f044d0a039aad0c4b6d9fe02b3a2faebcc8a
size 108859
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bc1b97579497bc0dd6cbd72f11b09a1ef50508baea8871f34f3f64b512652ffd
size 202796
oid sha256:6292feb05252ba017f063f455eeee30d9a48ded0a2e74dc486e78675db5bf31a
size 173094
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:924992e12a0c4c25f20c9bd7d79388ad0640cbba7bd0d0bd1fba2c58e1d7e025
size 184886
oid sha256:582e1c2a98c6cb08637f1329bcd119e4abd8aaf1e177a738dd781bb015ab2f83
size 178558
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4cd0921da82ea2a1ee28bfe36c98ef62128e49f9f2244966f45f80d46512f512
size 156826
oid sha256:4e5a3b21c2b5370186f1a6868606456e980c0e8f170985a46fcc466406d6d667
size 149682
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2845c3bdf17835c7d25fb5f40b7c8f2e71f7ecf12dd739599b7935a24d7bd425
size 130104
oid sha256:00f38866c3f3810463005c6ed775a08d76b7486da55a1328ad3bd38a04a4210d
size 131831
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:51b7109da2b03924f3a476d1be83b2b35c7f494839cb9509bd9f8cc2ee7600d2
size 113342
oid sha256:488837d7e8fb28268dc66505211d0bd91dd6715dadbcd5f81df50bda52becb8b
size 117214
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8a92d04655f2c51f2c595d359d7c0b75b47018a17e2ca82f2063b20be6cb737a
size 132541
oid sha256:a4a6fcf1c1e28e5386a31491f0211996abcaed1b3351ad492e4664a5972b6196
size 131917
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e0e8a9421366b6cfc72443ab9d7ee96ffe0b81ada3cd7ed48b911fae4cccf845
size 112719
oid sha256:155cce8a7b5c98e5e8733207ed8a0d38b0ad039d4bcad7573692fc43c857bb4c
size 114647
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:85e5dfea417d1130ed453e1c483034b2e1f717a1418b164a4f9856bfe5a175fc
size 353862
oid sha256:9aa494529eacf68fb164344ea79df97c18b9c2018952e6d4c89ee43969ed5864
size 359486
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7cebc6d0ae1b65debda82b586a4033d83a5c21e9d125855c8544ac70631aaee3
size 112839
oid sha256:0624e010476e83da7d3af40c0119aef50ff3f1cc0b4e17aa1a23a10cad2fdecf
size 121285
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a0cdb8a004a3ca661aae6a649fdbdd0c8990f6bcc8760cf222b05490a7785fa7
size 114708
oid sha256:78a8161f388f6219e397f0edc692f89cd6c047eb8200a0e87800cbf98dfdf1ed
size 121788
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0410d0f1a5b47c01c7fa4349c8aa10ee9a41e7fc550621ca6f10e8b1dc9e099a
size 87208
oid sha256:592e556366f2e641b90b6fa7781e60d2e20ae49ff22941217c762ed1d985b4f0
size 89084
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4916268dcb63398ae44654dc5f98386102d0c9b3516fedfd75a6868dc7d77a1b
size 79861
oid sha256:17829e2bba60490c2c7f407ea76ef165604f58688accd9e651605439e6152801
size 79726
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:edcf7669efe5e3e0124de8b15bb0e7d12705f534a54ee2bbc7b6d6577fc15be2
size 173595
oid sha256:f10b8116c8cb3b3a10f40801d20ce13e82601d4797d52cf24b6c823a8048286d
size 91095
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:130c4c3c1e6fc4b872ab1dff2c728bb90ffe695490ddebed3efd261d8900931a
size 193286
oid sha256:8815bab7cae82e1a4e909e77a0a3d238fefd805d23c95f4382b881a29098ac39
size 113070
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c695db4e372c07c4f0b054673fab92138dbcbb14acf17dcd94541cd67d793ea1
size 133816
oid sha256:dd2edd16664559d36dad411cb91f3e8e0c900bee0cec0a705d38f1ed56c21e10
size 131842
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:22d07df302e6dc102265be2fa731733c0c78f74520c94822fc09ccedbf9f14c9
size 235617
oid sha256:fad59c0ede5e2149e11acca6b0aa8b2520663625305e2c36d36be3ece5603f14
size 155345
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1d48027b7f07bd8e88e1f4b396ce6cfe72575c7c69cf53409318cad60fa5c31b
size 170726
oid sha256:bb8945c748b19041383905c2b7d95635e5c4a5f17fc76c5e914d1fb0ad3c5f16
size 158543
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5a3044792fc93c2b662b03ffe34d4fcd386547196fa6e06417006d406845d011
size 148728
oid sha256:884862e412e1092149c570be27622dc162d74a0cb81d05481bb2467ed158f908
size 136386
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0763b757551c435ecd922bdee878d617d5760c74d81a3c0a9138e64495affea8
size 111446
oid sha256:dd65d281319ced39cb030be3d30a696563199a64c409fa5408f2bb63e17c88a4
size 112846
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f2ceb1f749349fbf41e5fabb7dfd3e7daf5adb47dfb35810bd0f9ed01c8bdc98
size 102368
oid sha256:e70e0b6f6479e63e617901129e6ece9b44e3cefe2749c4dc45118dfa3e09cdda
size 103953
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:343d749a7294d6acd559e2425a704225368adab14553aada095cac8ea94c499a
size 111617
oid sha256:a36ca04a3fd8f6986b681cfb1225b8a5718f07993f243e8d5dca2ca2dc932b56
size 113042
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6b14407efd99162bdd2ad4b859896cdf11b8bf98fc3522e85ae399e909f39c22
size 103057
oid sha256:bfc1198cac4e564e4e16893ef63d40f69821509b999ea1a056bcce828c63d5e3
size 105787
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2b33ca3e65d765ae24b1a0223198e7b42ed0686a825629f5b0f8bc93b357d6b3
size 233512
oid sha256:6d542cd3da60962a5448e535588017cad7d32165a2cae24582f6fb8d2774ad7f
size 236437
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1ecf3a404f38c4c4fa376c236e50a1160f4d6eadbcd77145f156a81cec245e12
size 104268
oid sha256:36902da1fec889dfd9f25d478700a369c53a7887463cecdcf6b4fb59df983003
size 107261
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:161305b820d23a1e29a5d80f1a4a306c68a65af933d039dcd50f49d888a1f552
size 104655
oid sha256:f0eb1c140b180a447c7dd05e39b6f1b79996a11f9e639813c582a22defd8d750
size 107639
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:95180272e203386363518550eaca4ab66a5362a3514e8046b72e2d5010380fab
size 81651
oid sha256:46415f67c771fd4427df70d597c91949acfe8febd255e6913dc9ae6565240944
size 81364
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dff46af4fb867cfe29c989b5c0e4c3ec7db25f2af19ca60e4ea075f65ac719ce
size 78457
oid sha256:b779f2fee144be2ffde89974a6eab6d0dec18c972e21f9453c00a581dec395fd
size 78284
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:59084649c92cd9450f10cd3c36e6f349eb1c6ae118bdbe3e00c0538ef6e4007b
size 137037
oid sha256:f7c664b920004d7aab3c699e22db994fc22d711ab651569745a4cf95bb2626dc
size 79869
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:eb5e31e230afc158c1a8c2de86a34f909bb27e4c42eee5b1b840b397ed5db275
size 152584
oid sha256:c71f306d56b74a008048a7181ed105db4c055756b023064e887606c8739beee1
size 92172
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5ee809d18954f64b7376887a323297c66e6a639b2fb004385eb8143d692bd604
size 114129
oid sha256:ad71830118395e8553974306ab68cb3cc31951193a0cd209e22f3930b81007e8
size 113453
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:652b2de02c2c6e22728d980a9fb2e30408c9e67f556e1771e186167e05bc8b8f
size 215886
oid sha256:fd74a679802a11fcc2b43138acdb49aede21aaf56f852acaff5032e718e92aec
size 219285
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:104762372501a72e46b0c277f803208588c6da3cb3cf2693aacf34673ce0dee1
size 237256
oid sha256:a3e7c958e56948c7e69c1b5d7f1fbf4ad6c970494e717ea8e67846f383cc2b0c
size 225672
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:64e4e2e86430c86983f21b71d0ec153ee2f7ee9edd15f4865c3f5f55b7dd285f
size 219197
oid sha256:3261ec264fccba89811841b001e912520ffe86eca1517e4a62bdad97f371ea70
size 196194
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d08b47a4b95215b19063f50561cf6d9ed70c2ee3d811896b32f18fb5eaa715c3
size 136471
oid sha256:1d59d603e4b5ebf5f595473f0fd4141935bc05d440d4bdc83ab59abf9f330ef9
size 137894
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3def893c8b3107f5382c260d5643f2229e23a738b73c94ec4f55d63e1353ca1b
size 117552
oid sha256:7cf8afe28c88f19e2eeeacabcdda97dce7508aa4ca86cf1c97cbffaadbafb06c
size 120558
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:51c9ca1cd193dd47c68c75adfc1d0e0f58c1f95b2ab55135f4cc7d815ecd3c0e
size 136285
oid sha256:41361e31931f7b2148193157c3b215e255d37a52829d86b71a81d4bef8bc853e
size 137234
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c5e5a6fec62819bf6f74d4beab057fe78de8f3205f87a108e3593f235bc55948
size 120091
oid sha256:7ff0c839e01431065ce8b6d360d1eb6eaf0bed9711cc65f37dcbee9354d8faf6
size 118430
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ef8a8fe4a9ae1bdd437a2618574962d1c57175e1893131293d6fa13f9a9dd1c1
size 522278
oid sha256:faec25c167db1f2e972af19945822ad6700b0d049bad575e996c1ff19e87a21c
size 527282
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dc43c5d83a75af17a28421ffed097f67d1ffc026b8d0d36c5cae1f4a1c012336
size 137190
oid sha256:4cd0a9237375a40dfb0103869c8c3613e4641f69bbdb66541c180614af4dd9f6
size 147939
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7de304c4383db02fde81ede2169907a2394c27dd49a1238fb91626f110d44b3f
size 137714
oid sha256:7a505964d2db2ca2aa32e6ffc4534fdd46150bd857fd25986863fe3ee9f6f72d
size 148426
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2f2e6eb2365ccd1c1a0ace52872a8e84f191736dd7b74c7898256c420e30fe73
size 90556
oid sha256:5af45df522a03e9d181e645ff1c02fc2de8a5c85e4adc630a627e737e1a73b81
size 92003
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:232a667d2fd91bd1746509b186db7eb9676a4732110a84e3c9543808abf1d2f5
size 82817
oid sha256:cf607207533190eec6d025525d94af8c9531132c1459eb8b5ca98e899ea22108
size 84390
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:968b785eb407bd7ef81eae302508f8e81166e2ab3a3c5226d6886840ee2cefbb
size 145486
oid sha256:2769d12441a0c98f7ef2030ed3a60615fa77d86b8281bb2543b3b546332eab4c
size 92958
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6742804364719e4bb519d0f04a07ae6b7f9f06cf3e9ae994aef0e184d40cbcda
size 167718
oid sha256:7c7a5833443554ba94d968d95f636566fa7c5730de97af9f006c6281977a9ba8
size 115013
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:aba080e7925f3d3cc9593509e6ba183b9e31a8a7ec7da4d7d574e35f192678a1
size 140952
oid sha256:2d3881b83f72809c29c370c6da8cc37bc496869e3dc976a2d8cf775f512242fe
size 140966
@@ -77,8 +77,8 @@ class RoomFlowCoordinatorTests: XCTestCase {
func testNoOp() async {
await process(route: .roomDetails(roomID: "1"), expectedAction: .presentedRoom("1"))
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
await process(route: .roomDetails(roomID: "1"), expectedAction: nil)
roomFlowCoordinator.handleAppRoute(.roomDetails(roomID: "1"), animated: true)
await Task.yield()
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
}
@@ -94,7 +94,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
await process(route: .room(roomID: "1"), expectedAction: .presentedRoom("1"))
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
await process(route: .roomDetails(roomID: "1"), expectedAction: nil)
await process(route: .roomDetails(roomID: "1"), expectedAction: .presentedRoom("1"))
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 1)
XCTAssert(navigationStackCoordinator.stackCoordinators.first is RoomDetailsScreenCoordinator)
@@ -104,22 +104,25 @@ class RoomFlowCoordinatorTests: XCTestCase {
await process(route: .roomDetails(roomID: "1"), expectedAction: .presentedRoom("1"))
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
await process(route: .room(roomID: "1"), expectedAction: .presentedRoom("1"))
await process(route: .room(roomID: "1"), expectedActions: [.dismissedRoom, .presentedRoom("1")])
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
}
// MARK: - Private
func process(route: AppRoute, expectedAction: RoomFlowCoordinatorAction?) async {
let routeTask = Task.detached(priority: .low) {
try? await Task.sleep(for: .seconds(0.2))
await self.roomFlowCoordinator.handleAppRoute(route, animated: true)
private func process(route: AppRoute, expectedAction: RoomFlowCoordinatorAction) async {
await process(route: route, expectedActions: [expectedAction])
}
private func process(route: AppRoute, expectedActions: [RoomFlowCoordinatorAction]) async {
Task {
await Task.yield()
self.roomFlowCoordinator.handleAppRoute(route, animated: true)
}
if let expectedAction {
_ = await roomFlowCoordinator.actions.values.first(where: { $0 == expectedAction })
} else {
await routeTask.value
if !expectedActions.isEmpty {
let actions = await roomFlowCoordinator.actions.collect(expectedActions.count).values.first()
XCTAssertEqual(actions, expectedActions)
}
}
}