* 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
160 lines
7.3 KiB
Swift
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")
|
|
}
|
|
}
|