From a5bb815be16e1a53b83b0aa309539f72446226b2 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 13 Nov 2024 18:44:46 +0100 Subject: [PATCH 01/25] WIP Signed-off-by: Milen Pivchev --- Brand/NCBrand.swift | 4 + iOSClient/Main/NCMainTabBarController.swift | 16 ++++ iOSClient/Menu/NCMenu.swift | 4 +- iOSClient/NCGlobal.swift | 1 + iOSClient/SceneDelegate.swift | 12 +++ .../Settings/Settings/NCSettingsView.swift | 81 ++++++++++++------- 6 files changed, 86 insertions(+), 32 deletions(-) diff --git a/Brand/NCBrand.swift b/Brand/NCBrand.swift index f02ea986a7..487e8d6d26 100755 --- a/Brand/NCBrand.swift +++ b/Brand/NCBrand.swift @@ -72,6 +72,7 @@ let userAgent: String = { var disable_show_more_nextcloud_apps_in_settings: Bool = false var doNotAskPasscodeAtStartup: Bool = false var disable_source_code_in_settings: Bool = false + var enforce_protection = true // (name: "Name 1", url: "https://cloud.nextcloud.com"),(name: "Name 2", url: "https://cloud.nextcloud.com") var enforce_servers: [(name: String, url: String)] = [] @@ -117,6 +118,9 @@ let userAgent: String = { if let str = configurationManaged[NCGlobal.shared.configuration_disable_openin_file] as? String { disable_openin_file = (str as NSString).boolValue } + if let str = configurationManaged[NCGlobal.shared.configuration_enforce_protection] as? String { + enforce_protection = (str as NSString).boolValue + } } } diff --git a/iOSClient/Main/NCMainTabBarController.swift b/iOSClient/Main/NCMainTabBarController.swift index 0c8a0aad70..d9b25c4a57 100644 --- a/iOSClient/Main/NCMainTabBarController.swift +++ b/iOSClient/Main/NCMainTabBarController.swift @@ -22,6 +22,7 @@ // import UIKit +import SwiftUI struct NavigationCollectionViewCommon { var serverUrl: String @@ -50,11 +51,26 @@ class NCMainTabBarController: UITabBarController { if #available(iOS 17.0, *) { traitOverrides.horizontalSizeClass = .compact } + +// if NCBrandOptions.shared.enforce_protection && NCKeychain().passcode.isEmptyOrNil { +// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(false))) +//// vc.isModalInPresentation = true +//// isModalInPresentation = true +//// vc.modalPresentationStyle = .fullScreen +// present(vc, animated: true) +// } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) previousIndex = selectedIndex + + if NCBrandOptions.shared.enforce_protection && !NCKeychain().presentPasscode { + let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(false))) + vc.isModalInPresentation = true + + present(vc, animated: true) + } } @objc func changeTheming(_ notification: NSNotification) { diff --git a/iOSClient/Menu/NCMenu.swift b/iOSClient/Menu/NCMenu.swift index 699b5a7bdb..a9614dcc4c 100644 --- a/iOSClient/Menu/NCMenu.swift +++ b/iOSClient/Menu/NCMenu.swift @@ -134,11 +134,11 @@ class NCMenu: UITableViewController { extension NCMenu: FloatingPanelControllerDelegate { func floatingPanel(_ fpc: FloatingPanelController, layoutFor size: CGSize) -> FloatingPanelLayout { - return NCMenuFloatingPanelLayout(actionsHeight: self.actions.listHeight) + return NCMenuFloatingPanelLayout(actionsHeight: 600) } func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout { - return NCMenuFloatingPanelLayout(actionsHeight: self.actions.listHeight) + return NCMenuFloatingPanelLayout(actionsHeight: 600) } func floatingPanel(_ fpc: FloatingPanelController, animatorForDismissingWith velocity: CGVector) -> UIViewPropertyAnimator { diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index ab0f5beff8..aa7f69329c 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -379,6 +379,7 @@ class NCGlobal: NSObject { let configuration_disable_log = "disable_log" let configuration_disable_more_external_site = "disable_more_external_site" let configuration_disable_openin_file = "disable_openin_file" + let configuration_enforce_protection = "enforce_protection" // MORE NEXTCLOUD APPS // diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index 7414a4facd..6e8b7d9d90 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -26,6 +26,7 @@ import UIKit import NextcloudKit import WidgetKit import SwiftEntryKit +import SwiftUI class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? @@ -64,6 +65,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window?.makeKeyAndVisible() /// Set the ACCOUNT controller.account = activeTableAccount.account + +// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(true))) +// controller.present(vc, animated: true) } } else { NCKeychain().removeAll() @@ -77,6 +81,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let navigationController = NCLoginNavigationController(rootViewController: viewController) window?.rootViewController = navigationController window?.makeKeyAndVisible() + + +// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(true))) +// controller.present(vc, animated: true) } } } @@ -101,6 +109,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { hidePrivacyProtectionWindow() if let window = SceneManager.shared.getWindow(scene: scene), let controller = SceneManager.shared.getController(scene: scene) { window.rootViewController = controller +// if NCBrandOptions.shared.enforce_protection && NCKeychain().passcode.isEmptyOrNil { +// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(true))) +// controller.present(vc, animated: true) +// } else if NCKeychain().presentPasscode { NCPasscode.shared.presentPasscode(viewController: controller, delegate: self) { NCPasscode.shared.enableTouchFaceID() diff --git a/iOSClient/Settings/Settings/NCSettingsView.swift b/iOSClient/Settings/Settings/NCSettingsView.swift index 75fc02d567..600350b1c7 100644 --- a/iOSClient/Settings/Settings/NCSettingsView.swift +++ b/iOSClient/Settings/Settings/NCSettingsView.swift @@ -63,6 +63,7 @@ struct NCSettingsView: View { }) /// `Privacy` Section Section(content: { + // Section(content: { Button(action: { showPasscode.toggle() }, label: { @@ -73,6 +74,7 @@ struct NCSettingsView: View { .font(Font.system(.body).weight(.light)) .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) .frame(width: 20, height: 20) + .opacity(NCBrandOptions.shared.enforce_protection ? 0.5 : 1) Text(model.isLockActive ? NSLocalizedString("_lock_active_", comment: "") : NSLocalizedString("_lock_not_active_", comment: "")) } .font(.system(size: 16)) @@ -81,41 +83,60 @@ struct NCSettingsView: View { .sheet(isPresented: $showPasscode) { PasscodeView(isLockActive: $model.isLockActive) } - /// Enable Touch ID - Toggle(NSLocalizedString("_enable_touch_face_id_", comment: ""), isOn: $model.enableTouchID) - .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) - .font(.system(size: 16)) - .onChange(of: model.enableTouchID) { _ in - model.updateTouchIDSetting() - } - /// Lock no screen - Toggle(NSLocalizedString("_lock_protection_no_screen_", comment: ""), isOn: $model.lockScreen) - .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) - .font(.system(size: 16)) - .onChange(of: model.lockScreen) { _ in - model.updateLockScreenSetting() + .disabled(NCBrandOptions.shared.enforce_protection) + }, header: { + Text(NSLocalizedString("_privacy_", comment: "")) + }, footer: { + Text("_lock_cannot_disable_") + }) + + Section(content: { + + if !model.isLockActive { +// if model.isLockActive { + /// Enable Touch ID + Group { + Button(action: { + showPasscode.toggle() + }, label: { + VStack { + Text(NSLocalizedString("_change_lock_passcode_", comment: "")) + .tint(Color(NCBrandColor.shared.textColor)) + } + .font(.system(size: 16)) + }) + + Toggle(NSLocalizedString("_enable_touch_face_id_", comment: ""), isOn: $model.enableTouchID) + .onChange(of: model.enableTouchID) { _ in + model.updateTouchIDSetting() + } + /// Lock no screen + Toggle(NSLocalizedString("_lock_protection_no_screen_", comment: ""), isOn: $model.lockScreen) + .onChange(of: model.lockScreen) { _ in + model.updateLockScreenSetting() + } + /// Privacy screen + Toggle(NSLocalizedString("_privacy_screen_", comment: ""), isOn: $model.privacyScreen) + .onChange(of: model.privacyScreen) { _ in + model.updatePrivacyScreenSetting() + } + /// Reset app wrong attempts + Toggle(NSLocalizedString("_reset_wrong_passcode_", comment: ""), isOn: $model.resetWrongAttempts) + .onChange(of: model.resetWrongAttempts) { _ in + model.updateResetWrongAttemptsSetting() + } } - /// Privacy screen - Toggle(NSLocalizedString("_privacy_screen_", comment: ""), isOn: $model.privacyScreen) - .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) .font(.system(size: 16)) - .onChange(of: model.privacyScreen) { _ in - model.updatePrivacyScreenSetting() - } - /// Reset app wrong attempts - Toggle(NSLocalizedString("_reset_wrong_passcode_", comment: ""), isOn: $model.resetWrongAttempts) .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) - .font(.system(size: 16)) - .onChange(of: model.resetWrongAttempts) { _ in - model.updateResetWrongAttemptsSetting() - } - }, header: { - Text(NSLocalizedString("_privacy_", comment: "")) + } }, footer: { - Text(NSLocalizedString("_lock_protection_no_screen_footer_", comment: "") + "\n" + String(format: NSLocalizedString("_reset_wrong_passcode_desc_", comment: ""), NCBrandOptions.shared.resetAppPasscodeAttempts)) - .font(.system(size: 12)) - .lineSpacing(1) + if model.isLockActive { + Text(NSLocalizedString("_lock_protection_no_screen_footer_", comment: "") + "\n" + String(format: NSLocalizedString("_reset_wrong_passcode_desc_", comment: ""), NCBrandOptions.shared.resetAppPasscodeAttempts)) + .font(.system(size: 12)) + .lineSpacing(1) + } }) + /// Display Section(header: Text(NSLocalizedString("_display_", comment: "")), content: { NavigationLink(destination: LazyView { From 25bcabae6079b511132a91c03e77c4cdb09ed96e Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 14 Nov 2024 18:03:14 +0100 Subject: [PATCH 02/25] WIP Signed-off-by: Milen Pivchev --- iOSClient/Main/NCMainTabBarController.swift | 10 +- iOSClient/SceneDelegate.swift | 8 - .../Settings/Settings/NCSettingsModel.swift | 18 +- .../Settings/Settings/NCSettingsView.swift | 55 ++-- .../en.lproj/Localizable.strings | 242 +++++++++--------- 5 files changed, 169 insertions(+), 164 deletions(-) diff --git a/iOSClient/Main/NCMainTabBarController.swift b/iOSClient/Main/NCMainTabBarController.swift index d9b25c4a57..e516be9e85 100644 --- a/iOSClient/Main/NCMainTabBarController.swift +++ b/iOSClient/Main/NCMainTabBarController.swift @@ -51,14 +51,6 @@ class NCMainTabBarController: UITabBarController { if #available(iOS 17.0, *) { traitOverrides.horizontalSizeClass = .compact } - -// if NCBrandOptions.shared.enforce_protection && NCKeychain().passcode.isEmptyOrNil { -// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(false))) -//// vc.isModalInPresentation = true -//// isModalInPresentation = true -//// vc.modalPresentationStyle = .fullScreen -// present(vc, animated: true) -// } } override func viewDidAppear(_ animated: Bool) { @@ -66,7 +58,7 @@ class NCMainTabBarController: UITabBarController { previousIndex = selectedIndex if NCBrandOptions.shared.enforce_protection && !NCKeychain().presentPasscode { - let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(false))) + let vc = UIHostingController(rootView: SetupPasscodeView(isLockActive: .constant(false))) vc.isModalInPresentation = true present(vc, animated: true) diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index 6e8b7d9d90..623e22ec90 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -81,10 +81,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let navigationController = NCLoginNavigationController(rootViewController: viewController) window?.rootViewController = navigationController window?.makeKeyAndVisible() - - -// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(true))) -// controller.present(vc, animated: true) } } } @@ -109,10 +105,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { hidePrivacyProtectionWindow() if let window = SceneManager.shared.getWindow(scene: scene), let controller = SceneManager.shared.getController(scene: scene) { window.rootViewController = controller -// if NCBrandOptions.shared.enforce_protection && NCKeychain().passcode.isEmptyOrNil { -// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(true))) -// controller.present(vc, animated: true) -// } else if NCKeychain().presentPasscode { NCPasscode.shared.presentPasscode(viewController: controller, delegate: self) { NCPasscode.shared.enableTouchFaceID() diff --git a/iOSClient/Settings/Settings/NCSettingsModel.swift b/iOSClient/Settings/Settings/NCSettingsModel.swift index ee25dfe4cd..40c0f4b03f 100644 --- a/iOSClient/Settings/Settings/NCSettingsModel.swift +++ b/iOSClient/Settings/Settings/NCSettingsModel.swift @@ -53,6 +53,8 @@ class NCSettingsModel: ObservableObject, ViewOnAppearHandling { NCSession.shared.getSession(controller: controller) } + var changePasscode = false + /// Initializes the view model with default values. init(controller: NCMainTabBarController?) { self.controller = controller @@ -112,13 +114,14 @@ class NCSettingsModel: ObservableObject, ViewOnAppearHandling { } } -struct PasscodeView: UIViewControllerRepresentable { +struct SetupPasscodeView: UIViewControllerRepresentable { @Binding var isLockActive: Bool + var changePasscode = false func makeUIViewController(context: Context) -> UIViewController { let laContext = LAContext() var error: NSError? - if NCKeychain().passcode != nil { + if NCKeychain().passcode != nil, !changePasscode { let passcodeViewController = TOPasscodeViewController(passcodeType: .sixDigits, allowCancel: true) passcodeViewController.keypadButtonShowLettering = false if NCKeychain().touchFaceID && laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { @@ -138,6 +141,13 @@ struct PasscodeView: UIViewControllerRepresentable { } passcodeViewController.delegate = context.coordinator return passcodeViewController + } else if changePasscode { + let passcodeSettingsViewController = TOPasscodeSettingsViewController() + passcodeSettingsViewController.hideOptionsButton = true +// passcodeSettingsViewController.requireCurrentPasscode = true + passcodeSettingsViewController.passcodeType = .sixDigits + passcodeSettingsViewController.delegate = context.coordinator + return passcodeSettingsViewController } else { let passcodeSettingsViewController = TOPasscodeSettingsViewController() passcodeSettingsViewController.hideOptionsButton = true @@ -157,8 +167,8 @@ struct PasscodeView: UIViewControllerRepresentable { } class Coordinator: NSObject, TOPasscodeSettingsViewControllerDelegate, TOPasscodeViewControllerDelegate { - var parent: PasscodeView - init(_ parent: PasscodeView) { + var parent: SetupPasscodeView + init(_ parent: SetupPasscodeView) { self.parent = parent } diff --git a/iOSClient/Settings/Settings/NCSettingsView.swift b/iOSClient/Settings/Settings/NCSettingsView.swift index 600350b1c7..cfff9db421 100644 --- a/iOSClient/Settings/Settings/NCSettingsView.swift +++ b/iOSClient/Settings/Settings/NCSettingsView.swift @@ -31,6 +31,8 @@ struct NCSettingsView: View { @State private var showAcknowledgements = false /// State to control the visibility of the passcode view @State private var showPasscode = false + /// State to contorl the visibility of the change passcode view + @State private var showChangePasscode = false /// State to control the visibility of the Policy view @State private var showBrowser = false /// State to control the visibility of the Source Code view @@ -63,7 +65,6 @@ struct NCSettingsView: View { }) /// `Privacy` Section Section(content: { - // Section(content: { Button(action: { showPasscode.toggle() }, label: { @@ -80,32 +81,31 @@ struct NCSettingsView: View { .font(.system(size: 16)) }) .tint(Color(NCBrandColor.shared.textColor)) - .sheet(isPresented: $showPasscode) { - PasscodeView(isLockActive: $model.isLockActive) - } .disabled(NCBrandOptions.shared.enforce_protection) }, header: { Text(NSLocalizedString("_privacy_", comment: "")) }, footer: { - Text("_lock_cannot_disable_") + Text(NSLocalizedString("_lock_cannot_disable_mdm_", comment: "")) }) Section(content: { if !model.isLockActive { -// if model.isLockActive { - /// Enable Touch ID Group { - Button(action: { - showPasscode.toggle() - }, label: { - VStack { - Text(NSLocalizedString("_change_lock_passcode_", comment: "")) - .tint(Color(NCBrandColor.shared.textColor)) - } - .font(.system(size: 16)) - }) - + // Change passcode + if model.enableTouchID { + Button(action: { + showChangePasscode.toggle() + }, label: { + VStack { + Text(NSLocalizedString("_change_lock_passcode_", comment: "")) + .tint(Color(NCBrandColor.shared.textColor)) + } + .font(.system(size: 16)) + }) + .transition(.scale) + } + /// Enable Touch ID Toggle(NSLocalizedString("_enable_touch_face_id_", comment: ""), isOn: $model.enableTouchID) .onChange(of: model.enableTouchID) { _ in model.updateTouchIDSetting() @@ -115,11 +115,7 @@ struct NCSettingsView: View { .onChange(of: model.lockScreen) { _ in model.updateLockScreenSetting() } - /// Privacy screen - Toggle(NSLocalizedString("_privacy_screen_", comment: ""), isOn: $model.privacyScreen) - .onChange(of: model.privacyScreen) { _ in - model.updatePrivacyScreenSetting() - } + /// Reset app wrong attempts Toggle(NSLocalizedString("_reset_wrong_passcode_", comment: ""), isOn: $model.resetWrongAttempts) .onChange(of: model.resetWrongAttempts) { _ in @@ -137,6 +133,14 @@ struct NCSettingsView: View { } }) + Section { + /// Privacy screen + Toggle(NSLocalizedString("_privacy_screen_", comment: ""), isOn: $model.privacyScreen) + .onChange(of: model.privacyScreen) { _ in + model.updatePrivacyScreenSetting() + } + } + /// Display Section(header: Text(NSLocalizedString("_display_", comment: "")), content: { NavigationLink(destination: LazyView { @@ -287,6 +291,13 @@ struct NCSettingsView: View { Text(model.footerApp + model.footerServer + model.footerSlogan) }) } + .transition(.scale) + .sheet(isPresented: $showPasscode) { + SetupPasscodeView(isLockActive: $model.isLockActive) + } + .sheet(isPresented: $showChangePasscode) { + SetupPasscodeView(isLockActive: $model.isLockActive, changePasscode: true) + } .navigationBarTitle(NSLocalizedString("_settings_", comment: "")) .defaultViewModifier(model) } diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 93796e43e0..0a18937cf8 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -60,7 +60,7 @@ "_destination_" = "Destination"; "_ok_" = "OK"; "_beta_version_" = "Beta version"; -"_function_in_testing_" = "Function in testing, please send information about any problems you run into."; +"_function_in_testing_" = "Function is in testing, please send information about any problems you run into."; "_done_" = "Done"; "_clear_" = "Clear"; "_passcode_too_short_" = "Passcode too short, at least 4 characters required"; @@ -97,18 +97,18 @@ "_initialization_" = "Initialization"; "_experimental_" = "Experimental"; "_select_dir_media_tab_" = "Select as folder \"Media\""; -"_error_creation_file_" = "Oops! Could not create the file"; +"_error_creation_file_" = "Oops! Could not create the file."; "_save_path_" = "Save path"; "_save_settings_" = "Save settings"; "_mode_filename_" = "Filename mode"; -"_warning_owncloud_" = "You are connected to an ownCloud server. This is untested and unsupported, use at your own risk. To upgrade to Nextcloud, see https://nextcloud.com/migration"; -"_warning_unsupported_" = "You are using an unsupported version of Nextcloud. For the safety of your data we strongly recommend updating to the latest stable Nextcloud"; +"_warning_owncloud_" = "You are connected to an ownCloud server. This is untested and unsupported, use at your own risk. To upgrade to Nextcloud, see https://nextcloud.com/migration."; +"_warning_unsupported_" = "You are using an unsupported version of Nextcloud. For the safety of your data we strongly recommend updating to the latest stable Nextcloud."; "_restore_" = "Restore"; "_camera_roll_" = "Camera roll"; "_tap_here_to_change_" = "Tap here to change"; "_no_albums_" = "No albums"; -"_denied_album_" = "This app does not have access to \"Photos\", you can enable access in Privacy Settings"; -"_denied_camera_" = "This app does not have access to \"Camera\", you can enable access in Privacy Settings"; +"_denied_album_" = "This app does not have access to \"Photos\". You can enable access in Privacy Settings."; +"_denied_camera_" = "This app does not have access to the \"Camera\". You can enable access in Privacy Settings."; "_start_" = "Start"; "_force_start_" = "Force the start"; "_purchase_" = "Purchase"; @@ -144,7 +144,7 @@ "_file_conflict_desc_" = "Which files do you want to keep?\nIf you select both versions, the copied file will have a number added to its name."; "_file_conflict_new_" = "New files"; "_file_conflict_exists_" = "Already existing files"; -"_file_not_rewite_doc_" = "It is not possible to overwrite a document but only to create a new one"; +"_file_not_rewite_doc_" = "It is not possible to overwrite a document but only to create a new one."; "_move_or_copy_" = "Move or copy"; "_copy_" = "Copy"; "_now_" = "Now"; @@ -174,7 +174,7 @@ "_unlock_selected_files_" = "Unlock files"; "_locked_by_" = "Locked by %@"; "_file_locked_no_override_" = "This file is locked. It cannot be overridden."; -"_lock_no_permissions_selected_" = "Not allowed for some selected files or folders"; +"_lock_no_permissions_selected_" = "Not allowed for some selected files or folders."; /* Remove a file from a list, don't delete it entirely */ "_remove_file_" = "Remove file"; @@ -185,7 +185,6 @@ "_delete_photo_" = "Delete photo"; "_delete_video_" = "Delete video"; "_automatic_Download_Image_" = "Use images in full resolution"; -"_automatic_Download_Image_footer_" = "When viewing images always download, if not available locally, the images in full resolution"; "_size_" = "Size"; "_file_size_" = "Exported file size"; "_dimension_" = "Dimension"; @@ -228,9 +227,10 @@ "_more_action_title_" = "More Details"; "_wait_file_preparation_" = "Please wait, preparing file upload…"; "_wait_file_encryption_" = "Please wait, encrypting file…"; -"_passcode_counter_fail_" = "Too many failed attempts, please wait before reattempting"; +"_passcode_counter_fail_" = "Too many failed attempts, please wait before reattempting."; "_seconds_" = "seconds"; - +"_change_lock_passcode_" = "Change passcode"; +"_lock_cannot_disable_mdm_" = "Disabling the lock is not permitted by your configuration profile."; /* Background of the file listing view */ "_use_as_background_" = "Use it as a background"; @@ -384,7 +384,7 @@ "_trial_" = "Trial"; "_trial_expired_day_" = "Days remaining"; "_time_remaining_" = "%@ remaining"; -"_disableLocalCacheAfterUpload_footer_" = "After uploading the file, do not keep it in the local cache"; +"_disableLocalCacheAfterUpload_footer_" = "After uploading the file, do not keep it in the local cache."; "_disableLocalCacheAfterUpload_" = "Disable local cache"; "_autoupload_" = "Auto upload photos/videos"; "_autoupload_select_folder_" = "Select the \"Auto upload\" folder"; @@ -394,7 +394,7 @@ "_autoupload_videos_" = "Auto upload videos"; "_autoupload_favorites_" = "Auto upload favorites only"; "_autoupload_description_" = "New photos/videos will be automatically uploaded to your server."; -"_autoupload_description_background_" = "This option requires the use of GPS to trigger the detection of new photos/videos in the camera roll once the location changes significantly"; +"_autoupload_description_background_" = "This option requires the use of GPS to trigger the detection of new photos/videos in the camera roll once the location changes significantly."; "_autoupload_background_title_" = "Limitations"; "_autoupload_background_msg_" = "Due to iOS restrictions, it is not yet possible to perform background processes, unless GPS services are activated. Once the cell in the cellular network is changed, the system wakes up for a short time and checks for new photos to upload to the cloud."; "_autoupload_change_location_" = "Change folder"; @@ -472,9 +472,9 @@ "_location_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Location Services\""; "_access_photo_not_enabled_" = "Access to Photos not enabled"; "_access_photo_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Photo Access\""; -"_access_background_app_refresh_denied_" = "\"Background App Refresh\" is denied. Please enable it in \"Settings\" otherwise, new photos or videos will not be detected when the application is in the background"; -"_access_photo_location_not_enabled_" = "Access to Photos and Location not enabled"; -"_access_photo_location_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Photo Access\" and \"Location Services\""; +"_access_background_app_refresh_denied_" = "\"Background App Refresh\" is denied. Please enable it in \"Settings\" otherwise, new photos or videos will not be detected when the application is in the background."; +"_access_photo_location_not_enabled_" = "Access to Photos and Location not enabled."; +"_access_photo_location_not_enabled_msg_" = "Please go to \"Settings\" and enable \"Photo Access\" and \"Location Services\"."; "_tutorial_photo_view_" = "No photos or videos uploaded yet"; "_error_createsubfolders_upload_" = "Error creating subfolders"; "_activate_autoupload_" = "Enable auto upload"; @@ -489,11 +489,11 @@ "_a_day_ago_" = "a day ago"; "_days_ago_" = "%d days ago"; "_over_30_days_" = "over 30 days"; -"_connection_internet_offline_" = "The internet connection appears to be offline or Wi-Fi is required"; +"_connection_internet_offline_" = "The internet connection appears to be offline or Wi-Fi is required."; "_insert_password_" = "Enter password"; "_update_in_progress_" = "Version upgrade, please wait …"; "_forbidden_characters_" = "Forbidden characters: %@"; -"_cannot_send_mail_error_" = "No account set up, or wrong email address entered"; +"_cannot_send_mail_error_" = "No account is set up, or wrong email address entered."; "_open_url_error_" = "Cannot open the URL for this action"; "_photo_camera_" = "Photos"; "_media_" = "Media"; @@ -551,8 +551,8 @@ "_browse_images_" = "Browse images"; "_synchronized_folder_" = "Keep the folder synchronized"; "_remove_synchronized_folder_" = "Turn off the synchronization"; -"_synchronized_confirm_" = "After enabling the synchronization, all files in the folder will be synchronized with the server, continue?"; -"_offline_folder_confirm_" = "After enabling the offline folder, all files in it will be synchronized with the server, continue?"; +"_synchronized_confirm_" = "After enabling the synchronization, all files in the folder will be synchronized with the server. Continue?"; +"_offline_folder_confirm_" = "After enabling the offline folder, all files in it will be synchronized with the server. Continue?"; "_file_not_found_reload_" = "File not found, pull down to refresh"; "_title_section_download_" = "DOWNLOAD"; "_download_" = "Download"; @@ -602,7 +602,7 @@ "_search_this_folder_" = "Search in this folder"; "_search_all_folders_" = "Search in all folders"; "_search_sub_folder_" = "Search here and in subfolders"; -"_theming_is_light_" = "Server theming too brightly coloured, not applicable"; +"_theming_is_light_" = "Server theming too brightly coloured, not applicable."; "_cancel_all_task_" = "Cancel all transfers"; "_status_wait_download_" = "Waiting for download"; "_status_in_download_" = "In download"; @@ -647,13 +647,13 @@ "_date_" = "Date"; "_share_title_" = "Share"; "_add_sharee_" = "Add users or groups"; -"_add_sharee_footer_" = "You can share this resource by adding users or groups. To remove a share, remove all users and groups"; +"_add_sharee_footer_" = "You can share this resource by adding users or groups. To remove a share, remove all users and groups."; "_find_sharee_title_" = "Search"; "_find_sharee_" = "Search for user or group …"; -"_find_sharee_footer_" = "Enter part of the name of the user or group to search for (at least 2 characters) followed by \"Return\", select the users that should be allowed to access the share followed by \"Done\" to confirm"; +"_find_sharee_footer_" = "Enter part of the name of the user or group to search for (at least 2 characters) followed by \"Return\", select the users that should be allowed to access the share followed by \"Done\" to confirm."; "_user_is_group_" = "(Group)"; "_direct_sharee_title_" = "Share"; -"_direct_sharee_footer_" = "If you already know the name, enter it, then select the share type and press \"Done\" to confirm"; +"_direct_sharee_footer_" = "If you already know the name, enter it, then select the share type and press \"Done\" to confirm."; "_direct_sharee_" = "Enter the username …"; "_user_sharee_footer_" = "Tap to change permissions"; "_share_type_title_" = "Type of share"; @@ -661,7 +661,7 @@ "_share_type_group_" = "Group"; "_share_type_remote_" = "Remote"; "_enforce_password_protection_" = "Enforce password protection"; -"_password_obligatory_" = "Enforce password protection enabled, password obligatory"; +"_password_obligatory_" = "Enforce password protection enabled, password obligatory."; "_shared_with_you_by_" = "Shared with you by"; "_shareLinksearch_placeholder_" = "Name, email, or Federated Cloud ID …"; "_shareLinksearch_mail_placeholder_" = "Type a name or an email and press Search"; @@ -687,9 +687,9 @@ "_share_can_download_" = "Allow download"; "_share_unshare_" = "Unshare"; "_share_internal_link_" = "Internal link"; -"_share_internal_link_des_" = "Only works for users with access to this file/folder"; -"_share_reshare_disabled_" = "You are not allowed to reshare this file/folder"; -"_share_reshare_restricted_" = "Note: You only have limited permission to reshare this file/folder"; +"_share_internal_link_des_" = "Only works for users with access to this file/folder."; +"_share_reshare_disabled_" = "You are not allowed to reshare this file/folder."; +"_share_reshare_restricted_" = "Note: You only have limited permission to reshare this file/folder."; "_no_transfer_" = "No transfers yet"; "_no_transfer_sub_" = "Uploads and downloads from this device will show up here"; @@ -698,7 +698,7 @@ "_transfers_" = "Transfers"; "_activity_" = "Activity"; "_activity_file_not_present_" = "File no longer present"; -"_trash_file_not_found_" = "It seems that the file is not in the trash. Go to the trash to update it and try again"; +"_trash_file_not_found_" = "It seems that the file is not in the Trash. Go to the Trash to update it and try again."; "_list_shares_" = "Shares"; "_list_shares_no_files_" = "No shares yet"; "_tutorial_list_shares_view_" = "Files and folders you share will show up here"; @@ -718,7 +718,7 @@ "_enter_filename_" = "Enter filename …"; "_default_preview_filename_footer_" = "Example preview of filename: IMG_0001.JPG"; "_filename_header_" = "Enter filename"; -"_preview_filename_" = "Example preview of filename. You can use the mask %@ for date/time"; +"_preview_filename_" = "Example preview of filename. You can use the mask %@ for date/time."; "_add_filenametype_" = "Specify type in filename"; "_filenametype_photo_video_" = "Photo/Video"; "_maintain_original_filename_" = "Maintain original filename"; @@ -736,7 +736,7 @@ "_e2e_server_disabled_" = "End-to-end encryption app disabled on server"; "_e2e_settings_view_passphrase_" = "All 12 words together make a very strong password, letting only you view and make use of your encrypted files. Please write it down and keep it somewhere safe."; "_e2e_settings_read_passphrase_" = "Read passphrase"; -"_e2e_settings_lock_not_active_" = "Lock not active, go back to \"Settings\" and activate it"; +"_e2e_settings_lock_not_active_" = "Lock not active. Go back to \"Settings\" and activate it."; "_e2e_settings_the_passphrase_is_" = "The passphrase is:"; "_e2e_passphrase_request_title_" = "Request passphrase"; "_e2e_passphrase_request_message_" = "Insert the 12 words"; @@ -744,9 +744,9 @@ "_e2e_settings_remove_message_" = "Confirm removal of encryption along with the passphrase."; "_e2e_set_folder_encrypted_" = "Encrypt"; "_e2e_remove_folder_encrypted_" = "Decrypt"; -"_e2e_goto_settings_for_enable_" = "This is an encrypted directory, go to \"Settings\" and enable end-to-end encryption"; -"_e2e_error_" = "An internal end-to-end encryption error occurred"; -"_e2e_in_upload_" = "Upload in progress, please wait for all files to be transferred"; +"_e2e_goto_settings_for_enable_" = "This is an encrypted directory, go to \"Settings\" and enable end-to-end encryption."; +"_e2e_error_" = "An internal end-to-end encryption error occurred."; +"_e2e_in_upload_" = "Upload in progress, please wait for all files to be transferred."; "_scans_document_" = "Scan document"; "_scanned_images_" = "Scanned images"; "_scan_document_pdf_page_" = "Page"; @@ -802,63 +802,63 @@ "_sign_up_" = "Sign up with provider"; "_host_your_own_server" = "Host your own server"; "_unauthorized_" = "Unauthorized"; -"_bad_username_password_" = "Wrong username or password"; -"_cancelled_by_user" = "Transfer canceled"; -"_error_folder_destiny_is_the_same_" = "It is not possible to move the folder into itself"; -"_error_not_permission_" = "You don't have permission to complete the operation"; -"_error_path_" = "Unable to open this file or folder. Please make sure it exists"; -"_file_upload_not_exitst_" = "The file that you want to upload does not exist"; -"_forbidden_characters_from_server_" = "The name contains at least one invalid character"; -"_error_not_modified_" = "Resource not modified"; -"_not_connected_internet_" = "Server connection error"; -"_not_possible_connect_to_server_" = "It is not possible to connect to the server at this time"; -"_not_possible_create_folder_" = "Folder could not be created"; -"_server_down_" = "Could not establish contact with server"; -"_time_out_" = "Timeout, try again"; -"_unknow_response_server_" = "Unexpected response from server"; -"_user_authentication_required_" = "User authentication required"; -"_file_directory_locked_" = "File or directory locked"; -"_ssl_certificate_untrusted_" = "The certificate for this server is invalid"; -"_ssl_certificate_changed_" = "The certificate for this server seems to have changed"; -"_internal_server_" = "Internal server error"; -"_file_already_exists_" = "Could not complete the operation, a file with the same name exists"; -"_file_folder_not_exists_" = "The source file wasn't found at the specified path"; -"_folder_contents_nochanged_" = "The folder contents have not changed"; -"_images_invalid_converted_" = "The image is invalid and cannot be converted to a thumbnail"; -"_method_not_expected_" = "Unexpected request method"; -"_reauthenticate_user_" = "Access expired, log in again"; -"_server_error_retry_" = "The server is temporarily unavailable"; -"_too_many_files_" = "Too many files would be involved in this operation"; -"_too_many_request_" = "Sending too many requests caused the rate limit to be reached"; -"_user_over_quota_" = "Storage quota is reached"; -"_ssl_connection_error_" = "Connection SSL error, try again"; -"_bad_request_" = "Bad request"; -"_webdav_locked_" = "WebDAV Locked: Trying to access locked resource"; -"_error_user_not_available_" = "The user is no longer available"; -"_server_response_error_" = "Server response content error"; -"_no_nextcloud_found_" = "Server not found"; -"_error_decompressing_" = "Error during decompressing. Unknown compression method or the file is corrupt"; -"_error_json_decoding_" = "Serious internal error in decoding metadata (The data couldn't be read because it isn't in the correct format.)"; -"_error_check_remote_user_" = "Server responded with an error. Please log in again"; -"_request_entity_too_large_" = "The file is too large"; -"_not_possible_download_" = "It is not possible to download the file"; -"_not_possible_upload_" = "It is not possible to upload the file"; -"_error_files_upload_" = "Error uploading files"; -"_method_not_allowed_" = "The requested method is not supported"; -"_invalid_url_" = "Invalid server URL"; -"_invalid_literal_" = "Invalid search string"; -"_invalid_date_format_" = "Invalid date format"; -"_invalid_data_format_" = "Invalid data format"; -"_error_decode_xml_" = "Invalid response, error decode XML"; -"_internal_generic_error_" = "internal error"; -"_editor_unknown_" = "Failed to open file: Editor is unknown"; -"_err_file_not_found_" = "File not found, removed"; -"_err_e2ee_app_version_" = "The app version of end-to-end encryption is not compatible with the server, please update your server"; -"_err_permission_microphone_" = "Please allow Microphone usage from Settings"; -"_err_permission_photolibrary_" = "Please allow Photos from Settings"; -"_err_permission_locationmanager_" = "Please allow Location - Always from Settings"; -"_qrcode_not_authorized_" = "This app is not authorized to use Back Camera"; -"_qrcode_not_supported_" = "QR code not supported by the current device"; +"_bad_username_password_" = "Wrong username or password."; +"_cancelled_by_user" = "Transfer canceled."; +"_error_folder_destiny_is_the_same_" = "It is not possible to move the folder into itself."; +"_error_not_permission_" = "You don't have permission to complete the operation."; +"_error_path_" = "Unable to open this file or folder. Please make sure it exists."; +"_file_upload_not_exitst_" = "The file that you want to upload does not exist."; +"_forbidden_characters_from_server_" = "The name contains at least one invalid character."; +"_error_not_modified_" = "Resource not modified."; +"_not_connected_internet_" = "Server connection error."; +"_not_possible_connect_to_server_" = "It is not possible to connect to the server at this time."; +"_not_possible_create_folder_" = "Folder could not be created."; +"_server_down_" = "Could not establish contact with server."; +"_time_out_" = "Timeout, try again."; +"_unknow_response_server_" = "Unexpected response from server."; +"_user_authentication_required_" = "User authentication required."; +"_file_directory_locked_" = "File or directory locked."; +"_ssl_certificate_untrusted_" = "The certificate for this server is invalid."; +"_ssl_certificate_changed_" = "The certificate for this server seems to have changed."; +"_internal_server_" = "Internal server error."; +"_file_already_exists_" = "Could not complete the operation, a file with the same name exists."; +"_file_folder_not_exists_" = "The source file wasn't found at the specified path."; +"_folder_contents_nochanged_" = "The folder contents have not changed."; +"_images_invalid_converted_" = "The image is invalid and cannot be converted to a thumbnail."; +"_method_not_expected_" = "Unexpected request method."; +"_reauthenticate_user_" = "Access expired, log in again."; +"_server_error_retry_" = "The server is temporarily unavailable."; +"_too_many_files_" = "Too many files would be involved in this operation."; +"_too_many_request_" = "Sending too many requests caused the rate limit to be reached."; +"_user_over_quota_" = "Storage quota is reached."; +"_ssl_connection_error_" = "Connection SSL error, try again."; +"_bad_request_" = "Bad request."; +"_webdav_locked_" = "WebDAV Locked: Trying to access locked resource."; +"_error_user_not_available_" = "The user is no longer available."; +"_server_response_error_" = "Server response content error."; +"_no_nextcloud_found_" = "Server not found."; +"_error_decompressing_" = "Error during decompressing. Unknown compression method or the file is corrupt."; +"_error_json_decoding_" = "Serious internal error in decoding metadata (The data couldn't be read because it isn't in the correct format)."; +"_error_check_remote_user_" = "Server responded with an error. Please log in again."; +"_request_entity_too_large_" = "The file is too large."; +"_not_possible_download_" = "It is not possible to download the file."; +"_not_possible_upload_" = "It is not possible to upload the file."; +"_error_files_upload_" = "Error uploading files."; +"_method_not_allowed_" = "The requested method is not supported."; +"_invalid_url_" = "Invalid server URL."; +"_invalid_literal_" = "Invalid search string."; +"_invalid_date_format_" = "Invalid date format."; +"_invalid_data_format_" = "Invalid data format."; +"_error_decode_xml_" = "Invalid response, error decode XML."; +"_internal_generic_error_" = "internal error."; +"_editor_unknown_" = "Failed to open file: Editor is unknown."; +"_err_file_not_found_" = "File not found, removed."; +"_err_e2ee_app_version_" = "The app version of end-to-end encryption is not compatible with the server, please update your server.."; +"_err_permission_microphone_" = "Please allow Microphone usage from Settings."; +"_err_permission_photolibrary_" = "Please allow Photos from Settings."; +"_err_permission_locationmanager_" = "Please allow Location - Always from Settings."; +"_qrcode_not_authorized_" = "This app is not authorized to use the Back Camera."; +"_qrcode_not_supported_" = "QR code not supported by the current device."; "_create_voice_memo_" = "Create voice memo"; "_voice_memo_start_" = "Tap to start"; "_voice_memo_stop_" = "Tap to stop"; @@ -879,7 +879,7 @@ "_overwrite_original_" = "Overwrite original"; "_save_as_copy_" = "Save as copy"; "_discard_changes_" = "Close and discard changes"; -"_message_disable_overwrite_livephoto_" = "This image is a Live Photo, overwrite will not be possible"; +"_message_disable_overwrite_livephoto_" = "This image is a Live Photo, overwrite will not be possible."; "_discard_changes_info_" = "Your changes will be discarded."; "_delete_files_desc_" = "Delete files to free up space"; "_delete_old_files_" = "Delete, in the cache, all files older than"; @@ -930,40 +930,40 @@ "_description_dashboardwidget_" = "Having the Dashboard always at your fingertips has never been easier."; "_description_fileswidget_" = "View your recent files and use the toolbar to speed up your operations."; "_description_toolbarwidget_" = "A toolbar to speed up your operations."; -"_no_data_available_" = "No data available"; -"_widget_available_nc25_" = "Widget only available starting with server version 25"; -"_keep_running_" = "Keep the app running for a better user experience"; +"_no_data_available_" = "No data available."; +"_widget_available_nc25_" = "Widget only available starting with server version 25."; +"_keep_running_" = "Keep the app running for a better user experience."; "_recent_activity_" = "Recent activity"; "_title_lockscreenwidget_" = "Status"; -"_description_lockscreenwidget_" = "Keep an eye on available space and recent activity"; +"_description_lockscreenwidget_" = "Keep an eye on available space and recent activity."; "_no_items_" = "No items"; -"_check_back_later_" = "Check back later"; +"_check_back_later_" = "Check back later."; "_exporting_video_" = "Exporting video … Tap to cancel."; "_apps_nextcloud_detect_" = "Detected %@ apps"; "_add_existing_account_" = "Other %@ Apps has been detected, do you want to add an existing account?"; "_status_in_progress_" = "Status reading in progress …"; -"_status_e2ee_on_server_" = "The end-to-end encryption is already configured in the server but not yet enabled on this device"; +"_status_e2ee_on_server_" = "The end-to-end encryption is already configured in the server but not yet enabled on this device."; "_status_e2ee_not_setup_" = "The end-to-end encryption is not yet configured on the server"; "_status_e2ee_configured_" = "The end-to-end encryption is correctly configured"; -"_e2ee_set_as_offline_" = "Not allowed for encrypted files or folders"; +"_e2ee_set_as_offline_" = "Not allowed for encrypted files or folders."; "_change_upload_filename_" = "Do you want to save the file with a different name?"; "_save_file_as_" = "Save file as %@"; -"_password_ascii_" = "The password cannot contain special characters"; +"_password_ascii_" = "The password cannot contain special characters."; "_calendar_contacts_" = "Calendar and Contacts"; "_mobile_config_" = "Download the configuration profile"; -"_calendar_contacts_footer_warning_" = "Configuration profile can only be downloaded if Safari is set as default browser"; +"_calendar_contacts_footer_warning_" = "Configuration profile can only be downloaded if Safari is set as default browser."; "_calendar_contacts_footer_" = "After downloading the profile you can install it from Settings."; "_preview_" = "Preview"; "_crop_" = "Crop"; "_modify_image_desc_" = "Tap on a file to modify or rename."; -"_message_disable_livephoto_" = "This image is a Live Photo, changing it will lose the Live Photo effect"; +"_message_disable_livephoto_" = "This image is a Live Photo, changing it will lose the Live Photo effect."; "_enable_livephoto_" = "Enable Live Photo"; "_disable_livephoto_" = "Disable Live Photo"; "_undo_modify_" = "Undo modifying"; -"_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode"; -"_disableFilesApp_" = "Files app cannot be used because it is disabled"; +"_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode."; +"_disableFilesApp_" = "Files app cannot be used because it is disabled."; "_reset_application_done_" = "Reset application, done."; -"_rename_already_exists_" = "A file with this name already exists"; +"_rename_already_exists_" = "A file with this name already exists."; "_created_" = "Created"; "_recipients_" = "Recipients"; "_are_sure_" = "Are you sure?"; @@ -976,11 +976,11 @@ "_deletion_progess_" = "Deletion in progress"; "_copying_progess_" = "Copying in progress"; "_moving_progess_" = "Moving in progress"; -"_chunk_enough_memory_" = "It seems there is not enough space to send the file"; -"_chunk_create_folder_" = "The file could not be sent, please check the server log"; -"_chunk_files_null_" = "The file for sending could not be created"; -"_chunk_file_null_" = "The file could not be sent"; -"_chunk_move_" = "The sent file could not be reassembled, please check the server log"; +"_chunk_enough_memory_" = "It seems there is not enough space to send the file."; +"_chunk_create_folder_" = "The file could not be sent, please check the server log."; +"_chunk_files_null_" = "The file for sending could not be created."; +"_chunk_file_null_" = "The file could not be sent."; +"_chunk_move_" = "The sent file could not be reassembled, please check the server log."; "_download_image_" = "Download image"; "_download_video_" = "Download video"; "_reset_wrong_passcode_" = "Reset application"; @@ -1009,9 +1009,9 @@ "_while_charging_" = "While charging"; "_additional_options_" = "Additional options"; "_keep_screen_awake_" = "Keep screen awake\nwhile transferring files"; -"_error_not_found_" = "The requested resource could not be found"; -"_error_conflict_" = "The request could not be completed due to a conflict with the current state of the resource"; -"_error_precondition_" = "The server does not meet one of the preconditions that the requester"; +"_error_not_found_" = "The requested resource could not be found."; +"_error_conflict_" = "The request could not be completed due to a conflict with the current state of the resource."; +"_error_precondition_" = "The server does not meet one of the preconditions that the requester."; // Video "_select_trace_" = "Select the trace"; @@ -1020,17 +1020,17 @@ "_downloading_" = "Downloading"; "_download_error_" = "Download error"; "_subtitle_" = "Subtitle"; -"_dts_to_ac3_" = "The DTS is not supported, it requires a conversion to Dolby Digital"; +"_dts_to_ac3_" = "DTS is not supported, it requires a conversion to Dolby Digital."; "_reuired_conversion_" = "This video takes a long time to convert."; "_stay_app_foreground_" = "Keep the app in the foreground …"; "_conversion_available_" = "The conversion is always available on menu"; "_video_format_not_recognized_" = "This video needs to be processed to be played, do you want to do it now?"; "_video_must_download_" = "This video needs to be downloaded and processed to be played, do you want to do it now?"; -"_conversion_max_compatibility_" = "Max compatibility, the conversion can take much longer"; -"_video_tap_for_close_" = "A slight pressure to close the processing"; -"_subtitle_not_found_" = "Subtitle not found"; +"_conversion_max_compatibility_" = "Max compatibility is set, the conversion can take much longer"; +"_video_tap_for_close_" = "A slight pressure to close the processing."; +"_subtitle_not_found_" = "Subtitle not found."; "_disable_" = "Disable"; -"_subtitle_not_dowloaded_" = "There are subtitles not downloaded locally"; +"_subtitle_not_dowloaded_" = "There are subtitles not downloaded locally."; "_user_" = "User"; "_add_subtitle_" = "Add an external subtitle"; "_add_audio_" = "Add an external audio"; @@ -1039,8 +1039,8 @@ "_upload_background_msg_" = "Files upload in progress …"; "_create_folder_error_" = "An error has occurred while creating the folder:\n%@.\n\nPlease resolve the issue as soon as possible.\n\nAll uploads are suspended until the problem is resolved.\n"; "_rename_file_error_" = "An error has occurred while renaming the file:\n%@."; -"_creating_dir_progress_" = "Creating directories in progress … keep the application active"; -"_creating_db_photo_progress" = "Creating photo archive in progress … keep the application active"; +"_creating_dir_progress_" = "Creating directories in progress … keep the application active."; +"_creating_db_photo_progress" = "Creating photo archive in progress … keep the application active."; "_account_unauthorized_" = "Warning, %@, you are not authorized, your account has been deleted, if you have changed your password, re-authenticate."; "_folder_offline_desc_" = "Even without an internet connection, you can organize your folders, create files. Once you're back online, your pending actions will automatically sync."; "_offline_not_allowed_" = "This operation is not allowed in offline mode"; @@ -1115,7 +1115,7 @@ "_client_cert_wrong_password_" = "Sorry, you entered an invalid password"; // MARK: Login poll -"_poll_desc_" = "Please complete the log in process in your browser"; +"_poll_desc_" = "Please complete the log in process in your browser."; // MARK: File name validator "_file_name_validator_error_ends_with_space_period_" = "Name must not contain spaces at the beginning or end."; From e6015cf212d7fb26cbb73bf572afadc70c40dc65 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 15 Nov 2024 10:49:39 +0100 Subject: [PATCH 03/25] WIP Signed-off-by: Milen Pivchev --- iOSClient/Settings/Settings/NCSettingsView.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/iOSClient/Settings/Settings/NCSettingsView.swift b/iOSClient/Settings/Settings/NCSettingsView.swift index cfff9db421..f6084d9c6d 100644 --- a/iOSClient/Settings/Settings/NCSettingsView.swift +++ b/iOSClient/Settings/Settings/NCSettingsView.swift @@ -90,10 +90,10 @@ struct NCSettingsView: View { Section(content: { - if !model.isLockActive { + if model.isLockActive { Group { // Change passcode - if model.enableTouchID { + if !model.enableTouchID { Button(action: { showChangePasscode.toggle() }, label: { @@ -140,6 +140,7 @@ struct NCSettingsView: View { model.updatePrivacyScreenSetting() } } + .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) /// Display Section(header: Text(NSLocalizedString("_display_", comment: "")), content: { From 9aef669746c0a25fdca43bcd10e29d7042f33635 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 15 Nov 2024 11:56:56 +0100 Subject: [PATCH 04/25] Finish Signed-off-by: Milen Pivchev --- iOSClient/Main/NCMainTabBarController.swift | 2 +- .../Settings/Settings/NCSettingsView.swift | 40 ++++++++----------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/iOSClient/Main/NCMainTabBarController.swift b/iOSClient/Main/NCMainTabBarController.swift index e516be9e85..c8028829dc 100644 --- a/iOSClient/Main/NCMainTabBarController.swift +++ b/iOSClient/Main/NCMainTabBarController.swift @@ -57,7 +57,7 @@ class NCMainTabBarController: UITabBarController { super.viewDidAppear(animated) previousIndex = selectedIndex - if NCBrandOptions.shared.enforce_protection && !NCKeychain().presentPasscode { + if NCBrandOptions.shared.enforce_protection && NCKeychain().passcode.isEmptyOrNil { let vc = UIHostingController(rootView: SetupPasscodeView(isLockActive: .constant(false))) vc.isModalInPresentation = true diff --git a/iOSClient/Settings/Settings/NCSettingsView.swift b/iOSClient/Settings/Settings/NCSettingsView.swift index f6084d9c6d..809d3792a6 100644 --- a/iOSClient/Settings/Settings/NCSettingsView.swift +++ b/iOSClient/Settings/Settings/NCSettingsView.swift @@ -59,7 +59,6 @@ struct NCSettingsView: View { } .font(.system(size: 16)) } - }, header: { }, footer: { Text(NSLocalizedString("_autoupload_description_", comment: "")) }) @@ -88,23 +87,19 @@ struct NCSettingsView: View { Text(NSLocalizedString("_lock_cannot_disable_mdm_", comment: "")) }) - Section(content: { - - if model.isLockActive { + if model.isLockActive { + Section(content: { Group { // Change passcode - if !model.enableTouchID { - Button(action: { - showChangePasscode.toggle() - }, label: { - VStack { - Text(NSLocalizedString("_change_lock_passcode_", comment: "")) - .tint(Color(NCBrandColor.shared.textColor)) - } - .font(.system(size: 16)) - }) - .transition(.scale) - } + Button(action: { + showChangePasscode.toggle() + }, label: { + VStack { + Text(NSLocalizedString("_change_lock_passcode_", comment: "")) + .tint(Color(NCBrandColor.shared.textColor)) + } + .font(.system(size: 16)) + }) /// Enable Touch ID Toggle(NSLocalizedString("_enable_touch_face_id_", comment: ""), isOn: $model.enableTouchID) .onChange(of: model.enableTouchID) { _ in @@ -121,17 +116,15 @@ struct NCSettingsView: View { .onChange(of: model.resetWrongAttempts) { _ in model.updateResetWrongAttemptsSetting() } + .font(.system(size: 16)) } - .font(.system(size: 16)) - .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) - } - }, footer: { - if model.isLockActive { + }, footer: { Text(NSLocalizedString("_lock_protection_no_screen_footer_", comment: "") + "\n" + String(format: NSLocalizedString("_reset_wrong_passcode_desc_", comment: ""), NCBrandOptions.shared.resetAppPasscodeAttempts)) .font(.system(size: 12)) .lineSpacing(1) - } - }) + }) + .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) + } Section { /// Privacy screen @@ -292,7 +285,6 @@ struct NCSettingsView: View { Text(model.footerApp + model.footerServer + model.footerSlogan) }) } - .transition(.scale) .sheet(isPresented: $showPasscode) { SetupPasscodeView(isLockActive: $model.isLockActive) } From 3020b695deaff66d137664f84bba4a44cc10200f Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 13 Nov 2024 18:44:46 +0100 Subject: [PATCH 05/25] WIP Signed-off-by: Milen Pivchev --- Brand/NCBrand.swift | 4 + iOSClient/Main/NCMainTabBarController.swift | 16 ++++ iOSClient/Menu/NCMenu.swift | 4 +- iOSClient/NCGlobal.swift | 1 + iOSClient/SceneDelegate.swift | 12 +++ .../Settings/Settings/NCSettingsView.swift | 81 ++++++++++++------- 6 files changed, 86 insertions(+), 32 deletions(-) diff --git a/Brand/NCBrand.swift b/Brand/NCBrand.swift index f02ea986a7..487e8d6d26 100755 --- a/Brand/NCBrand.swift +++ b/Brand/NCBrand.swift @@ -72,6 +72,7 @@ let userAgent: String = { var disable_show_more_nextcloud_apps_in_settings: Bool = false var doNotAskPasscodeAtStartup: Bool = false var disable_source_code_in_settings: Bool = false + var enforce_protection = true // (name: "Name 1", url: "https://cloud.nextcloud.com"),(name: "Name 2", url: "https://cloud.nextcloud.com") var enforce_servers: [(name: String, url: String)] = [] @@ -117,6 +118,9 @@ let userAgent: String = { if let str = configurationManaged[NCGlobal.shared.configuration_disable_openin_file] as? String { disable_openin_file = (str as NSString).boolValue } + if let str = configurationManaged[NCGlobal.shared.configuration_enforce_protection] as? String { + enforce_protection = (str as NSString).boolValue + } } } diff --git a/iOSClient/Main/NCMainTabBarController.swift b/iOSClient/Main/NCMainTabBarController.swift index 0c8a0aad70..d9b25c4a57 100644 --- a/iOSClient/Main/NCMainTabBarController.swift +++ b/iOSClient/Main/NCMainTabBarController.swift @@ -22,6 +22,7 @@ // import UIKit +import SwiftUI struct NavigationCollectionViewCommon { var serverUrl: String @@ -50,11 +51,26 @@ class NCMainTabBarController: UITabBarController { if #available(iOS 17.0, *) { traitOverrides.horizontalSizeClass = .compact } + +// if NCBrandOptions.shared.enforce_protection && NCKeychain().passcode.isEmptyOrNil { +// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(false))) +//// vc.isModalInPresentation = true +//// isModalInPresentation = true +//// vc.modalPresentationStyle = .fullScreen +// present(vc, animated: true) +// } } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) previousIndex = selectedIndex + + if NCBrandOptions.shared.enforce_protection && !NCKeychain().presentPasscode { + let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(false))) + vc.isModalInPresentation = true + + present(vc, animated: true) + } } @objc func changeTheming(_ notification: NSNotification) { diff --git a/iOSClient/Menu/NCMenu.swift b/iOSClient/Menu/NCMenu.swift index 699b5a7bdb..a9614dcc4c 100644 --- a/iOSClient/Menu/NCMenu.swift +++ b/iOSClient/Menu/NCMenu.swift @@ -134,11 +134,11 @@ class NCMenu: UITableViewController { extension NCMenu: FloatingPanelControllerDelegate { func floatingPanel(_ fpc: FloatingPanelController, layoutFor size: CGSize) -> FloatingPanelLayout { - return NCMenuFloatingPanelLayout(actionsHeight: self.actions.listHeight) + return NCMenuFloatingPanelLayout(actionsHeight: 600) } func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout { - return NCMenuFloatingPanelLayout(actionsHeight: self.actions.listHeight) + return NCMenuFloatingPanelLayout(actionsHeight: 600) } func floatingPanel(_ fpc: FloatingPanelController, animatorForDismissingWith velocity: CGVector) -> UIViewPropertyAnimator { diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index ab0f5beff8..aa7f69329c 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -379,6 +379,7 @@ class NCGlobal: NSObject { let configuration_disable_log = "disable_log" let configuration_disable_more_external_site = "disable_more_external_site" let configuration_disable_openin_file = "disable_openin_file" + let configuration_enforce_protection = "enforce_protection" // MORE NEXTCLOUD APPS // diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index 28bfb2df7c..51e560731b 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -26,6 +26,7 @@ import UIKit import NextcloudKit import WidgetKit import SwiftEntryKit +import SwiftUI class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? @@ -64,6 +65,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window?.makeKeyAndVisible() /// Set the ACCOUNT controller.account = activeTableAccount.account + +// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(true))) +// controller.present(vc, animated: true) } } else { NCKeychain().removeAll() @@ -77,6 +81,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let navigationController = NCLoginNavigationController(rootViewController: viewController) window?.rootViewController = navigationController window?.makeKeyAndVisible() + + +// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(true))) +// controller.present(vc, animated: true) } } } @@ -101,6 +109,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { hidePrivacyProtectionWindow() if let window = SceneManager.shared.getWindow(scene: scene), let controller = SceneManager.shared.getController(scene: scene) { window.rootViewController = controller +// if NCBrandOptions.shared.enforce_protection && NCKeychain().passcode.isEmptyOrNil { +// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(true))) +// controller.present(vc, animated: true) +// } else if NCKeychain().presentPasscode { NCPasscode.shared.presentPasscode(viewController: controller, delegate: self) { NCPasscode.shared.enableTouchFaceID() diff --git a/iOSClient/Settings/Settings/NCSettingsView.swift b/iOSClient/Settings/Settings/NCSettingsView.swift index 75fc02d567..600350b1c7 100644 --- a/iOSClient/Settings/Settings/NCSettingsView.swift +++ b/iOSClient/Settings/Settings/NCSettingsView.swift @@ -63,6 +63,7 @@ struct NCSettingsView: View { }) /// `Privacy` Section Section(content: { + // Section(content: { Button(action: { showPasscode.toggle() }, label: { @@ -73,6 +74,7 @@ struct NCSettingsView: View { .font(Font.system(.body).weight(.light)) .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) .frame(width: 20, height: 20) + .opacity(NCBrandOptions.shared.enforce_protection ? 0.5 : 1) Text(model.isLockActive ? NSLocalizedString("_lock_active_", comment: "") : NSLocalizedString("_lock_not_active_", comment: "")) } .font(.system(size: 16)) @@ -81,41 +83,60 @@ struct NCSettingsView: View { .sheet(isPresented: $showPasscode) { PasscodeView(isLockActive: $model.isLockActive) } - /// Enable Touch ID - Toggle(NSLocalizedString("_enable_touch_face_id_", comment: ""), isOn: $model.enableTouchID) - .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) - .font(.system(size: 16)) - .onChange(of: model.enableTouchID) { _ in - model.updateTouchIDSetting() - } - /// Lock no screen - Toggle(NSLocalizedString("_lock_protection_no_screen_", comment: ""), isOn: $model.lockScreen) - .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) - .font(.system(size: 16)) - .onChange(of: model.lockScreen) { _ in - model.updateLockScreenSetting() + .disabled(NCBrandOptions.shared.enforce_protection) + }, header: { + Text(NSLocalizedString("_privacy_", comment: "")) + }, footer: { + Text("_lock_cannot_disable_") + }) + + Section(content: { + + if !model.isLockActive { +// if model.isLockActive { + /// Enable Touch ID + Group { + Button(action: { + showPasscode.toggle() + }, label: { + VStack { + Text(NSLocalizedString("_change_lock_passcode_", comment: "")) + .tint(Color(NCBrandColor.shared.textColor)) + } + .font(.system(size: 16)) + }) + + Toggle(NSLocalizedString("_enable_touch_face_id_", comment: ""), isOn: $model.enableTouchID) + .onChange(of: model.enableTouchID) { _ in + model.updateTouchIDSetting() + } + /// Lock no screen + Toggle(NSLocalizedString("_lock_protection_no_screen_", comment: ""), isOn: $model.lockScreen) + .onChange(of: model.lockScreen) { _ in + model.updateLockScreenSetting() + } + /// Privacy screen + Toggle(NSLocalizedString("_privacy_screen_", comment: ""), isOn: $model.privacyScreen) + .onChange(of: model.privacyScreen) { _ in + model.updatePrivacyScreenSetting() + } + /// Reset app wrong attempts + Toggle(NSLocalizedString("_reset_wrong_passcode_", comment: ""), isOn: $model.resetWrongAttempts) + .onChange(of: model.resetWrongAttempts) { _ in + model.updateResetWrongAttemptsSetting() + } } - /// Privacy screen - Toggle(NSLocalizedString("_privacy_screen_", comment: ""), isOn: $model.privacyScreen) - .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) .font(.system(size: 16)) - .onChange(of: model.privacyScreen) { _ in - model.updatePrivacyScreenSetting() - } - /// Reset app wrong attempts - Toggle(NSLocalizedString("_reset_wrong_passcode_", comment: ""), isOn: $model.resetWrongAttempts) .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) - .font(.system(size: 16)) - .onChange(of: model.resetWrongAttempts) { _ in - model.updateResetWrongAttemptsSetting() - } - }, header: { - Text(NSLocalizedString("_privacy_", comment: "")) + } }, footer: { - Text(NSLocalizedString("_lock_protection_no_screen_footer_", comment: "") + "\n" + String(format: NSLocalizedString("_reset_wrong_passcode_desc_", comment: ""), NCBrandOptions.shared.resetAppPasscodeAttempts)) - .font(.system(size: 12)) - .lineSpacing(1) + if model.isLockActive { + Text(NSLocalizedString("_lock_protection_no_screen_footer_", comment: "") + "\n" + String(format: NSLocalizedString("_reset_wrong_passcode_desc_", comment: ""), NCBrandOptions.shared.resetAppPasscodeAttempts)) + .font(.system(size: 12)) + .lineSpacing(1) + } }) + /// Display Section(header: Text(NSLocalizedString("_display_", comment: "")), content: { NavigationLink(destination: LazyView { From eec051878f29d9825229326d00d465eb98bd00bd Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 14 Nov 2024 18:03:14 +0100 Subject: [PATCH 06/25] WIP Signed-off-by: Milen Pivchev --- iOSClient/Main/NCMainTabBarController.swift | 10 +- iOSClient/SceneDelegate.swift | 8 - .../Settings/Settings/NCSettingsModel.swift | 18 +- .../Settings/Settings/NCSettingsView.swift | 55 ++-- .../en.lproj/Localizable.strings | 242 +++++++++--------- 5 files changed, 169 insertions(+), 164 deletions(-) diff --git a/iOSClient/Main/NCMainTabBarController.swift b/iOSClient/Main/NCMainTabBarController.swift index d9b25c4a57..e516be9e85 100644 --- a/iOSClient/Main/NCMainTabBarController.swift +++ b/iOSClient/Main/NCMainTabBarController.swift @@ -51,14 +51,6 @@ class NCMainTabBarController: UITabBarController { if #available(iOS 17.0, *) { traitOverrides.horizontalSizeClass = .compact } - -// if NCBrandOptions.shared.enforce_protection && NCKeychain().passcode.isEmptyOrNil { -// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(false))) -//// vc.isModalInPresentation = true -//// isModalInPresentation = true -//// vc.modalPresentationStyle = .fullScreen -// present(vc, animated: true) -// } } override func viewDidAppear(_ animated: Bool) { @@ -66,7 +58,7 @@ class NCMainTabBarController: UITabBarController { previousIndex = selectedIndex if NCBrandOptions.shared.enforce_protection && !NCKeychain().presentPasscode { - let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(false))) + let vc = UIHostingController(rootView: SetupPasscodeView(isLockActive: .constant(false))) vc.isModalInPresentation = true present(vc, animated: true) diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index 51e560731b..47684c9e6e 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -81,10 +81,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { let navigationController = NCLoginNavigationController(rootViewController: viewController) window?.rootViewController = navigationController window?.makeKeyAndVisible() - - -// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(true))) -// controller.present(vc, animated: true) } } } @@ -109,10 +105,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { hidePrivacyProtectionWindow() if let window = SceneManager.shared.getWindow(scene: scene), let controller = SceneManager.shared.getController(scene: scene) { window.rootViewController = controller -// if NCBrandOptions.shared.enforce_protection && NCKeychain().passcode.isEmptyOrNil { -// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(true))) -// controller.present(vc, animated: true) -// } else if NCKeychain().presentPasscode { NCPasscode.shared.presentPasscode(viewController: controller, delegate: self) { NCPasscode.shared.enableTouchFaceID() diff --git a/iOSClient/Settings/Settings/NCSettingsModel.swift b/iOSClient/Settings/Settings/NCSettingsModel.swift index ee25dfe4cd..40c0f4b03f 100644 --- a/iOSClient/Settings/Settings/NCSettingsModel.swift +++ b/iOSClient/Settings/Settings/NCSettingsModel.swift @@ -53,6 +53,8 @@ class NCSettingsModel: ObservableObject, ViewOnAppearHandling { NCSession.shared.getSession(controller: controller) } + var changePasscode = false + /// Initializes the view model with default values. init(controller: NCMainTabBarController?) { self.controller = controller @@ -112,13 +114,14 @@ class NCSettingsModel: ObservableObject, ViewOnAppearHandling { } } -struct PasscodeView: UIViewControllerRepresentable { +struct SetupPasscodeView: UIViewControllerRepresentable { @Binding var isLockActive: Bool + var changePasscode = false func makeUIViewController(context: Context) -> UIViewController { let laContext = LAContext() var error: NSError? - if NCKeychain().passcode != nil { + if NCKeychain().passcode != nil, !changePasscode { let passcodeViewController = TOPasscodeViewController(passcodeType: .sixDigits, allowCancel: true) passcodeViewController.keypadButtonShowLettering = false if NCKeychain().touchFaceID && laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { @@ -138,6 +141,13 @@ struct PasscodeView: UIViewControllerRepresentable { } passcodeViewController.delegate = context.coordinator return passcodeViewController + } else if changePasscode { + let passcodeSettingsViewController = TOPasscodeSettingsViewController() + passcodeSettingsViewController.hideOptionsButton = true +// passcodeSettingsViewController.requireCurrentPasscode = true + passcodeSettingsViewController.passcodeType = .sixDigits + passcodeSettingsViewController.delegate = context.coordinator + return passcodeSettingsViewController } else { let passcodeSettingsViewController = TOPasscodeSettingsViewController() passcodeSettingsViewController.hideOptionsButton = true @@ -157,8 +167,8 @@ struct PasscodeView: UIViewControllerRepresentable { } class Coordinator: NSObject, TOPasscodeSettingsViewControllerDelegate, TOPasscodeViewControllerDelegate { - var parent: PasscodeView - init(_ parent: PasscodeView) { + var parent: SetupPasscodeView + init(_ parent: SetupPasscodeView) { self.parent = parent } diff --git a/iOSClient/Settings/Settings/NCSettingsView.swift b/iOSClient/Settings/Settings/NCSettingsView.swift index 600350b1c7..cfff9db421 100644 --- a/iOSClient/Settings/Settings/NCSettingsView.swift +++ b/iOSClient/Settings/Settings/NCSettingsView.swift @@ -31,6 +31,8 @@ struct NCSettingsView: View { @State private var showAcknowledgements = false /// State to control the visibility of the passcode view @State private var showPasscode = false + /// State to contorl the visibility of the change passcode view + @State private var showChangePasscode = false /// State to control the visibility of the Policy view @State private var showBrowser = false /// State to control the visibility of the Source Code view @@ -63,7 +65,6 @@ struct NCSettingsView: View { }) /// `Privacy` Section Section(content: { - // Section(content: { Button(action: { showPasscode.toggle() }, label: { @@ -80,32 +81,31 @@ struct NCSettingsView: View { .font(.system(size: 16)) }) .tint(Color(NCBrandColor.shared.textColor)) - .sheet(isPresented: $showPasscode) { - PasscodeView(isLockActive: $model.isLockActive) - } .disabled(NCBrandOptions.shared.enforce_protection) }, header: { Text(NSLocalizedString("_privacy_", comment: "")) }, footer: { - Text("_lock_cannot_disable_") + Text(NSLocalizedString("_lock_cannot_disable_mdm_", comment: "")) }) Section(content: { if !model.isLockActive { -// if model.isLockActive { - /// Enable Touch ID Group { - Button(action: { - showPasscode.toggle() - }, label: { - VStack { - Text(NSLocalizedString("_change_lock_passcode_", comment: "")) - .tint(Color(NCBrandColor.shared.textColor)) - } - .font(.system(size: 16)) - }) - + // Change passcode + if model.enableTouchID { + Button(action: { + showChangePasscode.toggle() + }, label: { + VStack { + Text(NSLocalizedString("_change_lock_passcode_", comment: "")) + .tint(Color(NCBrandColor.shared.textColor)) + } + .font(.system(size: 16)) + }) + .transition(.scale) + } + /// Enable Touch ID Toggle(NSLocalizedString("_enable_touch_face_id_", comment: ""), isOn: $model.enableTouchID) .onChange(of: model.enableTouchID) { _ in model.updateTouchIDSetting() @@ -115,11 +115,7 @@ struct NCSettingsView: View { .onChange(of: model.lockScreen) { _ in model.updateLockScreenSetting() } - /// Privacy screen - Toggle(NSLocalizedString("_privacy_screen_", comment: ""), isOn: $model.privacyScreen) - .onChange(of: model.privacyScreen) { _ in - model.updatePrivacyScreenSetting() - } + /// Reset app wrong attempts Toggle(NSLocalizedString("_reset_wrong_passcode_", comment: ""), isOn: $model.resetWrongAttempts) .onChange(of: model.resetWrongAttempts) { _ in @@ -137,6 +133,14 @@ struct NCSettingsView: View { } }) + Section { + /// Privacy screen + Toggle(NSLocalizedString("_privacy_screen_", comment: ""), isOn: $model.privacyScreen) + .onChange(of: model.privacyScreen) { _ in + model.updatePrivacyScreenSetting() + } + } + /// Display Section(header: Text(NSLocalizedString("_display_", comment: "")), content: { NavigationLink(destination: LazyView { @@ -287,6 +291,13 @@ struct NCSettingsView: View { Text(model.footerApp + model.footerServer + model.footerSlogan) }) } + .transition(.scale) + .sheet(isPresented: $showPasscode) { + SetupPasscodeView(isLockActive: $model.isLockActive) + } + .sheet(isPresented: $showChangePasscode) { + SetupPasscodeView(isLockActive: $model.isLockActive, changePasscode: true) + } .navigationBarTitle(NSLocalizedString("_settings_", comment: "")) .defaultViewModifier(model) } diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index c087da0415..75295cb53a 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -60,7 +60,7 @@ "_destination_" = "Destination"; "_ok_" = "OK"; "_beta_version_" = "Beta version"; -"_function_in_testing_" = "Function in testing, please send information about any problems you run into."; +"_function_in_testing_" = "Function is in testing, please send information about any problems you run into."; "_done_" = "Done"; "_clear_" = "Clear"; "_passcode_too_short_" = "Passcode too short, at least 4 characters required"; @@ -97,18 +97,18 @@ "_initialization_" = "Initialization"; "_experimental_" = "Experimental"; "_select_dir_media_tab_" = "Select as folder \"Media\""; -"_error_creation_file_" = "Oops! Could not create the file"; +"_error_creation_file_" = "Oops! Could not create the file."; "_save_path_" = "Save path"; "_save_settings_" = "Save settings"; "_mode_filename_" = "Filename mode"; -"_warning_owncloud_" = "You are connected to an ownCloud server. This is untested and unsupported, use at your own risk. To upgrade to Nextcloud, see https://nextcloud.com/migration"; -"_warning_unsupported_" = "You are using an unsupported version of Nextcloud. For the safety of your data we strongly recommend updating to the latest stable Nextcloud"; +"_warning_owncloud_" = "You are connected to an ownCloud server. This is untested and unsupported, use at your own risk. To upgrade to Nextcloud, see https://nextcloud.com/migration."; +"_warning_unsupported_" = "You are using an unsupported version of Nextcloud. For the safety of your data we strongly recommend updating to the latest stable Nextcloud."; "_restore_" = "Restore"; "_camera_roll_" = "Camera roll"; "_tap_here_to_change_" = "Tap here to change"; "_no_albums_" = "No albums"; -"_denied_album_" = "This app does not have access to \"Photos\", you can enable access in Privacy Settings"; -"_denied_camera_" = "This app does not have access to \"Camera\", you can enable access in Privacy Settings"; +"_denied_album_" = "This app does not have access to \"Photos\". You can enable access in Privacy Settings."; +"_denied_camera_" = "This app does not have access to the \"Camera\". You can enable access in Privacy Settings."; "_start_" = "Start"; "_force_start_" = "Force the start"; "_purchase_" = "Purchase"; @@ -144,7 +144,7 @@ "_file_conflict_desc_" = "Which files do you want to keep?\nIf you select both versions, the copied file will have a number added to its name."; "_file_conflict_new_" = "New files"; "_file_conflict_exists_" = "Already existing files"; -"_file_not_rewite_doc_" = "It is not possible to overwrite a document but only to create a new one"; +"_file_not_rewite_doc_" = "It is not possible to overwrite a document but only to create a new one."; "_move_or_copy_" = "Move or copy"; "_copy_" = "Copy"; "_now_" = "Now"; @@ -174,7 +174,7 @@ "_unlock_selected_files_" = "Unlock files"; "_locked_by_" = "Locked by %@"; "_file_locked_no_override_" = "This file is locked. It cannot be overridden."; -"_lock_no_permissions_selected_" = "Not allowed for some selected files or folders"; +"_lock_no_permissions_selected_" = "Not allowed for some selected files or folders."; /* Remove a file from a list, don't delete it entirely */ "_remove_file_" = "Remove file"; @@ -185,7 +185,6 @@ "_delete_photo_" = "Delete photo"; "_delete_video_" = "Delete video"; "_automatic_Download_Image_" = "Use images in full resolution"; -"_automatic_Download_Image_footer_" = "When viewing images always download, if not available locally, the images in full resolution"; "_size_" = "Size"; "_file_size_" = "Exported file size"; "_dimension_" = "Dimension"; @@ -228,9 +227,10 @@ "_more_action_title_" = "More Details"; "_wait_file_preparation_" = "Please wait, preparing file upload…"; "_wait_file_encryption_" = "Please wait, encrypting file…"; -"_passcode_counter_fail_" = "Too many failed attempts, please wait before reattempting"; +"_passcode_counter_fail_" = "Too many failed attempts, please wait before reattempting."; "_seconds_" = "seconds"; - +"_change_lock_passcode_" = "Change passcode"; +"_lock_cannot_disable_mdm_" = "Disabling the lock is not permitted by your configuration profile."; /* Background of the file listing view */ "_use_as_background_" = "Use it as a background"; @@ -384,7 +384,7 @@ "_trial_" = "Trial"; "_trial_expired_day_" = "Days remaining"; "_time_remaining_" = "%@ remaining"; -"_disableLocalCacheAfterUpload_footer_" = "After uploading the file, do not keep it in the local cache"; +"_disableLocalCacheAfterUpload_footer_" = "After uploading the file, do not keep it in the local cache."; "_disableLocalCacheAfterUpload_" = "Disable local cache"; "_autoupload_" = "Auto upload photos/videos"; "_autoupload_select_folder_" = "Select the \"Auto upload\" folder"; @@ -394,7 +394,7 @@ "_autoupload_videos_" = "Auto upload videos"; "_autoupload_favorites_" = "Auto upload favorites only"; "_autoupload_description_" = "New photos/videos will be automatically uploaded to your server."; -"_autoupload_description_background_" = "This option requires the use of GPS to trigger the detection of new photos/videos in the camera roll once the location changes significantly"; +"_autoupload_description_background_" = "This option requires the use of GPS to trigger the detection of new photos/videos in the camera roll once the location changes significantly."; "_autoupload_background_title_" = "Limitations"; "_autoupload_background_msg_" = "Due to iOS restrictions, it is not yet possible to perform background processes, unless GPS services are activated. Once the cell in the cellular network is changed, the system wakes up for a short time and checks for new photos to upload to the cloud."; "_autoupload_change_location_" = "Change folder"; @@ -472,9 +472,9 @@ "_location_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Location Services\""; "_access_photo_not_enabled_" = "Access to Photos not enabled"; "_access_photo_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Photo Access\""; -"_access_background_app_refresh_denied_" = "\"Background App Refresh\" is denied. Please enable it in \"Settings\" otherwise, new photos or videos will not be detected when the application is in the background"; -"_access_photo_location_not_enabled_" = "Access to Photos and Location not enabled"; -"_access_photo_location_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Photo Access\" and \"Location Services\""; +"_access_background_app_refresh_denied_" = "\"Background App Refresh\" is denied. Please enable it in \"Settings\" otherwise, new photos or videos will not be detected when the application is in the background."; +"_access_photo_location_not_enabled_" = "Access to Photos and Location not enabled."; +"_access_photo_location_not_enabled_msg_" = "Please go to \"Settings\" and enable \"Photo Access\" and \"Location Services\"."; "_tutorial_photo_view_" = "No photos or videos uploaded yet"; "_error_createsubfolders_upload_" = "Error creating subfolders"; "_activate_autoupload_" = "Enable auto upload"; @@ -489,11 +489,11 @@ "_a_day_ago_" = "a day ago"; "_days_ago_" = "%d days ago"; "_over_30_days_" = "over 30 days"; -"_connection_internet_offline_" = "The internet connection appears to be offline or Wi-Fi is required"; +"_connection_internet_offline_" = "The internet connection appears to be offline or Wi-Fi is required."; "_insert_password_" = "Enter password"; "_update_in_progress_" = "Version upgrade, please wait …"; "_forbidden_characters_" = "Forbidden characters: %@"; -"_cannot_send_mail_error_" = "No account set up, or wrong email address entered"; +"_cannot_send_mail_error_" = "No account is set up, or wrong email address entered."; "_open_url_error_" = "Cannot open the URL for this action"; "_photo_camera_" = "Photos"; "_media_" = "Media"; @@ -551,8 +551,8 @@ "_browse_images_" = "Browse images"; "_synchronized_folder_" = "Keep the folder synchronized"; "_remove_synchronized_folder_" = "Turn off the synchronization"; -"_synchronized_confirm_" = "After enabling the synchronization, all files in the folder will be synchronized with the server, continue?"; -"_offline_folder_confirm_" = "After enabling the offline folder, all files in it will be synchronized with the server, continue?"; +"_synchronized_confirm_" = "After enabling the synchronization, all files in the folder will be synchronized with the server. Continue?"; +"_offline_folder_confirm_" = "After enabling the offline folder, all files in it will be synchronized with the server. Continue?"; "_file_not_found_reload_" = "File not found, pull down to refresh"; "_title_section_download_" = "DOWNLOAD"; "_download_" = "Download"; @@ -602,7 +602,7 @@ "_search_this_folder_" = "Search in this folder"; "_search_all_folders_" = "Search in all folders"; "_search_sub_folder_" = "Search here and in subfolders"; -"_theming_is_light_" = "Server theming too brightly coloured, not applicable"; +"_theming_is_light_" = "Server theming too brightly coloured, not applicable."; "_cancel_all_task_" = "Cancel all transfers"; "_status_wait_download_" = "Waiting for download"; "_status_in_download_" = "In download"; @@ -647,13 +647,13 @@ "_date_" = "Date"; "_share_title_" = "Share"; "_add_sharee_" = "Add users or groups"; -"_add_sharee_footer_" = "You can share this resource by adding users or groups. To remove a share, remove all users and groups"; +"_add_sharee_footer_" = "You can share this resource by adding users or groups. To remove a share, remove all users and groups."; "_find_sharee_title_" = "Search"; "_find_sharee_" = "Search for user or group …"; -"_find_sharee_footer_" = "Enter part of the name of the user or group to search for (at least 2 characters) followed by \"Return\", select the users that should be allowed to access the share followed by \"Done\" to confirm"; +"_find_sharee_footer_" = "Enter part of the name of the user or group to search for (at least 2 characters) followed by \"Return\", select the users that should be allowed to access the share followed by \"Done\" to confirm."; "_user_is_group_" = "(Group)"; "_direct_sharee_title_" = "Share"; -"_direct_sharee_footer_" = "If you already know the name, enter it, then select the share type and press \"Done\" to confirm"; +"_direct_sharee_footer_" = "If you already know the name, enter it, then select the share type and press \"Done\" to confirm."; "_direct_sharee_" = "Enter the username …"; "_user_sharee_footer_" = "Tap to change permissions"; "_share_type_title_" = "Type of share"; @@ -661,7 +661,7 @@ "_share_type_group_" = "Group"; "_share_type_remote_" = "Remote"; "_enforce_password_protection_" = "Enforce password protection"; -"_password_obligatory_" = "Enforce password protection enabled, password obligatory"; +"_password_obligatory_" = "Enforce password protection enabled, password obligatory."; "_shared_with_you_by_" = "Shared with you by"; "_shareLinksearch_placeholder_" = "Name, email, or Federated Cloud ID …"; "_shareLinksearch_mail_placeholder_" = "Type a name or an email and press Search"; @@ -687,9 +687,9 @@ "_share_can_download_" = "Allow download"; "_share_unshare_" = "Unshare"; "_share_internal_link_" = "Internal link"; -"_share_internal_link_des_" = "Only works for users with access to this file/folder"; -"_share_reshare_disabled_" = "You are not allowed to reshare this file/folder"; -"_share_reshare_restricted_" = "Note: You only have limited permission to reshare this file/folder"; +"_share_internal_link_des_" = "Only works for users with access to this file/folder."; +"_share_reshare_disabled_" = "You are not allowed to reshare this file/folder."; +"_share_reshare_restricted_" = "Note: You only have limited permission to reshare this file/folder."; "_no_transfer_" = "No transfers yet"; "_no_transfer_sub_" = "Uploads and downloads from this device will show up here"; @@ -698,7 +698,7 @@ "_transfers_" = "Transfers"; "_activity_" = "Activity"; "_activity_file_not_present_" = "File no longer present"; -"_trash_file_not_found_" = "It seems that the file is not in the trash. Go to the trash to update it and try again"; +"_trash_file_not_found_" = "It seems that the file is not in the Trash. Go to the Trash to update it and try again."; "_list_shares_" = "Shares"; "_list_shares_no_files_" = "No shares yet"; "_tutorial_list_shares_view_" = "Files and folders you share will show up here"; @@ -718,7 +718,7 @@ "_enter_filename_" = "Enter filename …"; "_default_preview_filename_footer_" = "Example preview of filename: IMG_0001.JPG"; "_filename_header_" = "Enter filename"; -"_preview_filename_" = "Example preview of filename. You can use the mask %@ for date/time"; +"_preview_filename_" = "Example preview of filename. You can use the mask %@ for date/time."; "_add_filenametype_" = "Specify type in filename"; "_filenametype_photo_video_" = "Photo/Video"; "_maintain_original_filename_" = "Maintain original filename"; @@ -736,7 +736,7 @@ "_e2e_server_disabled_" = "End-to-end encryption app disabled on server"; "_e2e_settings_view_passphrase_" = "All 12 words together make a very strong password, letting only you view and make use of your encrypted files. Please write it down and keep it somewhere safe."; "_e2e_settings_read_passphrase_" = "Read passphrase"; -"_e2e_settings_lock_not_active_" = "Lock not active, go back to \"Settings\" and activate it"; +"_e2e_settings_lock_not_active_" = "Lock not active. Go back to \"Settings\" and activate it."; "_e2e_settings_the_passphrase_is_" = "The passphrase is:"; "_e2e_passphrase_request_title_" = "Request passphrase"; "_e2e_passphrase_request_message_" = "Insert the 12 words"; @@ -744,9 +744,9 @@ "_e2e_settings_remove_message_" = "Confirm removal of encryption along with the passphrase."; "_e2e_set_folder_encrypted_" = "Encrypt"; "_e2e_remove_folder_encrypted_" = "Decrypt"; -"_e2e_goto_settings_for_enable_" = "This is an encrypted directory, go to \"Settings\" and enable end-to-end encryption"; -"_e2e_error_" = "An internal end-to-end encryption error occurred"; -"_e2e_in_upload_" = "Upload in progress, please wait for all files to be transferred"; +"_e2e_goto_settings_for_enable_" = "This is an encrypted directory, go to \"Settings\" and enable end-to-end encryption."; +"_e2e_error_" = "An internal end-to-end encryption error occurred."; +"_e2e_in_upload_" = "Upload in progress, please wait for all files to be transferred."; "_scans_document_" = "Scan document"; "_scanned_images_" = "Scanned images"; "_scan_document_pdf_page_" = "Page"; @@ -802,63 +802,63 @@ "_sign_up_" = "Sign up with provider"; "_host_your_own_server" = "Host your own server"; "_unauthorized_" = "Unauthorized"; -"_bad_username_password_" = "Wrong username or password"; -"_cancelled_by_user" = "Transfer canceled"; -"_error_folder_destiny_is_the_same_" = "It is not possible to move the folder into itself"; -"_error_not_permission_" = "You don't have permission to complete the operation"; -"_error_path_" = "Unable to open this file or folder. Please make sure it exists"; -"_file_upload_not_exitst_" = "The file that you want to upload does not exist"; -"_forbidden_characters_from_server_" = "The name contains at least one invalid character"; -"_error_not_modified_" = "Resource not modified"; -"_not_connected_internet_" = "Server connection error"; -"_not_possible_connect_to_server_" = "It is not possible to connect to the server at this time"; -"_not_possible_create_folder_" = "Folder could not be created"; -"_server_down_" = "Could not establish contact with server"; -"_time_out_" = "Timeout, try again"; -"_unknow_response_server_" = "Unexpected response from server"; -"_user_authentication_required_" = "User authentication required"; -"_file_directory_locked_" = "File or directory locked"; -"_ssl_certificate_untrusted_" = "The certificate for this server is invalid"; -"_ssl_certificate_changed_" = "The certificate for this server seems to have changed"; -"_internal_server_" = "Internal server error"; -"_file_already_exists_" = "Could not complete the operation, a file with the same name exists"; -"_file_folder_not_exists_" = "The source file wasn't found at the specified path"; -"_folder_contents_nochanged_" = "The folder contents have not changed"; -"_images_invalid_converted_" = "The image is invalid and cannot be converted to a thumbnail"; -"_method_not_expected_" = "Unexpected request method"; -"_reauthenticate_user_" = "Access expired, log in again"; -"_server_error_retry_" = "The server is temporarily unavailable"; -"_too_many_files_" = "Too many files would be involved in this operation"; -"_too_many_request_" = "Sending too many requests caused the rate limit to be reached"; -"_user_over_quota_" = "Storage quota is reached"; -"_ssl_connection_error_" = "Connection SSL error, try again"; -"_bad_request_" = "Bad request"; -"_webdav_locked_" = "WebDAV Locked: Trying to access locked resource"; -"_error_user_not_available_" = "The user is no longer available"; -"_server_response_error_" = "Server response content error"; -"_no_nextcloud_found_" = "Server not found"; -"_error_decompressing_" = "Error during decompressing. Unknown compression method or the file is corrupt"; -"_error_json_decoding_" = "Serious internal error in decoding metadata (The data couldn't be read because it isn't in the correct format.)"; -"_error_check_remote_user_" = "Server responded with an error. Please log in again"; -"_request_entity_too_large_" = "The file is too large"; -"_not_possible_download_" = "It is not possible to download the file"; -"_not_possible_upload_" = "It is not possible to upload the file"; -"_error_files_upload_" = "Error uploading files"; -"_method_not_allowed_" = "The requested method is not supported"; -"_invalid_url_" = "Invalid server URL"; -"_invalid_literal_" = "Invalid search string"; -"_invalid_date_format_" = "Invalid date format"; -"_invalid_data_format_" = "Invalid data format"; -"_error_decode_xml_" = "Invalid response, error decode XML"; -"_internal_generic_error_" = "internal error"; -"_editor_unknown_" = "Failed to open file: Editor is unknown"; -"_err_file_not_found_" = "File not found, removed"; -"_err_e2ee_app_version_" = "The app version of end-to-end encryption is not compatible with the server, please update your server"; -"_err_permission_microphone_" = "Please allow Microphone usage from Settings"; -"_err_permission_photolibrary_" = "Please allow Photos from Settings"; -"_err_permission_locationmanager_" = "Please allow Location - Always from Settings"; -"_qrcode_not_authorized_" = "This app is not authorized to use Back Camera"; -"_qrcode_not_supported_" = "QR code not supported by the current device"; +"_bad_username_password_" = "Wrong username or password."; +"_cancelled_by_user" = "Transfer canceled."; +"_error_folder_destiny_is_the_same_" = "It is not possible to move the folder into itself."; +"_error_not_permission_" = "You don't have permission to complete the operation."; +"_error_path_" = "Unable to open this file or folder. Please make sure it exists."; +"_file_upload_not_exitst_" = "The file that you want to upload does not exist."; +"_forbidden_characters_from_server_" = "The name contains at least one invalid character."; +"_error_not_modified_" = "Resource not modified."; +"_not_connected_internet_" = "Server connection error."; +"_not_possible_connect_to_server_" = "It is not possible to connect to the server at this time."; +"_not_possible_create_folder_" = "Folder could not be created."; +"_server_down_" = "Could not establish contact with server."; +"_time_out_" = "Timeout, try again."; +"_unknow_response_server_" = "Unexpected response from server."; +"_user_authentication_required_" = "User authentication required."; +"_file_directory_locked_" = "File or directory locked."; +"_ssl_certificate_untrusted_" = "The certificate for this server is invalid."; +"_ssl_certificate_changed_" = "The certificate for this server seems to have changed."; +"_internal_server_" = "Internal server error."; +"_file_already_exists_" = "Could not complete the operation, a file with the same name exists."; +"_file_folder_not_exists_" = "The source file wasn't found at the specified path."; +"_folder_contents_nochanged_" = "The folder contents have not changed."; +"_images_invalid_converted_" = "The image is invalid and cannot be converted to a thumbnail."; +"_method_not_expected_" = "Unexpected request method."; +"_reauthenticate_user_" = "Access expired, log in again."; +"_server_error_retry_" = "The server is temporarily unavailable."; +"_too_many_files_" = "Too many files would be involved in this operation."; +"_too_many_request_" = "Sending too many requests caused the rate limit to be reached."; +"_user_over_quota_" = "Storage quota is reached."; +"_ssl_connection_error_" = "Connection SSL error, try again."; +"_bad_request_" = "Bad request."; +"_webdav_locked_" = "WebDAV Locked: Trying to access locked resource."; +"_error_user_not_available_" = "The user is no longer available."; +"_server_response_error_" = "Server response content error."; +"_no_nextcloud_found_" = "Server not found."; +"_error_decompressing_" = "Error during decompressing. Unknown compression method or the file is corrupt."; +"_error_json_decoding_" = "Serious internal error in decoding metadata (The data couldn't be read because it isn't in the correct format)."; +"_error_check_remote_user_" = "Server responded with an error. Please log in again."; +"_request_entity_too_large_" = "The file is too large."; +"_not_possible_download_" = "It is not possible to download the file."; +"_not_possible_upload_" = "It is not possible to upload the file."; +"_error_files_upload_" = "Error uploading files."; +"_method_not_allowed_" = "The requested method is not supported."; +"_invalid_url_" = "Invalid server URL."; +"_invalid_literal_" = "Invalid search string."; +"_invalid_date_format_" = "Invalid date format."; +"_invalid_data_format_" = "Invalid data format."; +"_error_decode_xml_" = "Invalid response, error decode XML."; +"_internal_generic_error_" = "internal error."; +"_editor_unknown_" = "Failed to open file: Editor is unknown."; +"_err_file_not_found_" = "File not found, removed."; +"_err_e2ee_app_version_" = "The app version of end-to-end encryption is not compatible with the server, please update your server.."; +"_err_permission_microphone_" = "Please allow Microphone usage from Settings."; +"_err_permission_photolibrary_" = "Please allow Photos from Settings."; +"_err_permission_locationmanager_" = "Please allow Location - Always from Settings."; +"_qrcode_not_authorized_" = "This app is not authorized to use the Back Camera."; +"_qrcode_not_supported_" = "QR code not supported by the current device."; "_create_voice_memo_" = "Create voice memo"; "_voice_memo_start_" = "Tap to start"; "_voice_memo_stop_" = "Tap to stop"; @@ -879,7 +879,7 @@ "_overwrite_original_" = "Overwrite original"; "_save_as_copy_" = "Save as copy"; "_discard_changes_" = "Close and discard changes"; -"_message_disable_overwrite_livephoto_" = "This image is a Live Photo, overwrite will not be possible"; +"_message_disable_overwrite_livephoto_" = "This image is a Live Photo, overwrite will not be possible."; "_discard_changes_info_" = "Your changes will be discarded."; "_delete_files_desc_" = "Delete files to free up space"; "_delete_old_files_" = "Delete, in the cache, all files older than"; @@ -931,40 +931,40 @@ "_description_dashboardwidget_" = "Having the Dashboard always at your fingertips has never been easier."; "_description_fileswidget_" = "View your recent files and use the toolbar to speed up your operations."; "_description_toolbarwidget_" = "A toolbar to speed up your operations."; -"_no_data_available_" = "No data available"; -"_widget_available_nc25_" = "Widget only available starting with server version 25"; -"_keep_running_" = "Keep the app running for a better user experience"; +"_no_data_available_" = "No data available."; +"_widget_available_nc25_" = "Widget only available starting with server version 25."; +"_keep_running_" = "Keep the app running for a better user experience."; "_recent_activity_" = "Recent activity"; "_title_lockscreenwidget_" = "Status"; -"_description_lockscreenwidget_" = "Keep an eye on available space and recent activity"; +"_description_lockscreenwidget_" = "Keep an eye on available space and recent activity."; "_no_items_" = "No items"; -"_check_back_later_" = "Check back later"; +"_check_back_later_" = "Check back later."; "_exporting_video_" = "Exporting video … Tap to cancel."; "_apps_nextcloud_detect_" = "Detected %@ apps"; "_add_existing_account_" = "Other %@ Apps has been detected, do you want to add an existing account?"; "_status_in_progress_" = "Status reading in progress …"; -"_status_e2ee_on_server_" = "The end-to-end encryption is already configured in the server but not yet enabled on this device"; +"_status_e2ee_on_server_" = "The end-to-end encryption is already configured in the server but not yet enabled on this device."; "_status_e2ee_not_setup_" = "The end-to-end encryption is not yet configured on the server"; "_status_e2ee_configured_" = "The end-to-end encryption is correctly configured"; -"_e2ee_set_as_offline_" = "Not allowed for encrypted files or folders"; +"_e2ee_set_as_offline_" = "Not allowed for encrypted files or folders."; "_change_upload_filename_" = "Do you want to save the file with a different name?"; "_save_file_as_" = "Save file as %@"; -"_password_ascii_" = "The password cannot contain special characters"; +"_password_ascii_" = "The password cannot contain special characters."; "_calendar_contacts_" = "Calendar and Contacts"; "_mobile_config_" = "Download the configuration profile"; -"_calendar_contacts_footer_warning_" = "Configuration profile can only be downloaded if Safari is set as default browser"; +"_calendar_contacts_footer_warning_" = "Configuration profile can only be downloaded if Safari is set as default browser."; "_calendar_contacts_footer_" = "After downloading the profile you can install it from Settings."; "_preview_" = "Preview"; "_crop_" = "Crop"; "_modify_image_desc_" = "Tap on a file to modify or rename."; -"_message_disable_livephoto_" = "This image is a Live Photo, changing it will lose the Live Photo effect"; +"_message_disable_livephoto_" = "This image is a Live Photo, changing it will lose the Live Photo effect."; "_enable_livephoto_" = "Enable Live Photo"; "_disable_livephoto_" = "Disable Live Photo"; "_undo_modify_" = "Undo modifying"; -"_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode"; -"_disableFilesApp_" = "Files app cannot be used because it is disabled"; +"_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode."; +"_disableFilesApp_" = "Files app cannot be used because it is disabled."; "_reset_application_done_" = "Reset application, done."; -"_rename_already_exists_" = "A file with this name already exists"; +"_rename_already_exists_" = "A file with this name already exists."; "_created_" = "Created"; "_recipients_" = "Recipients"; "_are_sure_" = "Are you sure?"; @@ -977,11 +977,11 @@ "_deletion_progess_" = "Deletion in progress"; "_copying_progess_" = "Copying in progress"; "_moving_progess_" = "Moving in progress"; -"_chunk_enough_memory_" = "It seems there is not enough space to send the file"; -"_chunk_create_folder_" = "The file could not be sent, please check the server log"; -"_chunk_files_null_" = "The file for sending could not be created"; -"_chunk_file_null_" = "The file could not be sent"; -"_chunk_move_" = "The sent file could not be reassembled, please check the server log"; +"_chunk_enough_memory_" = "It seems there is not enough space to send the file."; +"_chunk_create_folder_" = "The file could not be sent, please check the server log."; +"_chunk_files_null_" = "The file for sending could not be created."; +"_chunk_file_null_" = "The file could not be sent."; +"_chunk_move_" = "The sent file could not be reassembled, please check the server log."; "_download_image_" = "Download image"; "_download_video_" = "Download video"; "_reset_wrong_passcode_" = "Reset application"; @@ -1010,9 +1010,9 @@ "_while_charging_" = "While charging"; "_additional_options_" = "Additional options"; "_keep_screen_awake_" = "Keep screen awake\nwhile transferring files"; -"_error_not_found_" = "The requested resource could not be found"; -"_error_conflict_" = "The request could not be completed due to a conflict with the current state of the resource"; -"_error_precondition_" = "The server does not meet one of the preconditions that the requester"; +"_error_not_found_" = "The requested resource could not be found."; +"_error_conflict_" = "The request could not be completed due to a conflict with the current state of the resource."; +"_error_precondition_" = "The server does not meet one of the preconditions that the requester."; // Video "_select_trace_" = "Select the trace"; @@ -1021,17 +1021,17 @@ "_downloading_" = "Downloading"; "_download_error_" = "Download error"; "_subtitle_" = "Subtitle"; -"_dts_to_ac3_" = "The DTS is not supported, it requires a conversion to Dolby Digital"; +"_dts_to_ac3_" = "DTS is not supported, it requires a conversion to Dolby Digital."; "_reuired_conversion_" = "This video takes a long time to convert."; "_stay_app_foreground_" = "Keep the app in the foreground …"; "_conversion_available_" = "The conversion is always available on menu"; "_video_format_not_recognized_" = "This video needs to be processed to be played, do you want to do it now?"; "_video_must_download_" = "This video needs to be downloaded and processed to be played, do you want to do it now?"; -"_conversion_max_compatibility_" = "Max compatibility, the conversion can take much longer"; -"_video_tap_for_close_" = "A slight pressure to close the processing"; -"_subtitle_not_found_" = "Subtitle not found"; +"_conversion_max_compatibility_" = "Max compatibility is set, the conversion can take much longer"; +"_video_tap_for_close_" = "A slight pressure to close the processing."; +"_subtitle_not_found_" = "Subtitle not found."; "_disable_" = "Disable"; -"_subtitle_not_dowloaded_" = "There are subtitles not downloaded locally"; +"_subtitle_not_dowloaded_" = "There are subtitles not downloaded locally."; "_user_" = "User"; "_add_subtitle_" = "Add an external subtitle"; "_add_audio_" = "Add an external audio"; @@ -1040,8 +1040,8 @@ "_upload_background_msg_" = "Files upload in progress …"; "_create_folder_error_" = "An error has occurred while creating the folder:\n%@.\n\nPlease resolve the issue as soon as possible.\n\nAll uploads are suspended until the problem is resolved.\n"; "_rename_file_error_" = "An error has occurred while renaming the file:\n%@."; -"_creating_dir_progress_" = "Creating directories in progress … keep the application active"; -"_creating_db_photo_progress" = "Creating photo archive in progress … keep the application active"; +"_creating_dir_progress_" = "Creating directories in progress … keep the application active."; +"_creating_db_photo_progress" = "Creating photo archive in progress … keep the application active."; "_account_unauthorized_" = "Warning, %@, you are not authorized, your account has been deleted, if you have changed your password, re-authenticate."; "_folder_offline_desc_" = "Even without an internet connection, you can organize your folders, create files. Once you're back online, your pending actions will automatically sync."; "_offline_not_allowed_" = "This operation is not allowed in offline mode"; @@ -1116,7 +1116,7 @@ "_client_cert_wrong_password_" = "Sorry, you entered an invalid password"; // MARK: Login poll -"_poll_desc_" = "Please complete the log in process in your browser"; +"_poll_desc_" = "Please complete the log in process in your browser."; // MARK: File name validator "_file_name_validator_error_ends_with_space_period_" = "Name must not contain spaces at the beginning or end."; From ca2731a25232aefe20f0ea93c5bda8d4ddb380f9 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 15 Nov 2024 10:49:39 +0100 Subject: [PATCH 07/25] WIP Signed-off-by: Milen Pivchev --- iOSClient/Settings/Settings/NCSettingsView.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/iOSClient/Settings/Settings/NCSettingsView.swift b/iOSClient/Settings/Settings/NCSettingsView.swift index cfff9db421..f6084d9c6d 100644 --- a/iOSClient/Settings/Settings/NCSettingsView.swift +++ b/iOSClient/Settings/Settings/NCSettingsView.swift @@ -90,10 +90,10 @@ struct NCSettingsView: View { Section(content: { - if !model.isLockActive { + if model.isLockActive { Group { // Change passcode - if model.enableTouchID { + if !model.enableTouchID { Button(action: { showChangePasscode.toggle() }, label: { @@ -140,6 +140,7 @@ struct NCSettingsView: View { model.updatePrivacyScreenSetting() } } + .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) /// Display Section(header: Text(NSLocalizedString("_display_", comment: "")), content: { From b7be621a2d46d4810da38477827c386cf463999f Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 15 Nov 2024 11:56:56 +0100 Subject: [PATCH 08/25] Finish Signed-off-by: Milen Pivchev --- iOSClient/Main/NCMainTabBarController.swift | 2 +- .../Settings/Settings/NCSettingsView.swift | 40 ++++++++----------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/iOSClient/Main/NCMainTabBarController.swift b/iOSClient/Main/NCMainTabBarController.swift index e516be9e85..c8028829dc 100644 --- a/iOSClient/Main/NCMainTabBarController.swift +++ b/iOSClient/Main/NCMainTabBarController.swift @@ -57,7 +57,7 @@ class NCMainTabBarController: UITabBarController { super.viewDidAppear(animated) previousIndex = selectedIndex - if NCBrandOptions.shared.enforce_protection && !NCKeychain().presentPasscode { + if NCBrandOptions.shared.enforce_protection && NCKeychain().passcode.isEmptyOrNil { let vc = UIHostingController(rootView: SetupPasscodeView(isLockActive: .constant(false))) vc.isModalInPresentation = true diff --git a/iOSClient/Settings/Settings/NCSettingsView.swift b/iOSClient/Settings/Settings/NCSettingsView.swift index f6084d9c6d..809d3792a6 100644 --- a/iOSClient/Settings/Settings/NCSettingsView.swift +++ b/iOSClient/Settings/Settings/NCSettingsView.swift @@ -59,7 +59,6 @@ struct NCSettingsView: View { } .font(.system(size: 16)) } - }, header: { }, footer: { Text(NSLocalizedString("_autoupload_description_", comment: "")) }) @@ -88,23 +87,19 @@ struct NCSettingsView: View { Text(NSLocalizedString("_lock_cannot_disable_mdm_", comment: "")) }) - Section(content: { - - if model.isLockActive { + if model.isLockActive { + Section(content: { Group { // Change passcode - if !model.enableTouchID { - Button(action: { - showChangePasscode.toggle() - }, label: { - VStack { - Text(NSLocalizedString("_change_lock_passcode_", comment: "")) - .tint(Color(NCBrandColor.shared.textColor)) - } - .font(.system(size: 16)) - }) - .transition(.scale) - } + Button(action: { + showChangePasscode.toggle() + }, label: { + VStack { + Text(NSLocalizedString("_change_lock_passcode_", comment: "")) + .tint(Color(NCBrandColor.shared.textColor)) + } + .font(.system(size: 16)) + }) /// Enable Touch ID Toggle(NSLocalizedString("_enable_touch_face_id_", comment: ""), isOn: $model.enableTouchID) .onChange(of: model.enableTouchID) { _ in @@ -121,17 +116,15 @@ struct NCSettingsView: View { .onChange(of: model.resetWrongAttempts) { _ in model.updateResetWrongAttemptsSetting() } + .font(.system(size: 16)) } - .font(.system(size: 16)) - .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) - } - }, footer: { - if model.isLockActive { + }, footer: { Text(NSLocalizedString("_lock_protection_no_screen_footer_", comment: "") + "\n" + String(format: NSLocalizedString("_reset_wrong_passcode_desc_", comment: ""), NCBrandOptions.shared.resetAppPasscodeAttempts)) .font(.system(size: 12)) .lineSpacing(1) - } - }) + }) + .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) + } Section { /// Privacy screen @@ -292,7 +285,6 @@ struct NCSettingsView: View { Text(model.footerApp + model.footerServer + model.footerSlogan) }) } - .transition(.scale) .sheet(isPresented: $showPasscode) { SetupPasscodeView(isLockActive: $model.isLockActive) } From 075e2c7362517efdbb4faca5135f79fe17242e26 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Nov 2024 11:35:21 +0100 Subject: [PATCH 09/25] WIP Signed-off-by: Milen Pivchev --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 75295cb53a..d1a5535be2 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -230,7 +230,7 @@ "_passcode_counter_fail_" = "Too many failed attempts, please wait before reattempting."; "_seconds_" = "seconds"; "_change_lock_passcode_" = "Change passcode"; -"_lock_cannot_disable_mdm_" = "Disabling the lock is not permitted by your configuration profile."; +"_lock_cannot_disable_mdm_" = "Disabling the passcode lock is not permitted by your configuration profile."; /* Background of the file listing view */ "_use_as_background_" = "Use it as a background"; @@ -305,8 +305,8 @@ "_passcode_" = "Password"; "_enter_password_" = "Enter password …"; "_lock_" = "Lock"; -"_lock_active_" = "Lock: On"; -"_lock_not_active_" = "Lock: Off"; +"_lock_active_" = "Passcode Lock: On"; +"_lock_not_active_" = "Passcode Lock: Off"; "_lock_protection_no_screen_" = "Do not ask at startup"; "_lock_protection_no_screen_footer_" = "Use \"Do not ask at startup\" for the encryption option."; "_enable_touch_face_id_" = "Enable Touch/Face ID"; From baf787c4e081cbf70916bd749a8fcdbb98c84044 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 19 Nov 2024 11:49:06 +0100 Subject: [PATCH 10/25] WIP Signed-off-by: Milen Pivchev --- iOSClient/Menu/NCMenu.swift | 4 ++-- iOSClient/SceneDelegate.swift | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/iOSClient/Menu/NCMenu.swift b/iOSClient/Menu/NCMenu.swift index a9614dcc4c..699b5a7bdb 100644 --- a/iOSClient/Menu/NCMenu.swift +++ b/iOSClient/Menu/NCMenu.swift @@ -134,11 +134,11 @@ class NCMenu: UITableViewController { extension NCMenu: FloatingPanelControllerDelegate { func floatingPanel(_ fpc: FloatingPanelController, layoutFor size: CGSize) -> FloatingPanelLayout { - return NCMenuFloatingPanelLayout(actionsHeight: 600) + return NCMenuFloatingPanelLayout(actionsHeight: self.actions.listHeight) } func floatingPanel(_ fpc: FloatingPanelController, layoutFor newCollection: UITraitCollection) -> FloatingPanelLayout { - return NCMenuFloatingPanelLayout(actionsHeight: 600) + return NCMenuFloatingPanelLayout(actionsHeight: self.actions.listHeight) } func floatingPanel(_ fpc: FloatingPanelController, animatorForDismissingWith velocity: CGVector) -> UIViewPropertyAnimator { diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index 47684c9e6e..cb01307d5d 100644 --- a/iOSClient/SceneDelegate.swift +++ b/iOSClient/SceneDelegate.swift @@ -65,9 +65,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window?.makeKeyAndVisible() /// Set the ACCOUNT controller.account = activeTableAccount.account - -// let vc = UIHostingController(rootView: PasscodeView(isLockActive: .constant(true))) -// controller.present(vc, animated: true) } } else { NCKeychain().removeAll() From 84252b95902ff762b521300f49690298b6d963d2 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 26 Nov 2024 10:22:02 +0100 Subject: [PATCH 11/25] WIP Signed-off-by: Milen Pivchev --- iOSClient/Settings/Settings/NCSettingsView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/iOSClient/Settings/Settings/NCSettingsView.swift b/iOSClient/Settings/Settings/NCSettingsView.swift index 809d3792a6..a81bf35b6f 100644 --- a/iOSClient/Settings/Settings/NCSettingsView.swift +++ b/iOSClient/Settings/Settings/NCSettingsView.swift @@ -84,7 +84,9 @@ struct NCSettingsView: View { }, header: { Text(NSLocalizedString("_privacy_", comment: "")) }, footer: { - Text(NSLocalizedString("_lock_cannot_disable_mdm_", comment: "")) + if NCBrandOptions.shared.enforce_protection { + Text(NSLocalizedString("_lock_cannot_disable_mdm_", comment: "")) + } }) if model.isLockActive { From 280eb62d798fe199523c5ba704e1f941eea05d26 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Tue, 26 Nov 2024 13:54:50 +0100 Subject: [PATCH 12/25] WIP Signed-off-by: Milen Pivchev --- .../Advanced/NCSettingsAdvancedView.swift | 73 ++++++++----------- .../Settings/Settings/NCSettingsView.swift | 25 ------- .../en.lproj/Localizable.strings | 22 +++--- 3 files changed, 43 insertions(+), 77 deletions(-) diff --git a/iOSClient/Settings/Advanced/NCSettingsAdvancedView.swift b/iOSClient/Settings/Advanced/NCSettingsAdvancedView.swift index 20c54be610..03120ae4f7 100644 --- a/iOSClient/Settings/Advanced/NCSettingsAdvancedView.swift +++ b/iOSClient/Settings/Advanced/NCSettingsAdvancedView.swift @@ -42,18 +42,16 @@ struct NCSettingsAdvancedView: View { .onChange(of: model.showHiddenFiles) { _ in model.updateShowHiddenFiles() } - .font(.system(size: 16)) - }, footer: { }) + }) /// file name Section(content: { NavigationLink(destination: LazyView { NCFileNameView(model: NCFileNameModel(controller: model.controller)) }) { Text(NSLocalizedString("_filenamemask_", comment: "")) - .font(.system(size: 16)) } }, footer: { - Text(NSLocalizedString("_filenamemask_footer_", comment: "")) + Text(fileNameMaskFooter) }) /// Most Compatible & Enable Live Photo Section(content: { @@ -61,22 +59,21 @@ struct NCSettingsAdvancedView: View { .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) .onChange(of: model.mostCompatible) { _ in model.updateMostCompatible() - } - .font(.system(size: 16)) + } + }, footer: { + Text(NSLocalizedString("_format_compatibility_footer_", comment: "")) + }) + + Section(content: { Toggle(NSLocalizedString("_upload_mov_livephoto_", comment: ""), isOn: $model.livePhoto) .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) .onChange(of: model.livePhoto) { _ in model.updateLivePhoto() - } - .font(.system(size: 16)) + } }, footer: { - ( - Text(NSLocalizedString("_format_compatibility_footer_", comment: "")) - + - Text(NSLocalizedString("_upload_mov_livephoto_footer_", comment: "")) - ).font(.system(size: 12)) - .multilineTextAlignment(.leading) + Text(NSLocalizedString("_upload_mov_livephoto_footer_", comment: "")) }) + /// Remove from Camera Roll Section(content: { Toggle(NSLocalizedString("_remove_photo_CameraRoll_", comment: ""), isOn: $model.removeFromCameraRoll) @@ -84,11 +81,8 @@ struct NCSettingsAdvancedView: View { .onChange(of: model.removeFromCameraRoll) { _ in model.updateRemoveFromCameraRoll() } - .font(.system(size: 16)) }, footer: { Text(NSLocalizedString("_remove_photo_CameraRoll_desc_", comment: "")) - .font(.system(size: 12)) - .multilineTextAlignment(.leading) }) /// Section : Files App if !NCBrandOptions.shared.disable_openin_file { @@ -98,11 +92,8 @@ struct NCSettingsAdvancedView: View { .onChange(of: model.appIntegration) { _ in model.updateAppIntegration() } - .font(.system(size: 16)) }, footer: { Text(NSLocalizedString("_disable_files_app_footer_", comment: "")) - .font(.system(size: 12)) - .multilineTextAlignment(.leading) }) } /// Section: Privacy @@ -114,7 +105,6 @@ struct NCSettingsAdvancedView: View { model.updateCrashReporter() showCrashReporter.toggle() } - .font(.system(size: 16)) .alert(NSLocalizedString("_crashservice_title_", comment: ""), isPresented: $showCrashReporter, actions: { Button(NSLocalizedString("OK", comment: ""), role: .cancel) { model.exitNextCloud(ext: showCrashReporter) @@ -126,8 +116,6 @@ struct NCSettingsAdvancedView: View { Text(NSLocalizedString("_privacy_", comment: "")) }, footer: { Text(NSLocalizedString("_privacy_footer_", comment: "")) - .font(.system(size: 12)) - .multilineTextAlignment(.leading) }) } /// Section: Diagnostic LOG @@ -141,12 +129,10 @@ struct NCSettingsAdvancedView: View { Image(systemName: "doc.badge.gearshape") .resizable() .scaledToFit() - .font(Font.system(.body).weight(.light)) .frame(width: 25, height: 25) .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) Text(NSLocalizedString("_view_log_", comment: "")) } - .font(.system(size: 16)) }) .tint(Color(UIColor.label)) /// Set Log Level() @@ -155,7 +141,6 @@ struct NCSettingsAdvancedView: View { Text(level.displayText).tag(level) } } - .font(.system(size: 16)) .onChange(of: model.selectedLogLevel) { _ in model.updateSelectedLogLevel() } @@ -167,12 +152,10 @@ struct NCSettingsAdvancedView: View { Image(systemName: "xmark") .resizable() .scaledToFit() - .font(Font.system(.body).weight(.light)) .frame(width: 25, height: 15) .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) Text(NSLocalizedString("_clear_log_", comment: "")) } - .font(.system(size: 16)) }) .tint(Color(UIColor.label)) .alert(NSLocalizedString("_log_file_clear_alert_", comment: ""), isPresented: $model.logFileCleared) { @@ -191,12 +174,10 @@ struct NCSettingsAdvancedView: View { Image(systemName: "list.bullet") .resizable() .scaledToFit() - .font(Font.system(.body).weight(.light)) .frame(width: 25, height: 25) .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) Text(NSLocalizedString("_capabilities_", comment: "")) } - .font(.system(size: 16)) } }, header: { Text(NSLocalizedString("_capabilities_", comment: "")) @@ -212,11 +193,10 @@ struct NCSettingsAdvancedView: View { Text(interval.displayText).tag(interval) } } - .font(.system(size: 16)) - .pickerStyle(.automatic) - .onChange(of: model.selectedInterval) { _ in - model.updateSelectedInterval() - } + .pickerStyle(.automatic) + .onChange(of: model.selectedInterval) { _ in + model.updateSelectedInterval() + } Button(action: { showCacheAlert.toggle() }, label: { @@ -224,12 +204,10 @@ struct NCSettingsAdvancedView: View { Image(systemName: "xmark") .resizable() .scaledToFit() - .font(Font.system(.body).weight(.light)) .frame(width: 15, height: 15) .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) Text(NSLocalizedString("_clear_cache_", comment: "")) } - .font(.system(size: 16)) }) .tint(Color(UIColor.label)) .alert(NSLocalizedString("_want_delete_cache_", comment: ""), isPresented: $showCacheAlert) { @@ -242,7 +220,6 @@ struct NCSettingsAdvancedView: View { Text(NSLocalizedString("_delete_files_desc_", comment: "")) }, footer: { Text(model.footerTitle) - .font(.system(size: 12)) .multilineTextAlignment(.leading) }) /// Reset Application @@ -254,13 +231,11 @@ struct NCSettingsAdvancedView: View { Image(systemName: "xmark") .resizable() .scaledToFit() - .font(Font.system(.body).weight(.light)) .frame(width: 15, height: 15) .foregroundColor(Color(UIColor.systemRed)) Text(NSLocalizedString("_exit_", comment: "")) .foregroundColor(Color(UIColor.systemRed)) } - .font(.system(size: 16)) }) .tint(Color(UIColor.label)) .alert(NSLocalizedString("_want_exit_", comment: ""), isPresented: $showExitAlert) { @@ -275,13 +250,27 @@ struct NCSettingsAdvancedView: View { + Text("\n\n") ) - .font(.system(size: 12)) - .multilineTextAlignment(.leading) }) } .navigationBarTitle(NSLocalizedString("_advanced_", comment: "")) .defaultViewModifier(model) } + + private var fileNameMaskFooter: AttributedString { + let boldPart = NSLocalizedString("_filenamemask_format_", comment: "") + let localizedString = String( + format: NSLocalizedString("_filenamemask_footer_", comment: ""), + boldPart + ) + + var attributedString = AttributedString(localizedString) + + if let range = attributedString.range(of: boldPart) { + attributedString[range].font = .footnote.weight(.semibold) + } + + return attributedString + } } #Preview { diff --git a/iOSClient/Settings/Settings/NCSettingsView.swift b/iOSClient/Settings/Settings/NCSettingsView.swift index a81bf35b6f..6a23679989 100644 --- a/iOSClient/Settings/Settings/NCSettingsView.swift +++ b/iOSClient/Settings/Settings/NCSettingsView.swift @@ -52,12 +52,10 @@ struct NCSettingsView: View { Image(systemName: "photo.circle") .resizable() .scaledToFit() - .font(Font.system(.body).weight(.light)) .frame(width: 25, height: 25) .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) Text(NSLocalizedString("_settings_autoupload_", comment: "")) } - .font(.system(size: 16)) } }, footer: { Text(NSLocalizedString("_autoupload_description_", comment: "")) @@ -71,13 +69,11 @@ struct NCSettingsView: View { Image(systemName: model.isLockActive ? "lock" : "lock.open") .resizable() .scaledToFit() - .font(Font.system(.body).weight(.light)) .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) .frame(width: 20, height: 20) .opacity(NCBrandOptions.shared.enforce_protection ? 0.5 : 1) Text(model.isLockActive ? NSLocalizedString("_lock_active_", comment: "") : NSLocalizedString("_lock_not_active_", comment: "")) } - .font(.system(size: 16)) }) .tint(Color(NCBrandColor.shared.textColor)) .disabled(NCBrandOptions.shared.enforce_protection) @@ -100,7 +96,6 @@ struct NCSettingsView: View { Text(NSLocalizedString("_change_lock_passcode_", comment: "")) .tint(Color(NCBrandColor.shared.textColor)) } - .font(.system(size: 16)) }) /// Enable Touch ID Toggle(NSLocalizedString("_enable_touch_face_id_", comment: ""), isOn: $model.enableTouchID) @@ -118,12 +113,9 @@ struct NCSettingsView: View { .onChange(of: model.resetWrongAttempts) { _ in model.updateResetWrongAttemptsSetting() } - .font(.system(size: 16)) } }, footer: { Text(NSLocalizedString("_lock_protection_no_screen_footer_", comment: "") + "\n" + String(format: NSLocalizedString("_reset_wrong_passcode_desc_", comment: ""), NCBrandOptions.shared.resetAppPasscodeAttempts)) - .font(.system(size: 12)) - .lineSpacing(1) }) .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) } @@ -146,12 +138,10 @@ struct NCSettingsView: View { Image(systemName: "sun.max.circle") .resizable() .scaledToFit() - .font(Font.system(.body).weight(.light)) .frame(width: 20, height: 20) .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) Text(NSLocalizedString("_display_", comment: "")) } - .font(.system(size: 16)) } }) /// Calender & Contacts @@ -164,12 +154,10 @@ struct NCSettingsView: View { Image(systemName: "calendar.badge.plus") .resizable() .scaledToFit() - .font(Font.system(.body).weight(.light)) .frame(width: 25, height: 25) .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) Text(NSLocalizedString("_mobile_config_", comment: "")) } - .font(.system(size: 16)) }) .tint(Color(NCBrandColor.shared.textColor)) }, header: { @@ -177,10 +165,8 @@ struct NCSettingsView: View { }, footer: { VStack(alignment: .leading) { Text(NSLocalizedString("_calendar_contacts_footer_warning_", comment: "")) - .font(.system(size: 12)) Spacer() Text(NSLocalizedString("_calendar_contacts_footer_", comment: "")) - .font(.system(size: 12)) } }) @@ -188,7 +174,6 @@ struct NCSettingsView: View { /// Users Section(content: { Toggle(NSLocalizedString("_settings_account_request_", comment: ""), isOn: $model.accountRequest) - .font(.system(size: 16)) .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) .onChange(of: model.accountRequest, perform: { _ in model.updateAccountRequest() @@ -197,8 +182,6 @@ struct NCSettingsView: View { Text(NSLocalizedString("_users_", comment: "")) }, footer: { Text(NSLocalizedString("_users_footer_", comment: "")) - .font(.system(size: 12)) - .lineSpacing(1) }) /// E2EEncryption` Section if capabilities.capabilityE2EEEnabled && NCGlobal.shared.e2eeVersions.contains(capabilities.capabilityE2EEApiVersion) { @@ -213,12 +196,10 @@ struct NCSettingsView: View { Image(systemName: "gear") .resizable() .scaledToFit() - .font(Font.system(.body).weight(.light)) .frame(width: 25, height: 25) .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) Text(NSLocalizedString("_advanced_", comment: "")) } - .font(.system(size: 16)) } } /// `Information` Section @@ -235,7 +216,6 @@ struct NCSettingsView: View { .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) Text(NSLocalizedString("_acknowledgements_", comment: "")) } - .font(.system(size: 16)) }) .tint(Color(NCBrandColor.shared.textColor)) .sheet(isPresented: $showAcknowledgements) { @@ -249,12 +229,10 @@ struct NCSettingsView: View { Image(systemName: "shield.checkerboard") .resizable() .scaledToFit() - .font(Font.system(.body).weight(.light)) .frame(width: 25, height: 25) .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) Text(NSLocalizedString("_privacy_legal_", comment: "")) } - .font(.system(size: 16)) }) .tint(Color(NCBrandColor.shared.textColor)) .sheet(isPresented: $showBrowser) { @@ -273,7 +251,6 @@ struct NCSettingsView: View { .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) Text(NSLocalizedString("_source_code_", comment: "")) } - .font(.system(size: 16)) }) .tint(Color(NCBrandColor.shared.textColor)) .sheet(isPresented: $showSourceCode) { @@ -310,12 +287,10 @@ struct E2EESection: View { Image(systemName: "lock") .resizable() .scaledToFit() - .font(Font.system(.body).weight(.light)) .frame(width: 20, height: 20) .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) Text(NSLocalizedString("_e2e_settings_", comment: "")) } - .font(.system(size: 16)) } }) } diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 3804c26bd0..7480fd7385 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -380,7 +380,7 @@ "_advanced_" = "Advanced"; "_permissions_" = "Permissions"; "_disable_files_app_" = "Disable Files App integration"; -"_disable_files_app_footer_" = "Do not permit the access of files via the iOS Files application."; +"_disable_files_app_footer_" = "Enabling this option will not permit Nextcloud files from being accessed inside the iOS Files app."; "_trial_" = "Trial"; "_trial_expired_day_" = "Days remaining"; "_time_remaining_" = "%@ remaining"; @@ -409,7 +409,8 @@ "_autoupload_create_subfolder_footer_" = "Store in subfolders based on year, month or daily."; "_autoupload_subfolder_granularity_" = "Subfolder Granularity"; "_filenamemask_" = "Change filename mask"; -"_filenamemask_footer_" = "By default, when a file is uploaded, it automatically gets the following format: yy-mm-dd hh-mm-ss plus a 4-digit counter. If you do not like this format, you can change it here except for the final 4-digit counter, which cannot be omitted."; +"_filenamemask_footer_" = "By default, when a file is uploaded, it automatically gets the following format: %@ plus a 4-digit counter. If you do not like this format, you can change it here except for the final 4-digit counter, which cannot be omitted."; +"_filenamemask_format_" = "yy-mm-dd hh-mm-ss"; "_autoupload_current_folder_" = "Currently selected folder"; "_help_tutorial_" = "Tutorial"; "_help_intro_" = "Introduction to Nextcloud"; @@ -417,16 +418,16 @@ "_help_activity_mail_" = "Send activity via email"; "_help_activity_clear_" = "Clear activity"; "_show_hidden_files_" = "Show hidden files"; -"_format_compatibility_" = "Most Compatible"; -"_format_compatibility_footer_" = "\"Most compatible\" will save photos as JPEG, if possible."; +"_format_compatibility_" = "Most compatible photos format"; +"_format_compatibility_footer_" = "Enabling this option will save photos as JPEG instead of HEIC. We recommend to keep this on, as some services can't display HEIC photos."; "_terms_" = "Terms of Service"; "_privacy_" = "Privacy"; "_privacy_policy_" = "Privacy Policy"; -"_privacy_footer_" = "This app uses a service for the analysis of a crash. Your personal information is not sent with the report. If you want disable it, please change the setting \"Disable crash reporter\" to ON."; +"_privacy_footer_" = "This app uses a service for analyzing crashes. Your personal information is not sent with the report. If you would like to stop sending crash reports, enable this option."; "_crashservice_title_" = "Disable crash reporter"; "_crashservice_alert_" = "This option requires a restart of the app to take effect"; "_upload_mov_livephoto_" = "Live Photo"; -"_upload_mov_livephoto_footer_" = "\"Live Photo\" will save the selected photo in \"Live Photo\" format, if possible."; +"_upload_mov_livephoto_footer_" = "Enabling this option will save photos as Live Photos, if possible."; "_view_capabilities_" = "View the capabilities"; "_capabilities_" = "Capabilities"; "_no_capabilities_found_" = "Capabilities not found"; @@ -473,13 +474,14 @@ "_access_photo_not_enabled_" = "Access to Photos not enabled"; "_access_photo_not_enabled_msg_" = "Please go to \"Settings\" and turn on \"Photo Access\""; "_access_background_app_refresh_denied_" = "\"Background App Refresh\" is denied. Please enable it in \"Settings\" otherwise, new photos or videos will not be detected when the application is in the background."; +"_access_background_app_refresh_denied_" = "\"Background App Refresh\" is denied. Please enable it in \"Settings\" otherwise, new photos or videos will not be detected when the application is in the background."; "_access_photo_location_not_enabled_" = "Access to Photos and Location not enabled."; "_access_photo_location_not_enabled_msg_" = "Please go to \"Settings\" and enable \"Photo Access\" and \"Location Services\"."; "_tutorial_photo_view_" = "No photos or videos uploaded yet"; "_error_createsubfolders_upload_" = "Error creating subfolders"; "_activate_autoupload_" = "Enable auto upload"; -"_remove_photo_CameraRoll_" = "Remove from camera roll"; -"_remove_photo_CameraRoll_desc_" = "\"Remove from camera roll\" after uploads, a confirmation message will be displayed to delete the uploaded photos or videos from the camera roll. The deleted photos or videos will still be available in the iOS Photos Trash for 30 days."; +"_remove_photo_CameraRoll_" = "Remove from Camera Roll on upload"; +"_remove_photo_CameraRoll_desc_" = "When this option is enabled, a confirmation message will be displayed after photos are uploaded to delete them from the Camera Roll. The deleted photos will still be available inside Trash in the iOS Photos app for 30 days."; "_never_" = "never"; "_less_a_minute_" = "less than a minute ago"; "_a_minute_ago_" = "a minute ago"; @@ -882,7 +884,7 @@ "_message_disable_overwrite_livephoto_" = "This image is a Live Photo, overwrite will not be possible."; "_discard_changes_info_" = "Your changes will be discarded."; "_delete_files_desc_" = "Delete files to free up space"; -"_delete_old_files_" = "Delete, in the cache, all files older than"; +"_delete_old_files_" = "Clear all cached files older than"; "_never_" = "Never"; "_1_year_" = "1 year"; "_6_months_" = "6 months"; @@ -895,7 +897,7 @@ "_weekly_" = "Weekly"; "_daily_" = "Daily"; "_day_" = "Day"; -"_used_space_" = "Used space"; +"_used_space_" = "Used space:"; "_open_in_onlyoffice_" = "Open in ONLYOFFICE"; "_open_in_collabora_" = "Open with Collabora Online"; "_login_address_detail_" = "The link to your %@ web interface when you open it in the browser."; From 90ed82d2f91896f9d1fefbc70aae43ee26c97cca Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 27 Nov 2024 16:34:08 +0100 Subject: [PATCH 13/25] WIP Signed-off-by: Milen Pivchev --- Brand/NCBrand.swift | 2 +- iOSClient/Settings/Settings/NCSettingsView.swift | 4 ++-- iOSClient/Supporting Files/en.lproj/Localizable.strings | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Brand/NCBrand.swift b/Brand/NCBrand.swift index 487e8d6d26..5e94a8443e 100755 --- a/Brand/NCBrand.swift +++ b/Brand/NCBrand.swift @@ -72,7 +72,7 @@ let userAgent: String = { var disable_show_more_nextcloud_apps_in_settings: Bool = false var doNotAskPasscodeAtStartup: Bool = false var disable_source_code_in_settings: Bool = false - var enforce_protection = true + var enforce_protection = false // (name: "Name 1", url: "https://cloud.nextcloud.com"),(name: "Name 2", url: "https://cloud.nextcloud.com") var enforce_servers: [(name: String, url: String)] = [] diff --git a/iOSClient/Settings/Settings/NCSettingsView.swift b/iOSClient/Settings/Settings/NCSettingsView.swift index 6a23679989..6895c08353 100644 --- a/iOSClient/Settings/Settings/NCSettingsView.swift +++ b/iOSClient/Settings/Settings/NCSettingsView.swift @@ -109,13 +109,13 @@ struct NCSettingsView: View { } /// Reset app wrong attempts - Toggle(NSLocalizedString("_reset_wrong_passcode_", comment: ""), isOn: $model.resetWrongAttempts) + Toggle(NSLocalizedString("_reset_wrong_passcode_option_", comment: ""), isOn: $model.resetWrongAttempts) .onChange(of: model.resetWrongAttempts) { _ in model.updateResetWrongAttemptsSetting() } } }, footer: { - Text(NSLocalizedString("_lock_protection_no_screen_footer_", comment: "") + "\n" + String(format: NSLocalizedString("_reset_wrong_passcode_desc_", comment: ""), NCBrandOptions.shared.resetAppPasscodeAttempts)) + Text(String(format: NSLocalizedString("_reset_wrong_passcode_desc_", comment: ""), NCBrandOptions.shared.resetAppPasscodeAttempts)) }) .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) } diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 7480fd7385..9612aa445f 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -308,7 +308,6 @@ "_lock_active_" = "Passcode Lock: On"; "_lock_not_active_" = "Passcode Lock: Off"; "_lock_protection_no_screen_" = "Do not ask at startup"; -"_lock_protection_no_screen_footer_" = "Use \"Do not ask at startup\" for the encryption option."; "_enable_touch_face_id_" = "Enable Touch/Face ID"; "_url_" = "URL"; "_username_" = "Username"; @@ -987,6 +986,7 @@ "_download_image_" = "Download image"; "_download_video_" = "Download video"; "_reset_wrong_passcode_" = "Reset application"; +"_reset_wrong_passcode_option_" = "Reset application after failed attempts"; "_reset_wrong_passcode_desc_" = "Use \"Reset application\" to remove all accounts and local data after %d failed code entry attempts."; "_deviceOwnerAuthentication_" = "The biometric sensor has been temporarily disabled due to multiple failed attempts. Enter the device passcode to re-enable the sensor."; "_virus_detect_" = "Virus detected. Upload cannot be completed!"; From 52e98fa46b1f2c9bd6374c19c7998a959d20e678 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 27 Nov 2024 16:39:51 +0100 Subject: [PATCH 14/25] WIP Signed-off-by: Milen Pivchev --- iOSClient/Supporting Files/en.lproj/Localizable.strings | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 9612aa445f..29b9f1fd58 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -964,7 +964,7 @@ "_undo_modify_" = "Undo modifying"; "_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode."; "_disableFilesApp_" = "Files app cannot be used because it is disabled."; -"_reset_application_done_" = "Reset application, done."; +"_reset_application_done_" = "Finished resetting application."; "_rename_already_exists_" = "A file with this name already exists."; "_created_" = "Created"; "_recipients_" = "Recipients"; @@ -987,7 +987,7 @@ "_download_video_" = "Download video"; "_reset_wrong_passcode_" = "Reset application"; "_reset_wrong_passcode_option_" = "Reset application after failed attempts"; -"_reset_wrong_passcode_desc_" = "Use \"Reset application\" to remove all accounts and local data after %d failed code entry attempts."; +"_reset_wrong_passcode_desc_" = "Enabling this option will remove all accounts and local data after %d failed passcode entry attempts."; "_deviceOwnerAuthentication_" = "The biometric sensor has been temporarily disabled due to multiple failed attempts. Enter the device passcode to re-enable the sensor."; "_virus_detect_" = "Virus detected. Upload cannot be completed!"; "_zoom_" = "Zoom"; From dcff66b0095e553e7d2bb99ae99fd9da779b3d0d Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Wed, 27 Nov 2024 16:42:42 +0100 Subject: [PATCH 15/25] WIP Signed-off-by: Milen Pivchev --- iOSClient/Settings/Settings/NCSettingsModel.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/iOSClient/Settings/Settings/NCSettingsModel.swift b/iOSClient/Settings/Settings/NCSettingsModel.swift index 40c0f4b03f..a9265227b6 100644 --- a/iOSClient/Settings/Settings/NCSettingsModel.swift +++ b/iOSClient/Settings/Settings/NCSettingsModel.swift @@ -141,13 +141,6 @@ struct SetupPasscodeView: UIViewControllerRepresentable { } passcodeViewController.delegate = context.coordinator return passcodeViewController - } else if changePasscode { - let passcodeSettingsViewController = TOPasscodeSettingsViewController() - passcodeSettingsViewController.hideOptionsButton = true -// passcodeSettingsViewController.requireCurrentPasscode = true - passcodeSettingsViewController.passcodeType = .sixDigits - passcodeSettingsViewController.delegate = context.coordinator - return passcodeSettingsViewController } else { let passcodeSettingsViewController = TOPasscodeSettingsViewController() passcodeSettingsViewController.hideOptionsButton = true From e8ab444c50b978d7f398f6f43ba279fadce83c71 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 28 Nov 2024 11:28:00 +0100 Subject: [PATCH 16/25] WIP Signed-off-by: Milen Pivchev --- Brand/NCBrand.swift | 9 ++++---- iOSClient/AppDelegate.swift | 4 ++++ iOSClient/Main/NCMainTabBarController.swift | 2 +- iOSClient/NCGlobal.swift | 2 +- .../Settings/Settings/NCSettingsModel.swift | 7 +++++++ .../Settings/Settings/NCSettingsView.swift | 21 +++++++++++-------- .../en.lproj/Localizable.strings | 2 +- 7 files changed, 30 insertions(+), 17 deletions(-) diff --git a/Brand/NCBrand.swift b/Brand/NCBrand.swift index 5e94a8443e..b496e61340 100755 --- a/Brand/NCBrand.swift +++ b/Brand/NCBrand.swift @@ -72,13 +72,13 @@ let userAgent: String = { var disable_show_more_nextcloud_apps_in_settings: Bool = false var doNotAskPasscodeAtStartup: Bool = false var disable_source_code_in_settings: Bool = false - var enforce_protection = false + var enforce_passcode_lock = false // (name: "Name 1", url: "https://cloud.nextcloud.com"),(name: "Name 2", url: "https://cloud.nextcloud.com") var enforce_servers: [(name: String, url: String)] = [] // Internal option behaviour - var cleanUpDay: Int = 0 // Set default "Delete, in the cache, all files older than" possible days value are: 0, 1, 7, 30, 90, 180, 365 + var cleanUpDay: Int = 0 // Set default "Delete all cached files older than" possible days value are: 0, 1, 7, 30, 90, 180, 365 // Max download/upload concurrent let maxConcurrentOperationDownload: Int = 5 @@ -96,7 +96,6 @@ let userAgent: String = { override init() { // wrapper AppConfig if let configurationManaged = UserDefaults.standard.dictionary(forKey: "com.apple.configuration.managed"), use_AppConfig { - if let str = configurationManaged[NCGlobal.shared.configuration_brand] as? String { brand = str } @@ -118,8 +117,8 @@ let userAgent: String = { if let str = configurationManaged[NCGlobal.shared.configuration_disable_openin_file] as? String { disable_openin_file = (str as NSString).boolValue } - if let str = configurationManaged[NCGlobal.shared.configuration_enforce_protection] as? String { - enforce_protection = (str as NSString).boolValue + if let str = configurationManaged[NCGlobal.shared.configuration_enforce_passcode_lock] as? String { + enforce_passcode_lock = (str as NSString).boolValue } } } diff --git a/iOSClient/AppDelegate.swift b/iOSClient/AppDelegate.swift index b5fda23476..9b502da283 100644 --- a/iOSClient/AppDelegate.swift +++ b/iOSClient/AppDelegate.swift @@ -105,6 +105,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD self.handleProcessingTask(task) } + if NCBrandOptions.shared.enforce_passcode_lock { + NCKeychain().requestPasscodeAtStart = true + } + /// Activation singleton _ = NCActionCenter.shared _ = NCNetworkingProcess.shared diff --git a/iOSClient/Main/NCMainTabBarController.swift b/iOSClient/Main/NCMainTabBarController.swift index c8028829dc..bc25f39946 100644 --- a/iOSClient/Main/NCMainTabBarController.swift +++ b/iOSClient/Main/NCMainTabBarController.swift @@ -57,7 +57,7 @@ class NCMainTabBarController: UITabBarController { super.viewDidAppear(animated) previousIndex = selectedIndex - if NCBrandOptions.shared.enforce_protection && NCKeychain().passcode.isEmptyOrNil { + if NCBrandOptions.shared.enforce_passcode_lock && NCKeychain().passcode.isEmptyOrNil { let vc = UIHostingController(rootView: SetupPasscodeView(isLockActive: .constant(false))) vc.isModalInPresentation = true diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index aa7f69329c..80594ccaaa 100644 --- a/iOSClient/NCGlobal.swift +++ b/iOSClient/NCGlobal.swift @@ -379,7 +379,7 @@ class NCGlobal: NSObject { let configuration_disable_log = "disable_log" let configuration_disable_more_external_site = "disable_more_external_site" let configuration_disable_openin_file = "disable_openin_file" - let configuration_enforce_protection = "enforce_protection" + let configuration_enforce_passcode_lock = "enforce_passcode_lock" // MORE NEXTCLOUD APPS // diff --git a/iOSClient/Settings/Settings/NCSettingsModel.swift b/iOSClient/Settings/Settings/NCSettingsModel.swift index a9265227b6..8178c3db12 100644 --- a/iOSClient/Settings/Settings/NCSettingsModel.swift +++ b/iOSClient/Settings/Settings/NCSettingsModel.swift @@ -141,6 +141,13 @@ struct SetupPasscodeView: UIViewControllerRepresentable { } passcodeViewController.delegate = context.coordinator return passcodeViewController + } else if changePasscode { + let passcodeSettingsViewController = TOPasscodeSettingsViewController() + passcodeSettingsViewController.hideOptionsButton = true + // passcodeSettingsViewController.requireCurrentPasscode = true + passcodeSettingsViewController.passcodeType = .sixDigits + passcodeSettingsViewController.delegate = context.coordinator + return passcodeSettingsViewController } else { let passcodeSettingsViewController = TOPasscodeSettingsViewController() passcodeSettingsViewController.hideOptionsButton = true diff --git a/iOSClient/Settings/Settings/NCSettingsView.swift b/iOSClient/Settings/Settings/NCSettingsView.swift index 6895c08353..c3bc9f676f 100644 --- a/iOSClient/Settings/Settings/NCSettingsView.swift +++ b/iOSClient/Settings/Settings/NCSettingsView.swift @@ -71,16 +71,16 @@ struct NCSettingsView: View { .scaledToFit() .foregroundColor(Color(NCBrandColor.shared.iconImageColor)) .frame(width: 20, height: 20) - .opacity(NCBrandOptions.shared.enforce_protection ? 0.5 : 1) + .opacity(NCBrandOptions.shared.enforce_passcode_lock ? 0.5 : 1) Text(model.isLockActive ? NSLocalizedString("_lock_active_", comment: "") : NSLocalizedString("_lock_not_active_", comment: "")) } }) .tint(Color(NCBrandColor.shared.textColor)) - .disabled(NCBrandOptions.shared.enforce_protection) + .disabled(NCBrandOptions.shared.enforce_passcode_lock) }, header: { Text(NSLocalizedString("_privacy_", comment: "")) }, footer: { - if NCBrandOptions.shared.enforce_protection { + if NCBrandOptions.shared.enforce_passcode_lock { Text(NSLocalizedString("_lock_cannot_disable_mdm_", comment: "")) } }) @@ -102,11 +102,14 @@ struct NCSettingsView: View { .onChange(of: model.enableTouchID) { _ in model.updateTouchIDSetting() } - /// Lock no screen - Toggle(NSLocalizedString("_lock_protection_no_screen_", comment: ""), isOn: $model.lockScreen) - .onChange(of: model.lockScreen) { _ in - model.updateLockScreenSetting() - } + + if !NCBrandOptions.shared.enforce_passcode_lock { + /// Do not ask for passcode on startup + Toggle(NSLocalizedString("_lock_protection_no_screen_", comment: ""), isOn: $model.lockScreen) + .onChange(of: model.lockScreen) { _ in + model.updateLockScreenSetting() + } + } /// Reset app wrong attempts Toggle(NSLocalizedString("_reset_wrong_passcode_option_", comment: ""), isOn: $model.resetWrongAttempts) @@ -121,7 +124,7 @@ struct NCSettingsView: View { } Section { - /// Privacy screen + /// Splash screen when app inactive Toggle(NSLocalizedString("_privacy_screen_", comment: ""), isOn: $model.privacyScreen) .onChange(of: model.privacyScreen) { _ in model.updatePrivacyScreenSetting() diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index 29b9f1fd58..c3ab139e4e 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -307,7 +307,7 @@ "_lock_" = "Lock"; "_lock_active_" = "Passcode Lock: On"; "_lock_not_active_" = "Passcode Lock: Off"; -"_lock_protection_no_screen_" = "Do not ask at startup"; +"_lock_protection_no_screen_" = "Do not ask for passcode on startup"; "_enable_touch_face_id_" = "Enable Touch/Face ID"; "_url_" = "URL"; "_username_" = "Username"; From 190869cf9f33b6b9c4b9029d76b581397ae0179f Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 28 Nov 2024 11:32:04 +0100 Subject: [PATCH 17/25] WIP Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 4 + .../Settings/Settings/NCSettingsModel.swift | 97 ---------------- .../Settings/Settings/SetupPasscodeView.swift | 109 ++++++++++++++++++ 3 files changed, 113 insertions(+), 97 deletions(-) create mode 100644 iOSClient/Settings/Settings/SetupPasscodeView.swift diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index cea10e5238..722833fc00 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -147,6 +147,7 @@ F37208C42BAB63F0006B5430 /* Gzip in Frameworks */ = {isa = PBXBuildFile; productRef = F37208C32BAB63F0006B5430 /* Gzip */; }; F37208C62BAB63F0006B5430 /* LRUCache in Frameworks */ = {isa = PBXBuildFile; productRef = F37208C52BAB63F0006B5430 /* LRUCache */; }; F37208C82BAB63F1006B5430 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = F37208C72BAB63F1006B5430 /* KeychainAccess */; }; + F3754A7D2CF87D600009312E /* SetupPasscodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3754A7C2CF87D600009312E /* SetupPasscodeView.swift */; }; F38F71252B6BBDC300473CDC /* NCCollectionViewCommonSelectTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F38F71242B6BBDC300473CDC /* NCCollectionViewCommonSelectTabBar.swift */; }; F39170A92CB82024006127BC /* FileAutoRenamer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39170A82CB8201B006127BC /* FileAutoRenamer+Extensions.swift */; }; F39170AA2CB82024006127BC /* FileAutoRenamer+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F39170A82CB8201B006127BC /* FileAutoRenamer+Extensions.swift */; }; @@ -1198,6 +1199,7 @@ F36E64F62B9245210085ABB5 /* NCCollectionViewCommon+SelectTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NCCollectionViewCommon+SelectTabBar.swift"; sourceTree = ""; }; F37208742BAB4AB0006B5430 /* TestConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestConstants.swift; sourceTree = ""; }; F37208772BAB4B5D006B5430 /* BaseXCTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseXCTestCase.swift; sourceTree = ""; }; + F3754A7C2CF87D600009312E /* SetupPasscodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupPasscodeView.swift; sourceTree = ""; }; F38F71242B6BBDC300473CDC /* NCCollectionViewCommonSelectTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NCCollectionViewCommonSelectTabBar.swift; sourceTree = ""; }; F39170A82CB8201B006127BC /* FileAutoRenamer+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileAutoRenamer+Extensions.swift"; sourceTree = ""; }; F39298962A3B12CB00509762 /* BaseNCMoreCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseNCMoreCell.swift; sourceTree = ""; }; @@ -2381,6 +2383,7 @@ F768820D2C0DD1E7001CF441 /* E2EE */, F76882112C0DD1E7001CF441 /* NCSettingsModel.swift */, F768820C2C0DD1E7001CF441 /* NCSettingsView.swift */, + F3754A7C2CF87D600009312E /* SetupPasscodeView.swift */, ); path = Settings; sourceTree = ""; @@ -4267,6 +4270,7 @@ F768822C2C0DD1E7001CF441 /* NCKeychain.swift in Sources */, F7BFFD282C8846020029A201 /* NCHud.swift in Sources */, F71CD6CA2930D7B1006C95C1 /* NCApplicationHandle.swift in Sources */, + F3754A7D2CF87D600009312E /* SetupPasscodeView.swift in Sources */, F73EF7D72B0226080087E6E9 /* NCManageDatabase+Tip.swift in Sources */, F74BAE172C7E2F4E0028D4FA /* FileProviderDomain.swift in Sources */, F76882402C0DD30B001CF441 /* ViewOnAppear.swift in Sources */, diff --git a/iOSClient/Settings/Settings/NCSettingsModel.swift b/iOSClient/Settings/Settings/NCSettingsModel.swift index 8178c3db12..8e870e978f 100644 --- a/iOSClient/Settings/Settings/NCSettingsModel.swift +++ b/iOSClient/Settings/Settings/NCSettingsModel.swift @@ -113,100 +113,3 @@ class NCSettingsModel: ObservableObject, ViewOnAppearHandling { keychain.accountRequest = accountRequest } } - -struct SetupPasscodeView: UIViewControllerRepresentable { - @Binding var isLockActive: Bool - var changePasscode = false - - func makeUIViewController(context: Context) -> UIViewController { - let laContext = LAContext() - var error: NSError? - if NCKeychain().passcode != nil, !changePasscode { - let passcodeViewController = TOPasscodeViewController(passcodeType: .sixDigits, allowCancel: true) - passcodeViewController.keypadButtonShowLettering = false - if NCKeychain().touchFaceID && laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { - if error == nil { - if laContext.biometryType == .faceID { - passcodeViewController.biometryType = .faceID - passcodeViewController.allowBiometricValidation = true - passcodeViewController.automaticallyPromptForBiometricValidation = true - } else if laContext.biometryType == .touchID { - passcodeViewController.biometryType = .touchID - passcodeViewController.allowBiometricValidation = true - passcodeViewController.automaticallyPromptForBiometricValidation = true - } else { - print("No Biometric support") - } - } - } - passcodeViewController.delegate = context.coordinator - return passcodeViewController - } else if changePasscode { - let passcodeSettingsViewController = TOPasscodeSettingsViewController() - passcodeSettingsViewController.hideOptionsButton = true - // passcodeSettingsViewController.requireCurrentPasscode = true - passcodeSettingsViewController.passcodeType = .sixDigits - passcodeSettingsViewController.delegate = context.coordinator - return passcodeSettingsViewController - } else { - let passcodeSettingsViewController = TOPasscodeSettingsViewController() - passcodeSettingsViewController.hideOptionsButton = true - passcodeSettingsViewController.requireCurrentPasscode = false - passcodeSettingsViewController.passcodeType = .sixDigits - passcodeSettingsViewController.delegate = context.coordinator - return passcodeSettingsViewController - } - } - - func updateUIViewController(_ uiViewController: UIViewController, context: Context) { - // Update the view controller if needed - } - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - class Coordinator: NSObject, TOPasscodeSettingsViewControllerDelegate, TOPasscodeViewControllerDelegate { - var parent: SetupPasscodeView - init(_ parent: SetupPasscodeView) { - self.parent = parent - } - - func didPerformBiometricValidationRequest(in passcodeViewController: TOPasscodeViewController) { - let context = LAContext() - var error: NSError? - - if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { - context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: NCBrandOptions.shared.brand) { success, _ in - DispatchQueue.main.async { - if success { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - NCKeychain().passcode = nil - passcodeViewController.dismiss(animated: true) - } - } - } - } - } - } - - func passcodeSettingsViewController(_ passcodeSettingsViewController: TOPasscodeSettingsViewController, didChangeToNewPasscode passcode: String, of type: TOPasscodeType) { - NCKeychain().passcode = passcode - self.parent.isLockActive = true - passcodeSettingsViewController.dismiss(animated: true) - } - - func didTapCancel(in passcodeViewController: TOPasscodeViewController) { - passcodeViewController.dismiss(animated: true) - } - - func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode code: String) -> Bool { - if code == NCKeychain().passcode { - self.parent.isLockActive = false - NCKeychain().passcode = nil - return true - } - return false - } - } -} diff --git a/iOSClient/Settings/Settings/SetupPasscodeView.swift b/iOSClient/Settings/Settings/SetupPasscodeView.swift new file mode 100644 index 0000000000..733319cb64 --- /dev/null +++ b/iOSClient/Settings/Settings/SetupPasscodeView.swift @@ -0,0 +1,109 @@ +// +// SetupPasscodeView.swift +// Nextcloud +// +// Created by Milen Pivchev on 28.11.24. +// Copyright © 2024 Marino Faggiana. All rights reserved. +// + +import Foundation +import UIKit +import SwiftUI +import LocalAuthentication + +struct SetupPasscodeView: UIViewControllerRepresentable { + @Binding var isLockActive: Bool + var changePasscode = false + + func makeUIViewController(context: Context) -> UIViewController { + let laContext = LAContext() + var error: NSError? + if NCKeychain().passcode != nil, !changePasscode { + let passcodeViewController = TOPasscodeViewController(passcodeType: .sixDigits, allowCancel: true) + passcodeViewController.keypadButtonShowLettering = false + if NCKeychain().touchFaceID && laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { + if error == nil { + if laContext.biometryType == .faceID { + passcodeViewController.biometryType = .faceID + passcodeViewController.allowBiometricValidation = true + passcodeViewController.automaticallyPromptForBiometricValidation = true + } else if laContext.biometryType == .touchID { + passcodeViewController.biometryType = .touchID + passcodeViewController.allowBiometricValidation = true + passcodeViewController.automaticallyPromptForBiometricValidation = true + } else { + print("No Biometric support") + } + } + } + passcodeViewController.delegate = context.coordinator + return passcodeViewController + } else if changePasscode { + let passcodeSettingsViewController = TOPasscodeSettingsViewController() + passcodeSettingsViewController.hideOptionsButton = true + passcodeSettingsViewController.requireCurrentPasscode = true + passcodeSettingsViewController.passcodeType = .sixDigits + passcodeSettingsViewController.delegate = context.coordinator + return passcodeSettingsViewController + } else { + let passcodeSettingsViewController = TOPasscodeSettingsViewController() + passcodeSettingsViewController.hideOptionsButton = true + passcodeSettingsViewController.requireCurrentPasscode = false + passcodeSettingsViewController.passcodeType = .sixDigits + passcodeSettingsViewController.delegate = context.coordinator + return passcodeSettingsViewController + } + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) { + // Update the view controller if needed + } + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + class Coordinator: NSObject, TOPasscodeSettingsViewControllerDelegate, TOPasscodeViewControllerDelegate { + var parent: SetupPasscodeView + init(_ parent: SetupPasscodeView) { + self.parent = parent + } + + func didPerformBiometricValidationRequest(in passcodeViewController: TOPasscodeViewController) { + let context = LAContext() + var error: NSError? + + if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { + context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: NCBrandOptions.shared.brand) { success, _ in + DispatchQueue.main.async { + if success { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + NCKeychain().passcode = nil + passcodeViewController.dismiss(animated: true) + } + } + } + } + } + } + + func passcodeSettingsViewController(_ passcodeSettingsViewController: TOPasscodeSettingsViewController, didChangeToNewPasscode passcode: String, of type: TOPasscodeType) { + NCKeychain().passcode = passcode + self.parent.isLockActive = true + passcodeSettingsViewController.dismiss(animated: true) + } + + func didTapCancel(in passcodeViewController: TOPasscodeViewController) { + passcodeViewController.dismiss(animated: true) + } + + func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode code: String) -> Bool { + if code == NCKeychain().passcode { + self.parent.isLockActive = false + NCKeychain().passcode = nil + return true + } + return false + } + } +} From 301782390203bb4a3e8502725b0eea257c2eac49 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 28 Nov 2024 11:47:53 +0100 Subject: [PATCH 18/25] WIP Signed-off-by: Milen Pivchev --- Nextcloud.xcodeproj/project.pbxproj | 4 --- iOSClient/Settings/Settings.bundle/Root.plist | 31 ------------------ .../Settings.bundle/en.lproj/Root.strings | Bin 512 -> 0 bytes 3 files changed, 35 deletions(-) delete mode 100644 iOSClient/Settings/Settings.bundle/Root.plist delete mode 100644 iOSClient/Settings/Settings.bundle/en.lproj/Root.strings diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index 722833fc00..ef45d2c7a9 100644 --- a/Nextcloud.xcodeproj/project.pbxproj +++ b/Nextcloud.xcodeproj/project.pbxproj @@ -561,7 +561,6 @@ F76882282C0DD1E7001CF441 /* NCEndToEndInitialize.swift in Sources */ = {isa = PBXBuildFile; fileRef = F768820F2C0DD1E7001CF441 /* NCEndToEndInitialize.swift */; }; F76882292C0DD1E7001CF441 /* NCManageE2EEModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76882102C0DD1E7001CF441 /* NCManageE2EEModel.swift */; }; F768822A2C0DD1E7001CF441 /* NCSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76882112C0DD1E7001CF441 /* NCSettingsModel.swift */; }; - F768822B2C0DD1E7001CF441 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = F76882122C0DD1E7001CF441 /* Settings.bundle */; }; F768822C2C0DD1E7001CF441 /* NCKeychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76882132C0DD1E7001CF441 /* NCKeychain.swift */; }; F768822D2C0DD1E7001CF441 /* Acknowledgements.rtf in Resources */ = {isa = PBXBuildFile; fileRef = F76882142C0DD1E7001CF441 /* Acknowledgements.rtf */; }; F768822E2C0DD1E7001CF441 /* NCSettingsBundleHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = F76882152C0DD1E7001CF441 /* NCSettingsBundleHelper.swift */; }; @@ -1383,7 +1382,6 @@ F768820F2C0DD1E7001CF441 /* NCEndToEndInitialize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCEndToEndInitialize.swift; sourceTree = ""; }; F76882102C0DD1E7001CF441 /* NCManageE2EEModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCManageE2EEModel.swift; sourceTree = ""; }; F76882112C0DD1E7001CF441 /* NCSettingsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCSettingsModel.swift; sourceTree = ""; }; - F76882122C0DD1E7001CF441 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; F76882132C0DD1E7001CF441 /* NCKeychain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCKeychain.swift; sourceTree = ""; }; F76882142C0DD1E7001CF441 /* Acknowledgements.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Acknowledgements.rtf; sourceTree = ""; }; F76882152C0DD1E7001CF441 /* NCSettingsBundleHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NCSettingsBundleHelper.swift; sourceTree = ""; }; @@ -2352,7 +2350,6 @@ F76882142C0DD1E7001CF441 /* Acknowledgements.rtf */, F76882132C0DD1E7001CF441 /* NCKeychain.swift */, F76882152C0DD1E7001CF441 /* NCSettingsBundleHelper.swift */, - F76882122C0DD1E7001CF441 /* Settings.bundle */, ); path = Settings; sourceTree = ""; @@ -3756,7 +3753,6 @@ F774264A22EB4D0000B23912 /* NCSearchUserDropDownCell.xib in Resources */, F7CB689A2541676B0050EC94 /* NCMore.storyboard in Resources */, F77B0F7D1D118A16002130FE /* Images.xcassets in Resources */, - F768822B2C0DD1E7001CF441 /* Settings.bundle in Resources */, F73CB3B222E072A000AD728E /* NCShareHeaderView.xib in Resources */, F7AE00FA230E81EB007ACF8A /* NCBrowserWeb.storyboard in Resources */, F7EDE514262DC2CD00414FE6 /* NCSelectCommandViewSelect+CreateFolder.xib in Resources */, diff --git a/iOSClient/Settings/Settings.bundle/Root.plist b/iOSClient/Settings/Settings.bundle/Root.plist deleted file mode 100644 index 1184bfbc77..0000000000 --- a/iOSClient/Settings/Settings.bundle/Root.plist +++ /dev/null @@ -1,31 +0,0 @@ - - - - - StringsTable - Root - PreferenceSpecifiers - - - Type - PSToggleSwitchSpecifier - Title - _reset_application_ - Key - reset_application - DefaultValue - - - - Type - PSTitleValueSpecifier - Title - _version_ - Key - version_preference - DefaultValue - 0 - - - - diff --git a/iOSClient/Settings/Settings.bundle/en.lproj/Root.strings b/iOSClient/Settings/Settings.bundle/en.lproj/Root.strings deleted file mode 100644 index 6b57b356ce05f69289cc3e0982e46867ff21bfc8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 512 zcmaiw(P{!Q6h!CQuL%1nQa?cY&|eVgt1P=&yI?n>(PBSdJrlP=OG^pK&7C`UGP&2w zTvG+Tun+1rXrrxS$+Gq%av<-xyE5bcp*JP?=4J3a_Vi%Z*->f0-4oSn-f_1O3uDQnOFbAe9*5*=Hc{IRE>Xe=^lv6IP`)UajYO+szs*=$T#V^;@OV eV(jpg>>|UiacZx9`JLzgQ)T&Jhd Date: Thu, 28 Nov 2024 13:05:21 +0100 Subject: [PATCH 19/25] added didAttemptCurrentPasscode Signed-off-by: Marino Faggiana --- iOSClient/Settings/Settings/SetupPasscodeView.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/iOSClient/Settings/Settings/SetupPasscodeView.swift b/iOSClient/Settings/Settings/SetupPasscodeView.swift index 733319cb64..80733ea18a 100644 --- a/iOSClient/Settings/Settings/SetupPasscodeView.swift +++ b/iOSClient/Settings/Settings/SetupPasscodeView.swift @@ -105,5 +105,10 @@ struct SetupPasscodeView: UIViewControllerRepresentable { } return false } + + func passcodeSettingsViewController(_ passcodeSettingsViewController: TOPasscodeSettingsViewController, didAttemptCurrentPasscode passcode: String) -> Bool { + // verify here if the code is correct + return true + } } } From d74a381cb113a084b563e75a360b6bb7e9341b69 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Thu, 28 Nov 2024 15:01:01 +0100 Subject: [PATCH 20/25] Refactor Signed-off-by: Milen Pivchev --- .../Settings/Settings/SetupPasscodeView.swift | 62 +++++++++---------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/iOSClient/Settings/Settings/SetupPasscodeView.swift b/iOSClient/Settings/Settings/SetupPasscodeView.swift index 733319cb64..d81e1f37dc 100644 --- a/iOSClient/Settings/Settings/SetupPasscodeView.swift +++ b/iOSClient/Settings/Settings/SetupPasscodeView.swift @@ -18,40 +18,32 @@ struct SetupPasscodeView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { let laContext = LAContext() var error: NSError? - if NCKeychain().passcode != nil, !changePasscode { - let passcodeViewController = TOPasscodeViewController(passcodeType: .sixDigits, allowCancel: true) - passcodeViewController.keypadButtonShowLettering = false - if NCKeychain().touchFaceID && laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) { - if error == nil { - if laContext.biometryType == .faceID { - passcodeViewController.biometryType = .faceID - passcodeViewController.allowBiometricValidation = true - passcodeViewController.automaticallyPromptForBiometricValidation = true - } else if laContext.biometryType == .touchID { - passcodeViewController.biometryType = .touchID - passcodeViewController.allowBiometricValidation = true - passcodeViewController.automaticallyPromptForBiometricValidation = true - } else { - print("No Biometric support") - } + if let passcode = NCKeychain().passcode, !changePasscode { + let passcodeVC = TOPasscodeViewController(passcodeType: .sixDigits, allowCancel: true) + passcodeVC.keypadButtonShowLettering = false + + if NCKeychain().touchFaceID, laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error), error == nil { + switch laContext.biometryType { + case .faceID: + passcodeVC.biometryType = .faceID + case .touchID: + passcodeVC.biometryType = .touchID + default: + print("No Biometric support") } + passcodeVC.allowBiometricValidation = true + passcodeVC.automaticallyPromptForBiometricValidation = true } - passcodeViewController.delegate = context.coordinator - return passcodeViewController - } else if changePasscode { - let passcodeSettingsViewController = TOPasscodeSettingsViewController() - passcodeSettingsViewController.hideOptionsButton = true - passcodeSettingsViewController.requireCurrentPasscode = true - passcodeSettingsViewController.passcodeType = .sixDigits - passcodeSettingsViewController.delegate = context.coordinator - return passcodeSettingsViewController + + passcodeVC.delegate = context.coordinator + return passcodeVC } else { - let passcodeSettingsViewController = TOPasscodeSettingsViewController() - passcodeSettingsViewController.hideOptionsButton = true - passcodeSettingsViewController.requireCurrentPasscode = false - passcodeSettingsViewController.passcodeType = .sixDigits - passcodeSettingsViewController.delegate = context.coordinator - return passcodeSettingsViewController + let passcodeSettingsVC = TOPasscodeSettingsViewController() + passcodeSettingsVC.hideOptionsButton = true + passcodeSettingsVC.requireCurrentPasscode = changePasscode + passcodeSettingsVC.passcodeType = .sixDigits + passcodeSettingsVC.delegate = context.coordinator + return passcodeSettingsVC } } @@ -87,6 +79,10 @@ struct SetupPasscodeView: UIViewControllerRepresentable { } } + func passcodeSettingsViewController(_ passcodeSettingsViewController: TOPasscodeSettingsViewController, didAttemptCurrentPasscode passcode: String) -> Bool { + return passcode == NCKeychain().passcode + } + func passcodeSettingsViewController(_ passcodeSettingsViewController: TOPasscodeSettingsViewController, didChangeToNewPasscode passcode: String, of type: TOPasscodeType) { NCKeychain().passcode = passcode self.parent.isLockActive = true @@ -97,8 +93,8 @@ struct SetupPasscodeView: UIViewControllerRepresentable { passcodeViewController.dismiss(animated: true) } - func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode code: String) -> Bool { - if code == NCKeychain().passcode { + func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode passcode: String) -> Bool { + if passcode == NCKeychain().passcode { self.parent.isLockActive = false NCKeychain().passcode = nil return true From 32973f52975eed10246cd83cd2eb7b06ad3a9656 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 29 Nov 2024 11:06:44 +0100 Subject: [PATCH 21/25] Fix build Signed-off-by: Milen Pivchev --- iOSClient/Settings/Settings/SetupPasscodeView.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/iOSClient/Settings/Settings/SetupPasscodeView.swift b/iOSClient/Settings/Settings/SetupPasscodeView.swift index aa8ba7d916..d81e1f37dc 100644 --- a/iOSClient/Settings/Settings/SetupPasscodeView.swift +++ b/iOSClient/Settings/Settings/SetupPasscodeView.swift @@ -101,10 +101,5 @@ struct SetupPasscodeView: UIViewControllerRepresentable { } return false } - - func passcodeSettingsViewController(_ passcodeSettingsViewController: TOPasscodeSettingsViewController, didAttemptCurrentPasscode passcode: String) -> Bool { - // verify here if the code is correct - return true - } } } From 67422832a31f35d157dd069c7e8d741112e7bbdb Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 29 Nov 2024 12:04:02 +0100 Subject: [PATCH 22/25] Add attempt check Signed-off-by: Milen Pivchev --- .../Settings/Settings/SetupPasscodeView.swift | 16 ++++++++++++---- .../en.lproj/Localizable.strings | 1 + iOSClient/Utility/NCContentPresenter.swift | 4 ++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/iOSClient/Settings/Settings/SetupPasscodeView.swift b/iOSClient/Settings/Settings/SetupPasscodeView.swift index d81e1f37dc..88cc230530 100644 --- a/iOSClient/Settings/Settings/SetupPasscodeView.swift +++ b/iOSClient/Settings/Settings/SetupPasscodeView.swift @@ -10,15 +10,17 @@ import Foundation import UIKit import SwiftUI import LocalAuthentication +import PopupView struct SetupPasscodeView: UIViewControllerRepresentable { @Binding var isLockActive: Bool - var changePasscode = false + var changePasscode: Bool = false + let maxFailedAttempts = 3 func makeUIViewController(context: Context) -> UIViewController { let laContext = LAContext() var error: NSError? - if let passcode = NCKeychain().passcode, !changePasscode { + if !NCKeychain().passcode.isEmptyOrNil, !changePasscode { let passcodeVC = TOPasscodeViewController(passcodeType: .sixDigits, allowCancel: true) passcodeVC.keypadButtonShowLettering = false @@ -80,12 +82,17 @@ struct SetupPasscodeView: UIViewControllerRepresentable { } func passcodeSettingsViewController(_ passcodeSettingsViewController: TOPasscodeSettingsViewController, didAttemptCurrentPasscode passcode: String) -> Bool { + if passcodeSettingsViewController.failedPasscodeAttemptCount == parent.maxFailedAttempts - 1 { + passcodeSettingsViewController.dismiss(animated: true) + NCContentPresenter().showCustomMessage(message: NSLocalizedString("_too_many_failed_passcode_attempts_error_", comment: ""), type: .error) + } + return passcode == NCKeychain().passcode } func passcodeSettingsViewController(_ passcodeSettingsViewController: TOPasscodeSettingsViewController, didChangeToNewPasscode passcode: String, of type: TOPasscodeType) { NCKeychain().passcode = passcode - self.parent.isLockActive = true + parent.isLockActive = true passcodeSettingsViewController.dismiss(animated: true) } @@ -95,10 +102,11 @@ struct SetupPasscodeView: UIViewControllerRepresentable { func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode passcode: String) -> Bool { if passcode == NCKeychain().passcode { - self.parent.isLockActive = false + parent.isLockActive = false NCKeychain().passcode = nil return true } + return false } } diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index c3ab139e4e..9367cab8c7 100644 --- a/iOSClient/Supporting Files/en.lproj/Localizable.strings +++ b/iOSClient/Supporting Files/en.lproj/Localizable.strings @@ -868,6 +868,7 @@ "Enter Passcode" = "Enter Passcode"; "Enter a new passcode" = "Enter a new passcode"; "Confirm new passcode" = "Confirm new passcode"; +"_too_many_failed_passcode_attempts_error_" = "Too many failed passcode attempts. Please try again."; "Passcodes didn't match. Try again." = "Passcodes didn't match. Try again."; "Delete" = "Delete"; "Cancel" = "Cancel"; diff --git a/iOSClient/Utility/NCContentPresenter.swift b/iOSClient/Utility/NCContentPresenter.swift index 5c45e36ac4..50599bf478 100644 --- a/iOSClient/Utility/NCContentPresenter.swift +++ b/iOSClient/Utility/NCContentPresenter.swift @@ -58,6 +58,10 @@ class NCContentPresenter: NSObject { // MARK: - Message + func showCustomMessage(title: String = "", message: String, priority: EKAttributes.Precedence.Priority = .normal, delay: TimeInterval = NCGlobal.shared.dismissAfterSecond, type: messageType) { + self.flatTop(title: NSLocalizedString(title, comment: ""), description: message, delay: delay, type: type, priority: priority, dropEnqueuedEntries: false) + } + func showError(error: NKError, priority: EKAttributes.Precedence.Priority = .normal) { messageNotification( "_error_", From 02b40e114038a911a2a38e29b01b3ff2ff2debd3 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 29 Nov 2024 12:28:18 +0100 Subject: [PATCH 23/25] Fix Signed-off-by: Milen Pivchev --- iOSClient/Settings/Settings/SetupPasscodeView.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/iOSClient/Settings/Settings/SetupPasscodeView.swift b/iOSClient/Settings/Settings/SetupPasscodeView.swift index 88cc230530..3e3fc715b9 100644 --- a/iOSClient/Settings/Settings/SetupPasscodeView.swift +++ b/iOSClient/Settings/Settings/SetupPasscodeView.swift @@ -15,7 +15,7 @@ import PopupView struct SetupPasscodeView: UIViewControllerRepresentable { @Binding var isLockActive: Bool var changePasscode: Bool = false - let maxFailedAttempts = 3 + let maxFailedAttempts = 2 func makeUIViewController(context: Context) -> UIViewController { let laContext = LAContext() @@ -82,12 +82,15 @@ struct SetupPasscodeView: UIViewControllerRepresentable { } func passcodeSettingsViewController(_ passcodeSettingsViewController: TOPasscodeSettingsViewController, didAttemptCurrentPasscode passcode: String) -> Bool { - if passcodeSettingsViewController.failedPasscodeAttemptCount == parent.maxFailedAttempts - 1 { + print(passcodeSettingsViewController.failedPasscodeAttemptCount) + if passcode == NCKeychain().passcode { + return true + } else if passcodeSettingsViewController.failedPasscodeAttemptCount == parent.maxFailedAttempts { passcodeSettingsViewController.dismiss(animated: true) NCContentPresenter().showCustomMessage(message: NSLocalizedString("_too_many_failed_passcode_attempts_error_", comment: ""), type: .error) } - return passcode == NCKeychain().passcode + return false } func passcodeSettingsViewController(_ passcodeSettingsViewController: TOPasscodeSettingsViewController, didChangeToNewPasscode passcode: String, of type: TOPasscodeType) { @@ -99,7 +102,7 @@ struct SetupPasscodeView: UIViewControllerRepresentable { func didTapCancel(in passcodeViewController: TOPasscodeViewController) { passcodeViewController.dismiss(animated: true) } - + func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode passcode: String) -> Bool { if passcode == NCKeychain().passcode { parent.isLockActive = false From 592ae6afcb9900addffc033435003b638eb233a7 Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 29 Nov 2024 12:30:34 +0100 Subject: [PATCH 24/25] WIP Signed-off-by: Milen Pivchev --- iOSClient/Settings/Settings/SetupPasscodeView.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/iOSClient/Settings/Settings/SetupPasscodeView.swift b/iOSClient/Settings/Settings/SetupPasscodeView.swift index 3e3fc715b9..0d6e6ce93c 100644 --- a/iOSClient/Settings/Settings/SetupPasscodeView.swift +++ b/iOSClient/Settings/Settings/SetupPasscodeView.swift @@ -15,7 +15,7 @@ import PopupView struct SetupPasscodeView: UIViewControllerRepresentable { @Binding var isLockActive: Bool var changePasscode: Bool = false - let maxFailedAttempts = 2 + let maxFailedAttempts = 2 // + 1 = 3... The lib failed attempt counter starts at 0. Why? Who knows. func makeUIViewController(context: Context) -> UIViewController { let laContext = LAContext() @@ -102,14 +102,14 @@ struct SetupPasscodeView: UIViewControllerRepresentable { func didTapCancel(in passcodeViewController: TOPasscodeViewController) { passcodeViewController.dismiss(animated: true) } - + func passcodeViewController(_ passcodeViewController: TOPasscodeViewController, isCorrectCode passcode: String) -> Bool { if passcode == NCKeychain().passcode { parent.isLockActive = false NCKeychain().passcode = nil return true } - + return false } } From c22be9e3581d90a2b9ba8b6419c4e0316eac48cf Mon Sep 17 00:00:00 2001 From: Milen Pivchev Date: Fri, 29 Nov 2024 12:38:00 +0100 Subject: [PATCH 25/25] Refactor Signed-off-by: Milen Pivchev --- iOSClient/Settings/Settings/SetupPasscodeView.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/iOSClient/Settings/Settings/SetupPasscodeView.swift b/iOSClient/Settings/Settings/SetupPasscodeView.swift index 0d6e6ce93c..5543dc5907 100644 --- a/iOSClient/Settings/Settings/SetupPasscodeView.swift +++ b/iOSClient/Settings/Settings/SetupPasscodeView.swift @@ -82,7 +82,6 @@ struct SetupPasscodeView: UIViewControllerRepresentable { } func passcodeSettingsViewController(_ passcodeSettingsViewController: TOPasscodeSettingsViewController, didAttemptCurrentPasscode passcode: String) -> Bool { - print(passcodeSettingsViewController.failedPasscodeAttemptCount) if passcode == NCKeychain().passcode { return true } else if passcodeSettingsViewController.failedPasscodeAttemptCount == parent.maxFailedAttempts {