diff --git a/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Models/ErrorDetail.swift b/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Models/ErrorDetail.swift index 6e0866a8..d7e5dbb0 100644 --- a/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Models/ErrorDetail.swift +++ b/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Models/ErrorDetail.swift @@ -82,9 +82,9 @@ public struct ErrorDetail: Sendable { let errors = causes["errors"] as? [NSError], let firstError = errors.first, let subCauses = firstError.userInfo["causes"] as? [String: Any], - let ex = subCauses["exceptions"] as? [Any], !ex.isEmpty { + let exceptions = subCauses["exceptions"] as? [Any], !exceptions.isEmpty { - dict["exceptions"] = ex.map { "\($0)" } + dict["exceptions"] = exceptions.map { "\($0)" } } return dict diff --git a/RIADigiDoc/Supporting files/Localizable.xcstrings b/RIADigiDoc/Supporting files/Localizable.xcstrings index 5f940811..fab0d8d8 100644 --- a/RIADigiDoc/Supporting files/Localizable.xcstrings +++ b/RIADigiDoc/Supporting files/Localizable.xcstrings @@ -5088,6 +5088,24 @@ } } }, + "PIN blocked unblock message" : { + "comment" : "My eID PIN unblock instructions error message", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unblock to use the PIN again." + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tühista blokeering, et PIN-i taas kasutada." + } + } + } + }, "PIN change failed" : { "comment" : "OperationUnblockPin error", "extractionState" : "manual", @@ -5466,6 +5484,42 @@ } } }, + "PUK blocked Thales" : { + "comment" : "My eID PUK blocked Thales message", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "PUK has been blocked because PUK code has been entered incorrectly 3 times. You can not unblock the PUK code yourself.\n\nAs long as the PUK code is blocked, all eID options can be used, except transactions that need PUK code.\n\nA new document must be requested to receive the new PUK code." + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "PUK on blokeeritud, kuna PUK-koodi on sisestatud 3 korda valesti. PUK-koodi ei saa ise lahti blokeerida.\n\nKuigi PUK-kood on blokeeritud, saab kõiki eID võimalusi kasutada, välja arvatud PUK-koodi vajavaid.\n\nUue PUK-koodi saamiseks tuleb taotleda uus dokument." + } + } + } + }, + "PUK blocked Thales URL" : { + "comment" : "My eID PUK blocked Thales URL", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "https://www.id.ee/en/article/changing-id-card-pin-codes-and-puk-code/" + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "https://www.id.ee/artikkel/id-kaardi-pin-ja-puk-koodide-muutmine/" + } + } + } + }, "PUK blocked URL" : { "comment" : "My eID PUK blocked URL", "extractionState" : "manual", diff --git a/RIADigiDoc/UI/Component/Container/Signing/IdCard/IdCardView.swift b/RIADigiDoc/UI/Component/Container/Signing/IdCard/IdCardView.swift index f3a0098f..4940ad6d 100644 --- a/RIADigiDoc/UI/Component/Container/Signing/IdCard/IdCardView.swift +++ b/RIADigiDoc/UI/Component/Container/Signing/IdCard/IdCardView.swift @@ -265,7 +265,9 @@ struct IdCardView: View { .onDisappear { pinNumber.removeAll() Task { - await viewModel.stopDiscoveringReaders() + await MainActor.run { + viewModel.resetErrors() + } } } } diff --git a/RIADigiDoc/UI/Component/DiagnosticsView/PrimaryOutlinedButton.swift b/RIADigiDoc/UI/Component/DiagnosticsView/PrimaryOutlinedButton.swift index a070d41c..179f8312 100644 --- a/RIADigiDoc/UI/Component/DiagnosticsView/PrimaryOutlinedButton.swift +++ b/RIADigiDoc/UI/Component/DiagnosticsView/PrimaryOutlinedButton.swift @@ -62,7 +62,6 @@ struct PrimaryOutlinedButton: View { .padding(Dimensions.Padding.XSPadding) } .frame(maxWidth: .infinity) - .padding(.vertical, Dimensions.Padding.XSPadding) .background( Capsule() .fill(isButtonEnabled ? theme.surface : Color.gray) diff --git a/RIADigiDoc/UI/Component/My eID/MyEidPinChangeView.swift b/RIADigiDoc/UI/Component/My eID/MyEidPinChangeView.swift index 8b7cd3db..9a886d72 100644 --- a/RIADigiDoc/UI/Component/My eID/MyEidPinChangeView.swift +++ b/RIADigiDoc/UI/Component/My eID/MyEidPinChangeView.swift @@ -143,13 +143,15 @@ struct MyEidPinChangeView: View { let pin = Array(viewModel.input.utf8) - if !pin.isEmpty && viewModel - .isPINLengthValid(pin: pin) && viewModel.step == .new { - viewModel.verifyNewCode() + let currentCodeType = (pinAction == .unblock && viewModel.step == .current) ? .puk : codeType + + if !pin.isEmpty && viewModel.isPINLengthValid(for: currentCodeType, pin: pin) && + viewModel.step == .new { + viewModel.verifyNewCode() } return !inputErrorMessage.isEmpty || - !viewModel.isPINLengthValid(pin: pin) + !viewModel.isPINLengthValid(for: currentCodeType, pin: pin) } init( diff --git a/RIADigiDoc/UI/Component/My eID/MyEidPinsAndCertificatesView.swift b/RIADigiDoc/UI/Component/My eID/MyEidPinsAndCertificatesView.swift index d572659e..b357e14c 100644 --- a/RIADigiDoc/UI/Component/My eID/MyEidPinsAndCertificatesView.swift +++ b/RIADigiDoc/UI/Component/My eID/MyEidPinsAndCertificatesView.swift @@ -34,8 +34,29 @@ struct MyEidPinsAndCertificatesView: View { var signCertValidTo: String var isPUKChangeable: Bool + private var pukBlockedMessage: String { + languageSettings.localized( + isPUKChangeable ? "PUK blocked" : "PUK blocked Thales" + ) + } + private var pukBlockedUrl: String { - languageSettings.localized("PUK blocked URL") + languageSettings.localized( + isPUKChangeable ? "PUK blocked URL" : "PUK blocked Thales URL" + ) + } + + private var pinBlockedMessage: String { + let pinBlockedText = languageSettings.localized( + "PIN blocked", + [CodeType.pin1.name] + ) + + if isPukBlocked { + return pinBlockedText + } + + return "\(pinBlockedText) \(languageSettings.localized("PIN blocked unblock message", []))" } init( @@ -79,12 +100,10 @@ struct MyEidPinsAndCertificatesView: View { .opacity(opacityForPin1BlockedState) if isPin1Blocked { - Text(verbatim: languageSettings.localized( - "PIN blocked", [CodeType.pin1.name]) - ) - .font(typography.bodySmall) - .foregroundStyle(theme.error) - .padding(.vertical, Dimensions.Padding.XSPadding) + Text(verbatim: pinBlockedMessage) + .font(typography.bodySmall) + .foregroundStyle(theme.error) + .padding(.vertical, Dimensions.Padding.XSPadding) } } .padding(.vertical, Dimensions.Padding.SPadding) @@ -98,7 +117,7 @@ struct MyEidPinsAndCertificatesView: View { [signCertValidTo] ) ), - forgotPinText: isPin1Blocked ? + forgotPinText: isPin2Blocked ? languageSettings.localized("Unblock PIN", [CodeType.pin2.name]) : languageSettings.localized("Forgot PIN", [CodeType.pin2.name]), changePinText: languageSettings @@ -115,12 +134,10 @@ struct MyEidPinsAndCertificatesView: View { .opacity(opacityForPin2BlockedState) if isPin2Blocked { - Text(verbatim: languageSettings.localized( - "PIN blocked", [CodeType.pin2.name]) - ) - .font(typography.bodySmall) - .foregroundStyle(theme.error) - .padding(.vertical, Dimensions.Padding.XSPadding) + Text(verbatim: pinBlockedMessage) + .font(typography.bodySmall) + .foregroundStyle(theme.error) + .padding(.vertical, Dimensions.Padding.XSPadding) } } .padding(.bottom, Dimensions.Padding.SPadding) @@ -139,7 +156,7 @@ struct MyEidPinsAndCertificatesView: View { if isPukBlocked { VStack(alignment: .leading) { - Text(verbatim: languageSettings.localized("PUK blocked")) + Text(verbatim: pukBlockedMessage) .font(typography.bodySmall) .foregroundStyle(theme.error) .padding(.vertical, Dimensions.Padding.XSPadding) diff --git a/RIADigiDoc/UI/Component/My eID/MyEidView.swift b/RIADigiDoc/UI/Component/My eID/MyEidView.swift index 9c3c7346..a0b5f1ba 100644 --- a/RIADigiDoc/UI/Component/My eID/MyEidView.swift +++ b/RIADigiDoc/UI/Component/My eID/MyEidView.swift @@ -82,9 +82,9 @@ struct MyEidView: View { _viewModel = State(wrappedValue: Container.shared.myEidViewModel()) self.idCardData = idCardData - self._isPin1Blocked = State(initialValue: idCardData.retryCount.pin1 == 0) - self._isPin2Blocked = State(initialValue: idCardData.retryCount.pin2 == 0) - self._isPukBlocked = State(initialValue: idCardData.retryCount.puk == 0) + viewModel.setIsPinBlocked(.pin1, isBlocked: idCardData.retryCount.pin1 == 0) + viewModel.setIsPinBlocked(.pin2, isBlocked: idCardData.retryCount.pin2 == 0) + viewModel.setIsPinBlocked(.puk, isBlocked: idCardData.retryCount.puk == 0) } var body: some View { @@ -204,7 +204,7 @@ struct MyEidView: View { if viewModel.usbReaderStatus != .sCardConnected { await viewModel.stopDiscoveringReaders() await MainActor.run { - dismiss() + pathManager.replaceLast(to: .myEidRootView) } } } diff --git a/RIADigiDoc/UI/Component/Shared/Extension/KeyboardExtensions.swift b/RIADigiDoc/UI/Component/Shared/Extension/KeyboardExtensions.swift new file mode 100644 index 00000000..644f09e4 --- /dev/null +++ b/RIADigiDoc/UI/Component/Shared/Extension/KeyboardExtensions.swift @@ -0,0 +1,31 @@ +/* + * Copyright 2017 - 2025 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +import SwiftUI + +extension UIKeyboardType { + var needsDoneButton: Bool { + switch self { + case .numberPad, .decimalPad, .phonePad, .asciiCapableNumberPad: + return true + default: + return false + } + } +} diff --git a/RIADigiDoc/UI/Component/Shared/FloatingLabelTextField.swift b/RIADigiDoc/UI/Component/Shared/FloatingLabelTextField.swift index 0f31fa80..1726b7da 100644 --- a/RIADigiDoc/UI/Component/Shared/FloatingLabelTextField.swift +++ b/RIADigiDoc/UI/Component/Shared/FloatingLabelTextField.swift @@ -21,6 +21,7 @@ import SwiftUI import FactoryKit struct FloatingLabelTextField: View { + @Environment(\.accessibilityVoiceOverEnabled) private var voiceOverEnabled @Environment(\.sizeCategory) private var sizeCategory @AppTheme private var theme @AppTypography private var typography @@ -285,14 +286,16 @@ struct FloatingLabelTextField: View { ) } - Button( - action: { - fieldIsFocused = false - isAccessibilityFocused = true - onDone() - }, - label: { Text(verbatim: languageSettings.localized("Done")) } - ) + if keyboardType.needsDoneButton { + Button( + action: { + fieldIsFocused = false + isAccessibilityFocused = true + onDone() + }, + label: { Text(verbatim: languageSettings.localized("Done")) } + ) + } } } } @@ -326,14 +329,16 @@ struct FloatingLabelTextField: View { ) } - Button( - action: { - fieldIsFocused = false - isAccessibilityFocused = true - onDone() - }, - label: { Text(verbatim: languageSettings.localized("Done")) } - ) + if keyboardType.needsDoneButton { + Button( + action: { + fieldIsFocused = false + isAccessibilityFocused = true + onDone() + }, + label: { Text(verbatim: languageSettings.localized("Done")) } + ) + } } } } diff --git a/RIADigiDoc/ViewModel/FileOpeningViewModel.swift b/RIADigiDoc/ViewModel/FileOpeningViewModel.swift index 2233eca2..92d83af8 100644 --- a/RIADigiDoc/ViewModel/FileOpeningViewModel.swift +++ b/RIADigiDoc/ViewModel/FileOpeningViewModel.swift @@ -183,11 +183,20 @@ class FileOpeningViewModel: FileOpeningViewModelProtocol, Loggable { case .containerCreationFailed(let errorDetail), .containerOpeningFailed(let errorDetail), .containerSavingFailed(let errorDetail): - return ToastMessage(key: "Failed to open container", args: [errorDetail.userInfo["fileName"] as? String ?? ""]) + return ToastMessage( + key: "Failed to open container", + args: [errorDetail.userInfo["fileName"] as? String ?? ""] + ) case .addingFilesToContainerFailed(let errorDetail): - return ToastMessage(key: "Failed to open file", args: [errorDetail.userInfo["fileName"] as? String ?? ""]) + return ToastMessage( + key: "Failed to open file", + args: [errorDetail.userInfo["fileName"] as? String ?? ""] + ) case .containerDataFileSavingFailed(let errorDetail): - return ToastMessage(key: "Failed to save file", args: [errorDetail.userInfo["fileName"] as? String ?? ""]) + return ToastMessage( + key: "Failed to save file", + args: [errorDetail.userInfo["fileName"] as? String ?? ""] + ) case .alreadyInitialized: return ToastMessage(key: "Libdigidocpp is already initialized") default: diff --git a/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift b/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift index 48408c08..ea526017 100644 --- a/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift +++ b/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift @@ -143,7 +143,7 @@ final class MyEidPinChangeViewModel: MyEidPinChangeViewModelProtocol, Loggable { resetInputError() } - func isPINLengthValid(pin: [UInt8]) -> Bool { + func isPINLengthValid(for codeType: CodeType, pin: [UInt8]) -> Bool { guard codeType.validLength.contains(pin.count) else { return false } @@ -268,7 +268,9 @@ final class MyEidPinChangeViewModel: MyEidPinChangeViewModelProtocol, Loggable { sharedMyEidSession.setIsPinBlocked(codeType, isBlocked: true) case .wrongPIN(let remaining): errorMessage = remaining > 1 ? "PIN verification error multiple" : "PIN verification error one" - errorMessageExtraArguments = [codeType.name, String(remaining)] + errorMessageExtraArguments = [ + pinAction == .change ? codeType.name : CodeType.puk.name, String(remaining) + ] resetToCurrentPinEntryStep() default: resetInputError() diff --git a/RIADigiDoc/ViewModel/MyEid/MyEidViewModel.swift b/RIADigiDoc/ViewModel/MyEid/MyEidViewModel.swift index 334042ff..885a4412 100644 --- a/RIADigiDoc/ViewModel/MyEid/MyEidViewModel.swift +++ b/RIADigiDoc/ViewModel/MyEid/MyEidViewModel.swift @@ -62,6 +62,10 @@ class MyEidViewModel: MyEidViewModelProtocol, Loggable { .date } + public func setIsPinBlocked(_ codeType: CodeType, isBlocked: Bool) { + sharedMyEidSession.setIsPinBlocked(codeType, isBlocked: isBlocked) + } + public func getIsPinBlocked(for codeType: CodeType) -> Bool { return sharedMyEidSession.getIsPinBlocked(for: codeType) } diff --git a/RIADigiDoc/ViewModel/Protocols/MyEid/MyEidPinChangeViewModelProtocol.swift b/RIADigiDoc/ViewModel/Protocols/MyEid/MyEidPinChangeViewModelProtocol.swift index 80ee4162..84235597 100644 --- a/RIADigiDoc/ViewModel/Protocols/MyEid/MyEidPinChangeViewModelProtocol.swift +++ b/RIADigiDoc/ViewModel/Protocols/MyEid/MyEidPinChangeViewModelProtocol.swift @@ -18,6 +18,7 @@ */ import Foundation +import IdCardLib @MainActor protocol MyEidPinChangeViewModelProtocol: Sendable { @@ -34,5 +35,5 @@ protocol MyEidPinChangeViewModelProtocol: Sendable { func verifyNewCode() func verifyRepeatedCode() -> Bool - func isPINLengthValid(pin: [UInt8]) -> Bool + func isPINLengthValid(for codeType: CodeType, pin: [UInt8]) -> Bool } diff --git a/RIADigiDoc/ViewModel/Protocols/MyEid/MyEidViewModelProtocol.swift b/RIADigiDoc/ViewModel/Protocols/MyEid/MyEidViewModelProtocol.swift index f7273170..9f7d6bff 100644 --- a/RIADigiDoc/ViewModel/Protocols/MyEid/MyEidViewModelProtocol.swift +++ b/RIADigiDoc/ViewModel/Protocols/MyEid/MyEidViewModelProtocol.swift @@ -26,6 +26,7 @@ public protocol MyEidViewModelProtocol: Sendable { func parseDateOfBirth(personalCode: String) -> String func parseExpiryDate(expiryDate: String) -> String func getDocumentExpirationStatus(expiryDate: String) -> MyEidDocumentStatus + func setIsPinBlocked(_ codeType: CodeType, isBlocked: Bool) func getIsPinBlocked(for codeType: CodeType) -> Bool func stopDiscoveringReaders() async }