Skip to content

Commit aafa8dd

Browse files
committed
Add support for accessibility signature focus
1 parent 5d076d1 commit aafa8dd

10 files changed

Lines changed: 143 additions & 94 deletions

File tree

RIADigiDoc/UI/Component/Container/Signing/IdCard/IdCardActionView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ struct IdCardActionView: View {
6464
isIdCardActionMessageFocused = true
6565
}
6666
}
67+
.accessibilityElement(children: .combine)
6768
}
6869
}
6970

RIADigiDoc/UI/Component/Container/Signing/IdCard/IdCardInputView.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ struct IdCardInputView: View {
2828
@AppTheme private var theme
2929
@AppTypography private var typography
3030

31+
@AccessibilityFocusState private var isActionMessageFocused: Bool
32+
3133
let personIdentifier: String
3234
@Binding var pinNumber: String
3335
@Binding var pinError: String
@@ -73,6 +75,7 @@ struct IdCardInputView: View {
7375
Text(verbatim: actionMessage)
7476
.font(typography.labelLarge)
7577
.foregroundStyle(theme.onSurfaceVariant)
78+
.accessibilityFocused($isActionMessageFocused)
7679

7780
Text(verbatim: personIdentifier)
7881
.font(typography.bodyLarge)
@@ -97,6 +100,11 @@ struct IdCardInputView: View {
97100
.foregroundStyle(theme.error)
98101
}
99102
}
103+
.onAppear {
104+
DispatchQueue.main.async {
105+
isActionMessageFocused = true
106+
}
107+
}
100108
}
101109
}
102110

RIADigiDoc/UI/Component/Container/Signing/IdCard/IdCardView.swift

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,6 @@ struct IdCardView: View {
205205
await viewModel.stopDiscoveringReaders()
206206
cancelIdCardAction()
207207
resetIdCardAction()
208-
isInProgress = false
209-
isShowingPinView = false
210-
isShowingLoadingView = false
211208

212209
onSuccessDecrypt(container)
213210
dismiss()
@@ -239,6 +236,7 @@ struct IdCardView: View {
239236
icon: "ic_m3_smart_card_reader_48pt_wght400",
240237
message: $idCardActionMessage
241238
)
239+
.accessibilityHidden(!isInProgress)
242240
} else if isInProgress && isShowingPinView {
243241
IdCardInputView(
244242
personIdentifier: personIdentifier,
@@ -385,6 +383,10 @@ struct IdCardView: View {
385383
}
386384
}
387385
.onDisappear {
386+
isInProgress = false
387+
isShowingPinView = false
388+
isShowingLoadingView = false
389+
idCardActionMessage = ""
388390
pinNumber.removeAll()
389391
cancelIdCardAction()
390392
}
@@ -491,17 +493,19 @@ struct IdCardView: View {
491493

492494
await viewModel.stopDiscoveringReaders()
493495
cancelIdCardAction()
494-
isInProgress = false
495-
isShowingPinView = false
496-
isShowingLoadingView = false
497496

498-
Toast.show(signatureAddedMessage, type: .success)
499-
if voiceOverEnabled {
500-
AccessibilityUtil.announceMessage(signatureAddedMessage)
501-
}
497+
await MainActor.run {
498+
idCardActionMessage = ""
502499

503-
onSuccess(container)
504-
dismiss()
500+
Toast.show(signatureAddedMessage, type: .success)
501+
502+
if voiceOverEnabled {
503+
AccessibilityUtil.announceMessage(signatureAddedMessage)
504+
}
505+
506+
onSuccess(container)
507+
dismiss()
508+
}
505509
}
506510
}
507511
}

RIADigiDoc/UI/Component/Container/Signing/NFC/NFCInputView.swift

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import FactoryKit
2222
import IdCardLib
2323

