Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RIADigiDoc/RIADigiDocApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ struct RIADigiDocApp: App {
await dataStore.setIsRecentDocumentsMigrationDone(true)
}

await languageSettings.loadSelectedLanguage()
isInitialLanguageSelected = await dataStore.getIsInitialLanguageSelected()
await MainActor.run {
self.isSetupComplete = true
Expand Down
20 changes: 19 additions & 1 deletion RIADigiDoc/Supporting files/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -1843,7 +1843,7 @@
"et" : {
"stringUnit" : {
"state" : "translated",
"value" : "Adressaati eemaldamine ebaõnnestus"
"value" : "Adressaadi eemaldamine ebaõnnestus"
}
}
}
Expand Down Expand Up @@ -6220,6 +6220,24 @@
}
}
},
"Recipient added" : {
"comment" : "Toast message when recipient added in encryption view",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Recipient added"
}
},
"et" : {
"stringUnit" : {
"state" : "translated",
"value" : "Adressaat lisatud"
}
}
}
},
"Recipient already exists in the container" : {
"comment" : "Error shown in Encrypt Recipients view when adding recipient fails",
"extractionState" : "manual",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,12 @@ struct EncryptRecipientView: View {
VStack(alignment: .leading, spacing: Dimensions.Padding.ZeroPadding) {
if noSearchResults {
Text(verbatim: languageSettings.localized("Added recipients"))
.accessibilityHeading(.h2)
.accessibilityAddTraits([.isHeader])
} else {
Text(verbatim: languageSettings.localized("Recently added"))
.accessibilityHeading(.h2)
.accessibilityAddTraits([.isHeader])
}

Spacer().frame(height: Dimensions.Padding.MSPadding)
Expand Down Expand Up @@ -237,6 +241,49 @@ struct EncryptRecipientView: View {
}
}
.accessibilitySortPriority(filteredRecipients.isEmpty ? 2 : 0)

HStack {
Spacer()

Button(action: {
encryptionButtonEnabled = false
pathManager.replaceLast(to: .encryptView(isWithEncryption: true))
}, label: {
HStack(spacing: Dimensions.Padding.XSPadding) {
Image("ic_m3_encrypted_48pt_wght400")
.resizable()
.scaledToFit()
.frame(
width: Dimensions.Icon.IconSizeXXS,
height: Dimensions.Icon.IconSizeXXS
)
.foregroundStyle(theme.onPrimaryContainer)

Text(verbatim: encryptLabel)
.foregroundStyle(theme.onPrimaryContainer)
.font(typography.bodyLarge)
}
.accessibilityHidden(true)
})
.contentShape(Rectangle())
.disabled(!encryptionButtonEnabled)
.padding(Dimensions.Padding.MSPadding)
.background(
RoundedRectangle(cornerRadius: Dimensions.Corner.MSCornerRadius)
.fill(theme.primaryContainer)
.shadow(
color: theme.onSurfaceVariant.opacity(Dimensions.Shadow.SOpacity),
radius: Dimensions.Shadow.radius,
x: Dimensions.Shadow.xOffset,
y: Dimensions.Shadow.yOffset
)
)
.padding(Dimensions.Padding.MPadding)
.accessibilityElement(children: .ignore)
.accessibilityLabel(encryptLabel.lowercased())
.accessibilityAddTraits(.isButton)
.accessibilityIdentifier("bottomEncryptButton")
}
}
.padding(.horizontal, Dimensions.Padding.SPadding)
.accessibilityElement(children: .contain)
Expand Down Expand Up @@ -265,52 +312,6 @@ struct EncryptRecipientView: View {
)
}
}
.overlay(alignment: .bottom) {
HStack(spacing: Dimensions.Padding.XSPadding) {
if encryptionButtonEnabled {
Button(action: {
if encryptionButtonEnabled {
encryptionButtonEnabled = false
pathManager.replaceLast(to: .encryptView(isWithEncryption: true))
encryptionButtonEnabled = true
}
}, label: {
HStack(spacing: Dimensions.Padding.XSPadding) {
Image("ic_m3_encrypted_48pt_wght400")
.resizable()
.scaledToFit()
.frame(
width: Dimensions.Icon.IconSizeXXS,
height: Dimensions.Icon.IconSizeXXS
)
.foregroundStyle(theme.onPrimaryContainer)
.accessibilityHidden(true)

Text(verbatim: encryptLabel)
.foregroundStyle(theme.onPrimaryContainer)
.font(typography.bodyLarge)
.accessibilityHidden(true)
}
})
.accessibilityLabel(encryptLabel.lowercased())
.accessibilityAddTraits([.isButton])
.accessibilityIdentifier("bottomEncryptButton")
}
}
.padding(Dimensions.Padding.MSPadding)
.background(
RoundedRectangle(cornerRadius: Dimensions.Corner.MSCornerRadius)
.fill(theme.primaryContainer)
.shadow(
color: theme.onSurfaceVariant.opacity(Dimensions.Shadow.SOpacity),
radius: Dimensions.Shadow.radius,
x: Dimensions.Shadow.xOffset,
y: Dimensions.Shadow.yOffset
)
)
.frame(maxWidth: .infinity, alignment: .trailing)
.padding(Dimensions.Padding.MPadding)
}
.onAppear {
Task { @MainActor in
await viewModel.loadRecipients()
Expand All @@ -321,23 +322,37 @@ struct EncryptRecipientView: View {
showNoRecipientsFoundMessage = false
}
.onChange(of: viewModel.errorMessage) { _, error in
guard let errorMessage = error, !errorMessage.isEmpty else { return }
guard let error, !error.key.isEmpty else { return }

isTitleFocused = false

let localizedMessage = languageSettings.localized(errorMessage)
let localizedMessage = languageSettings.localized(error.key, [error.args.joined(separator: ", ")])
Toast.show(localizedMessage)

if voiceOverEnabled {
encryptionButtonEnabled = false
AccessibilityUtil.announceMessage(localizedMessage)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
isTitleFocused = true

}
}

viewModel.errorMessage = nil
encryptionButtonEnabled = true

viewModel.resetErrorMessage()
}
.onChange(of: viewModel.successMessage) { _, message in
guard let message, !message.key.isEmpty else { return }
let localizedMessage = languageSettings.localized(message.key, [message.args.joined(separator: ", ")])
Toast.show(localizedMessage, type: .success)

if voiceOverEnabled {
AccessibilityUtil.announceMessage(localizedMessage)
}

encryptionButtonEnabled = true

viewModel.resetSuccessMessage()
}
}
)
Expand Down
38 changes: 25 additions & 13 deletions RIADigiDoc/UI/Component/Shared/FloatingLabelTextField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ struct FloatingLabelTextField: View {
.joined(separator: " ")
}

