diff --git a/Brand/NCBrand.swift b/Brand/NCBrand.swift index f02ea986a7..b496e61340 100755 --- a/Brand/NCBrand.swift +++ b/Brand/NCBrand.swift @@ -72,12 +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_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 @@ -95,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 } @@ -117,6 +117,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_passcode_lock] as? String { + enforce_passcode_lock = (str as NSString).boolValue + } } } diff --git a/Nextcloud.xcodeproj/project.pbxproj b/Nextcloud.xcodeproj/project.pbxproj index fb804c09f2..eccc610ef5 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 */; }; @@ -560,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 */; }; @@ -1198,6 +1198,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 = ""; }; @@ -1381,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 = ""; }; @@ -2350,7 +2350,6 @@ F76882142C0DD1E7001CF441 /* Acknowledgements.rtf */, F76882132C0DD1E7001CF441 /* NCKeychain.swift */, F76882152C0DD1E7001CF441 /* NCSettingsBundleHelper.swift */, - F76882122C0DD1E7001CF441 /* Settings.bundle */, ); path = Settings; sourceTree = ""; @@ -2381,6 +2380,7 @@ F768820D2C0DD1E7001CF441 /* E2EE */, F76882112C0DD1E7001CF441 /* NCSettingsModel.swift */, F768820C2C0DD1E7001CF441 /* NCSettingsView.swift */, + F3754A7C2CF87D600009312E /* SetupPasscodeView.swift */, ); path = Settings; sourceTree = ""; @@ -3753,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 */, @@ -4267,6 +4266,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/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 0c8a0aad70..bc25f39946 100644 --- a/iOSClient/Main/NCMainTabBarController.swift +++ b/iOSClient/Main/NCMainTabBarController.swift @@ -22,6 +22,7 @@ // import UIKit +import SwiftUI struct NavigationCollectionViewCommon { var serverUrl: String @@ -55,6 +56,13 @@ class NCMainTabBarController: UITabBarController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) previousIndex = selectedIndex + + if NCBrandOptions.shared.enforce_passcode_lock && NCKeychain().passcode.isEmptyOrNil { + let vc = UIHostingController(rootView: SetupPasscodeView(isLockActive: .constant(false))) + vc.isModalInPresentation = true + + present(vc, animated: true) + } } @objc func changeTheming(_ notification: NSNotification) { diff --git a/iOSClient/NCGlobal.swift b/iOSClient/NCGlobal.swift index ab0f5beff8..80594ccaaa 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_passcode_lock = "enforce_passcode_lock" // MORE NEXTCLOUD APPS // diff --git a/iOSClient/SceneDelegate.swift b/iOSClient/SceneDelegate.swift index 07a0546b89..63b07f8020 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? 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.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 6b57b356ce..0000000000 Binary files a/iOSClient/Settings/Settings.bundle/en.lproj/Root.strings and /dev/null differ diff --git a/iOSClient/Settings/Settings/NCSettingsModel.swift b/iOSClient/Settings/Settings/NCSettingsModel.swift index ee25dfe4cd..8e870e978f 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 @@ -111,92 +113,3 @@ class NCSettingsModel: ObservableObject, ViewOnAppearHandling { keychain.accountRequest = accountRequest } } - -struct PasscodeView: UIViewControllerRepresentable { - @Binding var isLockActive: Bool - - func makeUIViewController(context: Context) -> UIViewController { - let laContext = LAContext() - var error: NSError? - if NCKeychain().passcode != nil { - 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 { - 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: PasscodeView - init(_ parent: PasscodeView) { - 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/NCSettingsView.swift b/iOSClient/Settings/Settings/NCSettingsView.swift index 75fc02d567..c3bc9f676f 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 @@ -50,14 +52,11 @@ 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)) } - }, header: { }, footer: { Text(NSLocalizedString("_autoupload_description_", comment: "")) }) @@ -70,52 +69,69 @@ 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_passcode_lock ? 0.5 : 1) Text(model.isLockActive ? NSLocalizedString("_lock_active_", comment: "") : NSLocalizedString("_lock_not_active_", comment: "")) } - .font(.system(size: 16)) }) .tint(Color(NCBrandColor.shared.textColor)) - .sheet(isPresented: $showPasscode) { - PasscodeView(isLockActive: $model.isLockActive) + .disabled(NCBrandOptions.shared.enforce_passcode_lock) + }, header: { + Text(NSLocalizedString("_privacy_", comment: "")) + }, footer: { + if NCBrandOptions.shared.enforce_passcode_lock { + Text(NSLocalizedString("_lock_cannot_disable_mdm_", comment: "")) } - /// 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() + }) + + if model.isLockActive { + Section(content: { + Group { + // Change passcode + Button(action: { + showChangePasscode.toggle() + }, label: { + VStack { + Text(NSLocalizedString("_change_lock_passcode_", comment: "")) + .tint(Color(NCBrandColor.shared.textColor)) + } + }) + /// Enable Touch ID + Toggle(NSLocalizedString("_enable_touch_face_id_", comment: ""), isOn: $model.enableTouchID) + .onChange(of: model.enableTouchID) { _ in + model.updateTouchIDSetting() + } + + 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) + .onChange(of: model.resetWrongAttempts) { _ in + model.updateResetWrongAttemptsSetting() + } } - /// Privacy screen + }, footer: { + Text(String(format: NSLocalizedString("_reset_wrong_passcode_desc_", comment: ""), NCBrandOptions.shared.resetAppPasscodeAttempts)) + }) + .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) + } + + Section { + /// Splash screen when app inactive 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) - }) + } + .tint(Color(NCBrandColor.shared.getElement(account: model.session.account))) + /// Display Section(header: Text(NSLocalizedString("_display_", comment: "")), content: { NavigationLink(destination: LazyView { @@ -125,12 +141,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 @@ -143,12 +157,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: { @@ -156,10 +168,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)) } }) @@ -167,7 +177,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() @@ -176,8 +185,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) { @@ -192,12 +199,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 @@ -214,7 +219,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) { @@ -228,12 +232,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) { @@ -252,7 +254,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) { @@ -266,6 +267,12 @@ struct NCSettingsView: View { Text(model.footerApp + model.footerServer + model.footerSlogan) }) } + .sheet(isPresented: $showPasscode) { + SetupPasscodeView(isLockActive: $model.isLockActive) + } + .sheet(isPresented: $showChangePasscode) { + SetupPasscodeView(isLockActive: $model.isLockActive, changePasscode: true) + } .navigationBarTitle(NSLocalizedString("_settings_", comment: "")) .defaultViewModifier(model) } @@ -283,12 +290,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/Settings/Settings/SetupPasscodeView.swift b/iOSClient/Settings/Settings/SetupPasscodeView.swift new file mode 100644 index 0000000000..5543dc5907 --- /dev/null +++ b/iOSClient/Settings/Settings/SetupPasscodeView.swift @@ -0,0 +1,115 @@ +// +// 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 +import PopupView + +struct SetupPasscodeView: UIViewControllerRepresentable { + @Binding var isLockActive: Bool + var changePasscode: Bool = false + let maxFailedAttempts = 2 // + 1 = 3... The lib failed attempt counter starts at 0. Why? Who knows. + + func makeUIViewController(context: Context) -> UIViewController { + let laContext = LAContext() + var error: NSError? + if !NCKeychain().passcode.isEmptyOrNil, !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 + } + + passcodeVC.delegate = context.coordinator + return passcodeVC + } else { + let passcodeSettingsVC = TOPasscodeSettingsViewController() + passcodeSettingsVC.hideOptionsButton = true + passcodeSettingsVC.requireCurrentPasscode = changePasscode + passcodeSettingsVC.passcodeType = .sixDigits + passcodeSettingsVC.delegate = context.coordinator + return passcodeSettingsVC + } + } + + 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, didAttemptCurrentPasscode passcode: String) -> Bool { + 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 false + } + + func passcodeSettingsViewController(_ passcodeSettingsViewController: TOPasscodeSettingsViewController, didChangeToNewPasscode passcode: String, of type: TOPasscodeType) { + NCKeychain().passcode = passcode + parent.isLockActive = true + passcodeSettingsViewController.dismiss(animated: true) + } + + 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 + } + } +} diff --git a/iOSClient/Supporting Files/en.lproj/Localizable.strings b/iOSClient/Supporting Files/en.lproj/Localizable.strings index e318445cd5..9367cab8c7 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 passcode lock is not permitted by your configuration profile."; /* Background of the file listing view */ "_use_as_background_" = "Use it as a background"; @@ -305,10 +305,9 @@ "_passcode_" = "Password"; "_enter_password_" = "Enter password …"; "_lock_" = "Lock"; -"_lock_active_" = "Lock: On"; -"_lock_not_active_" = "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."; +"_lock_active_" = "Passcode Lock: On"; +"_lock_not_active_" = "Passcode Lock: Off"; +"_lock_protection_no_screen_" = "Do not ask for passcode on startup"; "_enable_touch_face_id_" = "Enable Touch/Face ID"; "_url_" = "URL"; "_username_" = "Username"; @@ -380,11 +379,11 @@ "_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"; -"_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 +393,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"; @@ -409,7 +408,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 +417,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"; @@ -472,14 +472,15 @@ "_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_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"; @@ -489,11 +490,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 +552,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 +603,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 +648,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 +662,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 +688,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 +699,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 +719,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 +737,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 +745,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 +803,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"; @@ -867,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"; @@ -879,10 +881,10 @@ "_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"; +"_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."; @@ -931,40 +933,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"; -"_reset_application_done_" = "Reset application, done."; -"_rename_already_exists_" = "A file with this name already exists"; +"_unauthorizedFilesPasscode_" = "Files app cannot be used with an activated passcode."; +"_disableFilesApp_" = "Files app cannot be used because it is disabled."; +"_reset_application_done_" = "Finished resetting application."; +"_rename_already_exists_" = "A file with this name already exists."; "_created_" = "Created"; "_recipients_" = "Recipients"; "_are_sure_" = "Are you sure?"; @@ -977,15 +979,16 @@ "_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"; -"_reset_wrong_passcode_desc_" = "Use \"Reset application\" to remove all accounts and local data after %d failed code entry attempts."; +"_reset_wrong_passcode_option_" = "Reset application after failed 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"; @@ -1010,9 +1013,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 +1024,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 +1043,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"; @@ -1118,7 +1121,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."; 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_",