Files
element-x-ios/ElementX/Sources/Screens/EditRoomAddressScreen/View/EditRoomAddressScreen.swift
T
Mauro Romito ed58120fd3 Associate the room address field helper text and error with the field
The address field already had an accessibility label, but the section's
helper text and validation error were only shown in a separate footer and
weren't conveyed to VoiceOver when focusing the field. Compose them into the
field's accessibility hint.

Addresses #5258.
2026-06-23 13:06:36 +02:00

118 lines
5.4 KiB
Swift

//
// Copyright 2025 Element Creations Ltd.
// Copyright 2022-2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE in the repository root for full details.
//
import Compound
import SwiftUI
struct EditRoomAddressScreen: View {
@ObservedObject var context: EditRoomAddressScreenViewModel.Context
var body: some View {
Form {
Section {
EditRoomAddressListRow(aliasLocalPart: $context.desiredAliasLocalPart,
serverName: context.viewState.serverName,
errorDescription: context.viewState.aliasErrors.errorDescription,
footerText: L10n.screenEditRoomAddressRoomAddressSectionFooter)
.onChange(of: context.desiredAliasLocalPart) { _, newAliasLocalPart in
context.desiredAliasLocalPart = newAliasLocalPart.lowercased()
}
.onSubmit {
if context.viewState.canSave {
context.send(viewAction: .save)
}
}
} footer: {
VStack(alignment: .leading, spacing: 12) {
if let errorDescription = context.viewState.aliasErrors.errorDescription {
Label(errorDescription, icon: \.errorSolid, iconSize: .xSmall, relativeTo: .compound.bodySM)
.foregroundStyle(.compound.textCriticalPrimary)
.font(.compound.bodySM)
}
Text(L10n.screenEditRoomAddressRoomAddressSectionFooter)
.compoundListSectionFooter()
.font(.compound.bodySM)
}
}
}
.compoundList()
.navigationBarTitleDisplayMode(.inline)
.navigationTitle(L10n.screenEditRoomAddressTitle)
.toolbar { toolbar }
}
@ToolbarContentBuilder
var toolbar: some ToolbarContent {
ToolbarItem(placement: .confirmationAction) {
Button(L10n.actionSave) {
context.send(viewAction: .save)
}
.disabled(!context.viewState.canSave)
}
ToolbarItem(placement: .cancellationAction) {
Button(L10n.actionCancel) {
context.send(viewAction: .cancel)
}
}
}
}
// MARK: - Previews
struct EditRoomAddressScreen_Previews: PreviewProvider, TestablePreview {
static let noAliasviewModel = EditRoomAddressScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Room Name")),
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")),
userIndicatorController: UserIndicatorControllerMock())
static let aliasviewModel = EditRoomAddressScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Room Name", canonicalAlias: "#room-alias:matrix.org")),
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")),
userIndicatorController: UserIndicatorControllerMock())
static let invalidSymbolsViewModel = EditRoomAddressScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "Room Name", canonicalAlias: "#room#-alias:matrix.org")),
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")),
userIndicatorController: UserIndicatorControllerMock())
static let alreadyExistingViewModel = {
let clientProxy = ClientProxyMock(.init(userIDServerName: "matrix.org"))
clientProxy.isAliasAvailableReturnValue = .success(false)
return EditRoomAddressScreenViewModel(initialViewState: .init(serverName: "matrix.org",
bindings: .init(desiredAliasLocalPart: "whatever")),
roomProxy: JoinedRoomProxyMock(.init(name: "Room Name")),
clientProxy: clientProxy,
userIndicatorController: UserIndicatorControllerMock())
}()
static var previews: some View {
ElementNavigationStack {
EditRoomAddressScreen(context: noAliasviewModel.context)
}
.previewDisplayName("No alias")
ElementNavigationStack {
EditRoomAddressScreen(context: aliasviewModel.context)
}
.previewDisplayName("With alias")
ElementNavigationStack {
EditRoomAddressScreen(context: invalidSymbolsViewModel.context)
}
.snapshotPreferences(expect: invalidSymbolsViewModel.context.$viewState.map { state in
!state.aliasErrors.isEmpty
})
.previewDisplayName("Invalid symbols")
ElementNavigationStack {
EditRoomAddressScreen(context: alreadyExistingViewModel.context)
}
.snapshotPreferences(expect: alreadyExistingViewModel.context.$viewState.map { state in
!state.aliasErrors.isEmpty
})
.previewDisplayName("Already existing")
}
}