private var shouldShowToolbar: Bool {
fieldIsFocused && (showDashButton || keyboardType.needsDoneButton)
}

// MARK: - Body

var body: some View {
Expand Down Expand Up @@ -361,30 +365,38 @@ struct FloatingLabelTextField: View {
@ToolbarContentBuilder
private var keyboardToolbar: some ToolbarContent {
ToolbarItem(placement: .keyboard) {
if fieldIsFocused {
if shouldShowToolbar {
HStack {
if showDashButton {
Button(
action: { text.append("-") },
label: { Text(verbatim: "-") }
)
dashButton
}

if keyboardType.needsDoneButton {
Button(
action: {
fieldIsFocused = false
isAccessibilityFocused = true
onDone()
},
label: { Text(verbatim: languageSettings.localized("Done")) }
)
doneButton
}
}
}
}
}

private var dashButton: some View {
Button(
action: { text.append("-") },
label: { Text(verbatim: "-") }
)
}

private var doneButton: some View {
Button(
action: {
fieldIsFocused = false
isAccessibilityFocused = true
onDone()
},
label: { Text(verbatim: languageSettings.localized("Done")) }
)
}

// MARK: - Icons

@ViewBuilder
Expand Down
20 changes: 11 additions & 9 deletions RIADigiDoc/Util/Language/LanguageSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public final class LanguageSettings: LanguageSettingsProtocol {
private(set) var selectedLanguage: String = DefaultValues.language
private let dataStore: DataStoreProtocol

private var localizedBundle = Bundle.main.path(forResource: "en", ofType: "lproj").flatMap(Bundle.init)

public let supportedLanguages: [SupportedLanguage] = [
SupportedLanguage(code: "et", titleKey: "Init lang locale et", accessibilityInputLabel: "Estonian"),
SupportedLanguage(code: "en", titleKey: "Init lang locale en", accessibilityInputLabel: "English")
Expand All @@ -34,30 +36,30 @@ public final class LanguageSettings: LanguageSettingsProtocol {
dataStore: DataStoreProtocol
) {
self.dataStore = dataStore
Task {
self.selectedLanguage = await dataStore.getSelectedLanguage()
}
}

// MARK: - Public Methods

public func loadSelectedLanguage() async {
self.selectedLanguage = await dataStore.getSelectedLanguage()
localizedBundle = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj").flatMap(Bundle.init)
}

public func getSelectedLanguage() -> String {
return selectedLanguage
}

public func setSelectedLanguage(newLanguageCode: String) async {
selectedLanguage = newLanguageCode
localizedBundle = Bundle.main.path(forResource: newLanguageCode, ofType: "lproj").flatMap(Bundle.init)
await dataStore.setSelectedLanguage(newLanguageCode: newLanguageCode)
}

public func localized(_ key: String, _ args: [CVarArg] = []) -> String {
guard let path = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj"),
let bundle = Bundle(path: path) else {
return key
}

let bundle = localizedBundle ??
Bundle.main.path(forResource: selectedLanguage, ofType: "lproj").flatMap(Bundle.init) ?? Bundle.main
let format = bundle.localizedString(forKey: key, value: nil, table: nil)
return String.localizedStringWithFormat(format, args)
return args.isEmpty ? format : String.localizedStringWithFormat(format, args)
}

// MARK: - Constants
Expand Down
1 change: 1 addition & 0 deletions RIADigiDoc/Util/Language/LanguageSettingsProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import Foundation
/// @mockable
@MainActor
public protocol LanguageSettingsProtocol: Sendable {
func loadSelectedLanguage() async
func getSelectedLanguage() -> String
func setSelectedLanguage(newLanguageCode: String) async
func localized(_ key: String, _ args: [CVarArg]) -> String
Expand Down
22 changes: 17 additions & 5 deletions RIADigiDoc/ViewModel/EncryptRecipientViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ class EncryptRecipientViewModel: EncryptRecipientViewModelProtocol, Loggable {
var isImporting = false
var recipients: [Addressee] = []
var searchText: String = ""
var errorMessage: String?
private(set) var successMessage: ToastMessage?
private(set) var errorMessage: ToastMessage?

private let sharedContainerViewModel: SharedContainerViewModelProtocol
private let openLdap: OpenLdapProtocol
Expand Down Expand Up @@ -64,24 +65,27 @@ class EncryptRecipientViewModel: EncryptRecipientViewModelProtocol, Loggable {
let recipients = await cryptoContainer.getRecipients()

for recipient in recipients where chosenRecipient.data == recipient.data {
errorMessage = "Recipient already exists in the container"
errorMessage = ToastMessage(key: "Recipient already exists in the container", args: [])
EncryptRecipientViewModel.logger().error("Recipient already exists in the container")
return
}

await cryptoContainer.addRecipients([chosenRecipient])

successMessage = ToastMessage(key: "Recipient added", args: [])
EncryptRecipientViewModel.logger().info("Recipient added")
}

func loadRecipients() async {
if !searchText.isEmpty {
let result = await openLdap.search(identityCode: searchText)
if result.tooManyResults {
recipients = []
errorMessage = "Too many results"
errorMessage = ToastMessage(key: "Too many results", args: [])
EncryptRecipientViewModel.logger().error("Too many results for \(self.searchText)")
} else if result.addressees.isEmpty {
recipients = []
errorMessage = "No recipients found"
errorMessage = ToastMessage(key: "Person or company does not own a valid certificate", args: [])
EncryptRecipientViewModel.logger().error("No recipients found for \(self.searchText)")
} else {
recipients = result.addressees
Expand Down Expand Up @@ -120,8 +124,16 @@ class EncryptRecipientViewModel: EncryptRecipientViewModelProtocol, Loggable {

try await cryptoContainer.removeRecipient(recipient)
} catch {
errorMessage = "Failed to remove recipient"
errorMessage = ToastMessage(key: "Failed to remove recipient", args: [])
EncryptRecipientViewModel.logger().error("Unable to delete recipient: \(error)")
}
}

func resetErrorMessage() {
errorMessage = nil
}

func resetSuccessMessage() {
successMessage = nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ public protocol EncryptRecipientViewModelProtocol: Sendable {
func loadRecipients() async
func getContainerRecipientList() async -> [Addressee]
func deleteRecipient(_ recipient: Addressee) async
func resetErrorMessage()
func resetSuccessMessage()
}
Loading