Files
letro-ios/ElementX/Sources/Screens/Spaces/SpaceSettingsScreen/View/SpaceSettingsScreen.swift
Mauro 56eec826df Fix A11y tests (#5104)
* replace NavigationStack with ElementNavigationStack to allow the content to be rendered without a NavigationStack in a11y tests

* fix a11y tests

* update xcodeproject

* swiftformat fix

* use iOS 26.1 for CI

* use a wrapper to solve the issue for a11y tests

* ElementNavigationStack only uses the trick in DEBUG mode, and added a swiftlint rule to prevent the usage of NavigationStack
2026-02-13 16:45:58 +01:00

160 lines
7.3 KiB
Swift

//
// Copyright 2025 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Compound
import SwiftUI
struct SpaceSettingsScreen: View {
@Bindable var context: RoomDetailsScreenViewModel.Context
var body: some View {
Form {
editSection
if context.viewState.canSeeSecurityAndPrivacy {
securitySection
}
peopleSection
leaveSpaceSection
}
.compoundList()
.navigationTitle(L10n.commonSettings)
.sheet(item: $context.leaveSpaceViewModel) { viewModel in
LeaveSpaceView(context: viewModel.context)
}
}
private var editSection: some View {
Section {
ListRow(kind: .custom { editRow })
.accessibilityIdentifier(A11yIdentifiers.spaceSettingsScreen.editBaseInfo)
}
}
@ViewBuilder
private var editRow: some View {
if context.viewState.canEditBaseInfo {
Button {
context.send(viewAction: .processTapEdit)
} label: {
editRowContent
}
} else {
editRowContent
}
}
private var editRowContent: some View {
HStack(spacing: 12) {
RoomAvatarImage(avatar: context.viewState.details.avatar,
avatarSize: .room(on: .spaceSettings),
mediaProvider: context.mediaProvider)
.accessibilityHidden(true)
VStack(alignment: .leading, spacing: 2) {
Text(context.viewState.details.name ?? context.viewState.details.id)
.lineLimit(1)
.font(.compound.headingMD)
.foregroundStyle(.compound.textPrimary)
if let alias = context.viewState.details.canonicalAlias {
Text(alias)
.lineLimit(1)
.font(.compound.bodySM)
.foregroundStyle(.compound.textSecondary)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
if context.viewState.canEditBaseInfo {
ListRowAccessory.navigationLink
}
}
.padding(.horizontal, ListRowPadding.horizontal)
.padding(.vertical, 16)
}
private var securitySection: some View {
Section {
ListRow(label: .default(title: L10n.screenSpaceSettingsSecurityAndPrivacy, icon: \.lock),
kind: .navigationLink {
context.send(viewAction: .processTapSecurityAndPrivacy)
})
}
}
private var peopleSection: some View {
Section {
if context.viewState.hasMemberIdentityVerificationStateViolations {
ListRow(label: .default(title: L10n.commonPeople, icon: \.user),
details: .icon(CompoundIcon(\.infoSolid).foregroundStyle(.compound.iconCriticalPrimary)),
kind: .navigationLink {
context.send(viewAction: .processTapPeople)
})
} else {
ListRow(label: .default(title: L10n.commonPeople, icon: \.user),
details: .title(String(context.viewState.joinedMembersCount)),
kind: .navigationLink {
context.send(viewAction: .processTapPeople)
})
}
if context.viewState.canEditRolesOrPermissions {
ListRow(label: .default(title: L10n.screenSpaceSettingsRolesAndPermissions, icon: \.admin),
kind: .navigationLink {
context.send(viewAction: .processTapRolesAndPermissions)
})
}
}
}
private var leaveSpaceSection: some View {
ListRow(label: .action(title: L10n.screenSpaceSettingsLeaveSpace,
icon: \.leave,
role: .destructive),
kind: .button { context.send(viewAction: .processTapLeave) })
}
}
// MARK: - Previews
struct SpaceSettingsScreen_Previews: PreviewProvider, TestablePreview {
static let ownerViewModel = RoomDetailsScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Space",
avatarURL: .mockMXCAvatar,
isSpace: true,
canonicalAlias: "#space:matrix.org",
members: .allMembersAsCreator)),
userSession: UserSessionMock(.init()),
analyticsService: ServiceLocator.shared.analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: ServiceLocator.shared.settings)
static let userViewModel = RoomDetailsScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Space",
avatarURL: .mockMXCAvatar,
isSpace: true,
canonicalAlias: "#space:matrix.org",
members: .allMembers)),
userSession: UserSessionMock(.init()),
analyticsService: ServiceLocator.shared.analytics,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()),
attributedStringBuilder: AttributedStringBuilder(mentionBuilder: MentionBuilder()),
appSettings: ServiceLocator.shared.settings)
static var previews: some View {
ElementNavigationStack {
SpaceSettingsScreen(context: ownerViewModel.context)
}
.previewDisplayName("Owner")
ElementNavigationStack {
SpaceSettingsScreen(context: userViewModel.context)
}
.previewDisplayName("User")
}
}