2424
struct NFCInputView: View {
25+
@Environment(\.accessibilityVoiceOverEnabled) private var voiceOverEnabled
2526
@Environment(LanguageSettings.self) private var languageSettings
2627

2728
@AppTheme private var theme
@@ -80,27 +81,14 @@ struct NFCInputView: View {
8081
var body: some View {
8182
VStack(alignment: .leading) {
8283
VStack(alignment: .leading, spacing: Dimensions.Padding.MPadding) {
83-
VStack(alignment: .leading, spacing: Dimensions.Padding.XSPadding) {
84-
FloatingLabelTextField(
85-
title: canNumberTitle,
86-
placeholder: canNumberTitle,
87-
text: $canNumber,
88-
isError: !(canNumberError?.isEmpty ?? true),
89-
errorText: canNumberError ?? "",
90-
keyboardType: .numberPad,
91-
sortPriority: 0
92-
)
93-
.onChange(of: canNumber) {
94-
onInputChange()
95-
}
96-
97-
Text(verbatim: canNumberLocationLabel)
98-
.font(typography.labelMedium)
99-
.foregroundStyle(theme.onSecondaryContainer)
100-
.padding(.top, Dimensions.Padding.XXSPadding)
101-
.accessibilitySortPriority(1)
84+
// Allow keyboard to focus on input fields
85+
// but have custom sort priority (contain to VStack) for these elements only with VoiceOver
86+
if voiceOverEnabled {
87+
nfcInputFields
88+
.accessibilityElement(children: .contain)
89+
} else {
90+
nfcInputFields
10291
}
103-
.accessibilityElement(children: .contain)
10492
}
10593
.padding(.bottom, Dimensions.Padding.MPadding)
10694

@@ -138,6 +126,29 @@ struct NFCInputView: View {
138126
}
139127
}
140128
}
129+
130+
private var nfcInputFields: some View {
131+
VStack(alignment: .leading, spacing: Dimensions.Padding.XSPadding) {
132+
FloatingLabelTextField(
133+
title: canNumberTitle,
134+
placeholder: canNumberTitle,
135+
text: $canNumber,
136+
isError: !(canNumberError?.isEmpty ?? true),
137+
errorText: canNumberError ?? "",
138+
keyboardType: .numberPad,
139+
sortPriority: 0
140+
)
141+
.onChange(of: canNumber) {
142+
onInputChange()
143+
}
144+
145+
Text(verbatim: canNumberLocationLabel)
146+
.font(typography.labelMedium)
147+
.foregroundStyle(theme.onSecondaryContainer)
148+
.padding(.top, Dimensions.Padding.XXSPadding)
149+
.accessibilitySortPriority(1)
150+
}
151+
}
141152
}
142153

143154
#Preview {

RIADigiDoc/UI/Component/Container/Signing/NFC/NFCView.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,10 @@ struct NFCView: View {
367367

368368
Toast.show(signatureAddedMessage, type: .success)
369369

370+
if voiceOverEnabled {
371+
AccessibilityUtil.announceMessage(signatureAddedMessage)
372+
}
373+
370374
onSuccess(container)
371375
dismiss()
372376
}

RIADigiDoc/UI/Component/Container/Signing/SignaturesListView.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,15 @@ struct SignaturesListView: View {
3131
@Binding var selectedSignature: SignatureWrapper?
3232
@Binding var containerMimetype: String
3333
var dataFilesCount: Int
34+
@Binding var focusedIndex: Int?
3435
var showRemoveSignatureButton: Bool
3536
@Binding var showRemoveSignatureModal: Bool
3637

3738
let nameUtil: NameUtilProtocol
3839
let signatureUtil: SignatureUtilProtocol
3940

41+
@AccessibilityFocusState private var focusedSignatureIndex: Int?
42+
4043
var body: some View {
4144
VStack {
4245
if #available(iOS 26.0, *) {
@@ -55,6 +58,7 @@ struct SignaturesListView: View {
5558
selectedSignature = timestamp
5659
}
5760
)
61+
.accessibilityFocused($focusedSignatureIndex, equals: index)
5862
}
5963
} else {
6064
ForEach(Array(timestamps.enumerated()), id: \.offset) { index, timestamp in
@@ -72,8 +76,10 @@ struct SignaturesListView: View {
7276
selectedSignature = timestamp
7377
}
7478
)
79+
.accessibilityFocused($focusedSignatureIndex, equals: index)
7580
}
7681
}
82+
7783
if #available(iOS 26.0, *) {
7884
ForEach(signatures.enumerated(), id: \.offset) { index, signature in
7985
SignatureView(
@@ -89,6 +95,13 @@ struct SignaturesListView: View {
8995
selectedSignature = signature
9096
}
9197
)
98+
.id(index)
99+
.accessibilityFocused($focusedSignatureIndex, equals: index)
100+
}
101+
.onChange(of: focusedIndex) { _, newValue in
102+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
103+
focusedSignatureIndex = newValue
104+
}
92105
}
93106
} else {
94107
ForEach(Array(signatures.enumerated()), id: \.offset) { index, signature in
@@ -105,6 +118,7 @@ struct SignaturesListView: View {
105118
selectedSignature = signature
106119
}
107120
)
121+
.accessibilityFocused($focusedSignatureIndex, equals: index)
108122
}
109123
}
110124
}
@@ -140,6 +154,7 @@ struct SignaturesListView: View {
140154
selectedSignature: .constant(signature),
141155
containerMimetype: .constant("application/vnd.etsi.asic-e+zip"),
142156
dataFilesCount: 1,
157+
focusedIndex: .constant(nil),
143158
showRemoveSignatureButton: true,
144159
showRemoveSignatureModal: .constant(false),
145160
nameUtil: Container.shared.nameUtil(),

RIADigiDoc/UI/Component/Container/Signing/SigningRootView.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ struct SigningRootView: View {
2929
@Environment(NavigationPathManager.self) private var pathManager
3030

3131
@State private var chosenMethod: ActionMethod = .idCardViaNFC
32+
@State private var isSuccess = false
3233

3334
@State private var viewModel: SigningRootViewModel
3435

@@ -58,6 +59,7 @@ struct SigningRootView: View {
5859
pinType: CodeType.pin2,
5960
signedContainer: container,
6061
onSuccess: { container in
62+
isSuccess = true
6163
sharedContainerViewModel.removeLastContainer()
6264
sharedContainerViewModel.setSignedContainer(container)
6365
sharedContainerViewModel.setIsSignatureAdded(true)
@@ -76,11 +78,13 @@ struct SigningRootView: View {
7678
],
7779
signedContainer: container,
7880
onSuccess: { container in
81+
isSuccess = true
7982
sharedContainerViewModel.removeLastContainer()
8083
sharedContainerViewModel.setSignedContainer(container)
8184
sharedContainerViewModel.setIsSignatureAdded(true)
8285
}
8386
)
87+
.accessibilityHidden(isSuccess)
8488
}
8589
case .mobileId:
8690
if let container = signedContainer as? SignedContainerProtocol {

RIADigiDoc/UI/Component/Container/Signing/SigningView.swift

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import UtilsLib
2525

2626
struct SigningView: View {
2727
@Environment(\.presentationMode) var presentationMode
28+
@Environment(\.accessibilityVoiceOverEnabled) private var voiceOverEnabled
2829
@AppTheme private var theme
2930
@AppTypography private var typography
3031
@Environment(LanguageSettings.self) private var languageSettings
@@ -56,7 +57,8 @@ struct SigningView: View {
5657

5758
@State private var showSivaMessage = false
5859

59-
@AccessibilityFocusState private var focusedField: AccessibilityField?
60+
@State private var scrollPosition: Int?
61+
@State private var focusedSignatureIndex: Int?
6062

6163
private var containerTitle: String {
6264
!isContainerSigned && !isNestedContainer ?
@@ -282,6 +284,7 @@ struct SigningView: View {
282284
selectedSignature: $selectedSignature,
283285
containerMimetype: $viewModel.containerMimetype,
284286
dataFilesCount: viewModel.dataFiles.count,
287+
focusedIndex: $focusedSignatureIndex,
285288
showRemoveSignatureButton: viewModel.isSignatureRemoveButtonShown(),
286289
showRemoveSignatureModal: $showRemoveSignatureModal,
287290
nameUtil: nameUtil,
@@ -329,6 +332,11 @@ struct SigningView: View {
329332
}
330333
}
331334
.padding(Dimensions.Padding.SPadding)
335+
.scrollPosition(id: $scrollPosition, anchor: .bottom)
336+
.onChange(of: viewModel.signatures.count) { previousCount, newCount in
337+
guard newCount > previousCount else { return }
338+
scrollToBottom()
339+
}
332340

333341
if isSignedContainer {
334342
if let containerFile = viewModel.containerURL {
@@ -386,8 +394,10 @@ struct SigningView: View {
386394
}
387395
}
388396
.onAppear {
389-
if viewModel.isSignatureAdded() {
390-
selectedTab = .signatures
397+
DispatchQueue.main.async {
398+
if viewModel.isSignatureAdded() {
399+
selectedTab = .signatures
400+
}
391401
}
392402

393403
containerLoadingTask = Task {
@@ -474,6 +484,18 @@ struct SigningView: View {
474484
}
475485
}
476486

487+
private func scrollToBottom() {
488+
DispatchQueue.main.async {
489+
guard let lastSignature = viewModel.signatures.indices.last else { return }
490+
491+
scrollPosition = lastSignature
492+
493+
DispatchQueue.main.async {
494+
focusedSignatureIndex = lastSignature
495+
}
496+
}
497+
}
498+
477499
private func updateSignAndEncryptButtonVisibility() async {
478500
let shouldShowSignButton = await viewModel
479501
.isSignButtonShown(

0 commit comments

Comments
 (0)