diff --git a/RIADigiDoc/Domain/Model/Error/NFC/DecryptError.swift b/RIADigiDoc/Domain/Model/Error/NFC/DecryptError.swift index 0a51743f..52a0df51 100644 --- a/RIADigiDoc/Domain/Model/Error/NFC/DecryptError.swift +++ b/RIADigiDoc/Domain/Model/Error/NFC/DecryptError.swift @@ -32,7 +32,7 @@ extension DecryptError: LocalizedError { case .containerFileInvalid: return "Container file is invalid" case .recipientsEmpty: - return "No recipients found in container" + return "Person or company does not own a valid certificate" case .cancelled: return "Operation cancelled by user" case .unknown(let error): diff --git a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift index 9a28d9f7..293b56bf 100644 --- a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift +++ b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift @@ -95,7 +95,7 @@ public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol { OperationDecrypt.logger().error("NFC: \(error.localizedDescription)") operationError = error session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "No recipients found") + "Person or company does not own a valid certificate") return } diff --git a/RIADigiDoc/Supporting files/Localizable.xcstrings b/RIADigiDoc/Supporting files/Localizable.xcstrings index c7bae94d..1036e220 100644 --- a/RIADigiDoc/Supporting files/Localizable.xcstrings +++ b/RIADigiDoc/Supporting files/Localizable.xcstrings @@ -5197,24 +5197,6 @@ } } }, - "No recipients found" : { - "comment" : "Text for when no recipients are found", - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "No recipients found" - } - }, - "et" : { - "stringUnit" : { - "state" : "translated", - "value" : "Adressaati ei leitud" - } - } - } - }, "Not a mobile-id client" : { "comment" : "Mobile-ID error when user is not found or not active", "extractionState" : "manual", @@ -5394,6 +5376,24 @@ } } }, + "Person or company does not own a valid certificate" : { + "comment" : "LDAP search message when no results found", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Person or company does not own a valid certificate. It is necessary to have a valid certificate for encryption" + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Isikul või asutusel puudub kehtiv sertifikaat. Krüpteerimiseks on vaja kehtivat sertifikaati" + } + } + } + }, "Personal code" : { "comment" : "Used in Mobile-ID and Smart-ID textfield views, My eID public data", "extractionState" : "manual", diff --git a/RIADigiDoc/UI/Component/Container/Crypto/Recipient/EncryptRecipientView.swift b/RIADigiDoc/UI/Component/Container/Crypto/Recipient/EncryptRecipientView.swift index 565d462e..846f2c64 100644 --- a/RIADigiDoc/UI/Component/Container/Crypto/Recipient/EncryptRecipientView.swift +++ b/RIADigiDoc/UI/Component/Container/Crypto/Recipient/EncryptRecipientView.swift @@ -37,6 +37,8 @@ struct EncryptRecipientView: View { @State private var encryptionButtonEnabled = true + @State private var showNoRecipientsFoundMessage = false + @State private var selectedRecipient: Addressee? @State private var showRemoveRecipientModal = false @@ -65,6 +67,57 @@ struct EncryptRecipientView: View { var encryptLabel: String { languageSettings.localized("Encrypt") } + + var noSearchResultsMessage: String { + languageSettings.localized("Person or company does not own a valid certificate") + } + + private var addedRecipientsSection: some View { + VStack(alignment: .leading, spacing: Dimensions.Padding.ZeroPadding) { + if noSearchResults { + Text(verbatim: languageSettings.localized("Added recipients")) + } else { + Text(verbatim: languageSettings.localized("Recently added")) + } + + Spacer().frame(height: Dimensions.Padding.MSPadding) + + if #available(iOS 26.0, *) { + ForEach(addedRecipients.enumerated(), id: \.offset) { index, item in + addedRecipientRow(index: index, item: item) + } + } else { + ForEach(Array(addedRecipients.enumerated()), id: \.offset) { index, item in + addedRecipientRow(index: index, item: item) + } + } + } + .padding(.horizontal, Dimensions.Padding.SPadding) + .listStyle(.plain) + .scrollDisabled(true) + .scrollContentBackground(.hidden) + .listRowSpacing(0) + .listSectionSpacing(.compact) + } + + private var filteredRecipientsSection: some View { + VStack { + if #available(iOS 26.0, *) { + ForEach(filteredRecipients.enumerated(), id: \.offset) { index, item in + recipientRow(index: index, item: item) + } + } else { + ForEach(Array(filteredRecipients.enumerated()), id: \.offset) { index, item in + recipientRow(index: index, item: item) + } + } + } + .listStyle(.plain) + .scrollDisabled(true) + .scrollContentBackground(.hidden) + .listRowSpacing(0) + .listSectionSpacing(.compact) + } var body: some View { TopBarContainer( @@ -114,6 +167,11 @@ struct EncryptRecipientView: View { Task { await viewModel.loadRecipients() + + if noRecipients { + showNoRecipientsFoundMessage = true + } + isSearchFocused = true } } @@ -160,64 +218,16 @@ struct EncryptRecipientView: View { .listStyle(.plain) .scrollDisabled(true) .scrollContentBackground(.hidden) - } else if noRecipients { - ContentUnavailableView { - Text(verbatim: languageSettings.localized("No recipients found")) - .font(typography.bodyLarge) - .foregroundStyle(theme.onSurfaceVariant) - } - .listRowSeparator(.hidden) + } else if showNoRecipientsFoundMessage { + emptyStateView(languageSettings.localized("Person or company does not own a valid certificate")) } else { - VStack { - if #available(iOS 26.0, *) { - ForEach(filteredRecipients.enumerated(), id: \.offset - ) { index, item in - recipientRow(index: index, item: item) - } - } else { - ForEach(Array(filteredRecipients.enumerated()), id: \.offset - ) { index, item in - recipientRow(index: index, item: item) - } - } - } - .listStyle(.plain) - .scrollDisabled(true) - .scrollContentBackground(.hidden) - .listRowSpacing(0) - .listSectionSpacing(.compact) + filteredRecipientsSection } Spacer().frame(height: Dimensions.Padding.MSPadding) if addedRecipients.count > 0 { - VStack(alignment: .leading, spacing: Dimensions.Padding.ZeroPadding) { - if noSearchResults { - Text(verbatim: languageSettings.localized("Added recipients")) - } else { - Text(verbatim: languageSettings.localized("Recently added")) - } - - Spacer().frame(height: Dimensions.Padding.MSPadding) - - if #available(iOS 26.0, *) { - ForEach(addedRecipients.enumerated(), id: \.offset - ) { index, item in - addedRecipientRow(index: index, item: item) - } - } else { - ForEach(Array(addedRecipients.enumerated()), id: \.offset - ) { index, item in - addedRecipientRow(index: index, item: item) - } - } - } - .padding(.horizontal, Dimensions.Padding.SPadding) - .listStyle(.plain) - .scrollDisabled(true) - .scrollContentBackground(.hidden) - .listRowSpacing(0) - .listSectionSpacing(.compact) + addedRecipientsSection } } } @@ -297,6 +307,9 @@ struct EncryptRecipientView: View { addedRecipients = await viewModel.filteredAddedRecipients() } } + .onChange(of: viewModel.searchText) { _, _ in + showNoRecipientsFoundMessage = false + } .onChange(of: viewModel.errorMessage) { _, error in guard !error.isEmpty else { return } Toast.show(languageSettings.localized(error)) @@ -306,7 +319,6 @@ struct EncryptRecipientView: View { ) } - @ViewBuilder private func recipientRow(index: Int, item: Addressee) -> some View { RecipientsView( recipient: item, @@ -329,7 +341,6 @@ struct EncryptRecipientView: View { .background(theme.surface) } - @ViewBuilder private func addedRecipientRow(index: Int, item: Addressee) -> some View { RecipientsView( recipient: item, @@ -354,6 +365,15 @@ struct EncryptRecipientView: View { .buttonStyle(.plain) .background(theme.surface) } + + private func emptyStateView(_ text: String) -> some View { + ContentUnavailableView { + Text(verbatim: text) + .font(typography.bodyLarge) + .foregroundStyle(theme.onSurfaceVariant) + } + .listRowSeparator(.hidden) + } } #Preview { diff --git a/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCInputView.swift b/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCInputView.swift index 115d0ebb..dc6a2c51 100644 --- a/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCInputView.swift +++ b/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCInputView.swift @@ -96,7 +96,7 @@ struct NFCInputView: View { Text(verbatim: canNumberLocationLabel) .font(typography.labelMedium) .foregroundStyle(theme.onSecondaryContainer) - .padding(.vertical, Dimensions.Padding.XXSPadding) + .padding(.bottom, Dimensions.Padding.MPadding) } } .padding(.vertical, Dimensions.Padding.ZeroPadding) diff --git a/RIADigiDoc/UI/Component/Toast/ToastQueue.swift b/RIADigiDoc/UI/Component/Toast/ToastQueue.swift index 147c3926..060b9fc7 100644 --- a/RIADigiDoc/UI/Component/Toast/ToastQueue.swift +++ b/RIADigiDoc/UI/Component/Toast/ToastQueue.swift @@ -24,8 +24,11 @@ actor ToastQueue { private var queue: [ToastItem] = [] private var isPresenting = false + private var currentMessage: String? = nil func enqueue(message: String, duration: TimeInterval, type: ToastType) { + // Avoid consecutive duplicate messages + if message == currentMessage || message == queue.last?.message { return } queue.append(ToastItem(message: message, duration: duration, type: type)) processQueueIfNeeded() } @@ -34,6 +37,7 @@ actor ToastQueue { guard !isPresenting, let next = queue.first else { return } isPresenting = true + currentMessage = next.message queue.removeFirst() Task { @@ -44,6 +48,7 @@ actor ToastQueue { private func toastDidFinish() { isPresenting = false + currentMessage = nil processQueueIfNeeded() } }