From ea324ea7d8b463c454e9593e82429ef6826c1297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Tue, 25 Mar 2025 21:06:01 +0100 Subject: [PATCH] Add option to choose save directory & automatically accept files --- NearDrop/AppDelegate.swift | 89 ++++++++++++++----- NearDrop/Base.lproj/Localizable.strings | 11 ++- NearDrop/NearDrop.entitlements | 2 +- NearDrop/SettingsView.swift | 84 +++++++++++++++++ NearDrop/TutorialView.swift | 59 +++++++----- NearDrop/UserDefaultsKeys.swift | 2 + NearDrop/WelcomeScreen.swift | 27 ++++-- NearDrop/ar.lproj/Localizable.strings | 11 ++- NearDrop/de.lproj/Localizable.strings | 11 ++- NearDrop/es.lproj/Localizable.strings | 11 ++- NearDrop/fr.lproj/Localizable.strings | 11 ++- NearDrop/hu.lproj/Localizable.strings | 11 ++- NearDrop/it.lproj/Localizable.strings | 11 ++- NearDrop/ja.lproj/Localizable.strings | 11 ++- NearDrop/ko.lproj/Localizable.strings | 11 ++- NearDrop/nl.lproj/Localizable.strings | 11 ++- NearDrop/pt-BR.lproj/Localizable.strings | 11 ++- NearDrop/pt-PT.lproj/Localizable.strings | 11 ++- NearDrop/ro.lproj/Localizable.strings | 11 ++- NearDrop/ru.lproj/Localizable.strings | 11 ++- NearDrop/th.lproj/Localizable.strings | 11 ++- NearDrop/uk.lproj/Localizable.strings | 11 ++- NearDrop/zh-Hans.lproj/Localizable.strings | 11 ++- NearbyShare/InboundNearbyConnection.swift | 55 ++++++++++-- QuickDrop.xcodeproj/project.pbxproj | 6 ++ .../xcshareddata/xcschemes/QuickDrop.xcscheme | 2 +- 26 files changed, 436 insertions(+), 77 deletions(-) create mode 100644 NearDrop/SettingsView.swift diff --git a/NearDrop/AppDelegate.swift b/NearDrop/AppDelegate.swift index 8d6c6d5..e9db735 100644 --- a/NearDrop/AppDelegate.swift +++ b/NearDrop/AppDelegate.swift @@ -13,7 +13,7 @@ import StoreKit import AudioToolbox @main -class AppDelegate: NSObject, NSApplicationDelegate, MainAppDelegate{ +class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate, MainAppDelegate{ private var statusItem: NSStatusItem? private var activeIncomingTransfers: [String : TransferInfo] = [:] @@ -85,6 +85,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, MainAppDelegate{ } } } + + UNUserNotificationCenter.current().delegate=self } @@ -149,6 +151,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, MainAppDelegate{ return UserDefaults.standard.bool(forKey: UserDefaultsKeys.plusVersion.rawValue) } + func transmissionCount() -> Int { return UserDefaults.standard.integer(forKey: UserDefaultsKeys.transmissionCount.rawValue) } @@ -161,12 +164,14 @@ class AppDelegate: NSObject, NSApplicationDelegate, MainAppDelegate{ } } + @objc func getSupport() { if let url = URL(string: "mailto:quickdrop@leonboettger.com?subject=QuickDrop") { NSWorkspace.shared.open(url) } } + // Action for "Privacy Policy" menu item @objc func openPrivacyPolicy() { if let url = URL(string: "http://leonboettger.com/privacy") { @@ -174,20 +179,31 @@ class AppDelegate: NSObject, NSApplicationDelegate, MainAppDelegate{ } } + func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { statusItem?.isVisible=true return true } + + public func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + completionHandler([.sound, .banner]) + } + + func applicationWillTerminate(_ aNotification: Notification) { UNUserNotificationCenter.current().removeAllDeliveredNotifications() iapManager?.stopObserving() } + func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { return true } + public func continueTransmission(accept: Bool, transferID: String) { NearbyConnectionManager.shared.submitUserConsent(transferID: transferID, accept: accept) @@ -196,17 +212,17 @@ class AppDelegate: NSObject, NSApplicationDelegate, MainAppDelegate{ } } + func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo) { self.activeIncomingTransfers[transfer.id] = TransferInfo(device: device, transfer: transfer) AudioManager.playSound() - let alert = NSAlert() - alert.alertStyle = .informational + let acceptAutomatically = UserDefaults.standard.bool(forKey: UserDefaultsKeys.automaticallyAcceptFiles.rawValue) let fileStr: String - if let textTitle = transfer.textDescription{ + if let textTitle = transfer.textDescription { fileStr = textTitle } else if transfer.files.count == 1 { fileStr = transfer.files[0].name @@ -214,34 +230,65 @@ class AppDelegate: NSObject, NSApplicationDelegate, MainAppDelegate{ fileStr = String.localizedStringWithFormat(NSLocalizedString("NFiles", value: "%d files", comment: ""), transfer.files.count) } - let mainMessage = String(format: NSLocalizedString("DeviceSendingFiles", value: "%1$@ is sending you %2$@", comment: ""), arguments: [device.name, fileStr]) + let mainMessage = String(format: (acceptAutomatically ? "DeviceCurrentlySendingFiles" : "DeviceSendingFiles").localized(), arguments: [device.name, fileStr]) let pinCodeMessage = String(format:NSLocalizedString("PinCode", value: "PIN: %@", comment: ""), arguments: [transfer.pinCode ?? "?"]) let transferID = transfer.id - alert.messageText = "QuickDrop - \(pinCodeMessage)" - alert.informativeText = mainMessage - alert.addButton(withTitle: NSLocalizedString("Accept", comment: "")) - alert.addButton(withTitle: NSLocalizedString("Decline", comment: "")) - let result = alert.runModal() - - if result == .alertFirstButtonReturn { + if acceptAutomatically { + pressAcceptButton(transferID: transfer.id) - if !isPlusVersion() && transmissionCount() > 0 { - self.openPlusScreen { - self.continueTransmission(accept: self.isPlusVersion(), transferID: transferID) + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, err in + if granted { + log("User granted notification permissions") + + let notificationContent = UNMutableNotificationContent() + notificationContent.title = "QuickDrop" + notificationContent.body = mainMessage + notificationContent.sound = nil + let notificationId = UUID().uuidString + + let notificationReq = UNNotificationRequest(identifier: notificationId, content: notificationContent, trigger: nil) + UNUserNotificationCenter.current().add(notificationReq) + + DispatchQueue.main.asyncAfter(deadline: .now() + 5) { + UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [notificationId]) + } } } - else { - continueTransmission(accept: true, transferID: transferID) + } + else { + let alert = NSAlert() + alert.alertStyle = .informational + + alert.messageText = "QuickDrop - \(pinCodeMessage)" + alert.informativeText = mainMessage + alert.addButton(withTitle: NSLocalizedString("Accept", comment: "")) + alert.addButton(withTitle: NSLocalizedString("Decline", comment: "")) + + let result = alert.runModal() + + if result == .alertFirstButtonReturn { + pressAcceptButton(transferID: transferID) + } else if result == .alertSecondButtonReturn { + continueTransmission(accept: false, transferID: transferID) } - - } else if result == .alertSecondButtonReturn { - - continueTransmission(accept: false, transferID: transferID) } } + + private func pressAcceptButton(transferID: String) { + if !isPlusVersion() && transmissionCount() > 0 { + self.openPlusScreen { + self.continueTransmission(accept: self.isPlusVersion(), transferID: transferID) + } + } + else { + continueTransmission(accept: true, transferID: transferID) + } + } + + func incomingTransfer(id: String, didFinishWith error: Error?) { guard let transfer = self.activeIncomingTransfers[id] else { return } if let error = error { diff --git a/NearDrop/Base.lproj/Localizable.strings b/NearDrop/Base.lproj/Localizable.strings index 54eead8..e4dc6c4 100644 --- a/NearDrop/Base.lproj/Localizable.strings +++ b/NearDrop/Base.lproj/Localizable.strings @@ -52,7 +52,16 @@ "PrivacyPolicy" = "Privacy Policy"; -"UserManual" = "User Manual"; +"DeviceCurrentlySendingFiles" = "%1$@ is sending you %2$@"; +"UserManual" = "User Manual & Settings"; +"Settings" = "Settings"; +"AutomaticallyAcceptFiles" = "Automatically Accept Incoming Files"; +"AutomaticallyAcceptFilesFooter" = "Enabling this setting is only recommended in your private network. Anyone on the same network can send files to your device without your consent."; + +"SaveFilesTo" = "Save Incoming Files To:"; +"SaveFilesLocation" = "Incomping Files Location"; +"SaveFilesToButton" = "Select…"; +"DownloadsFolder" = "Downloads"; "SendFiles" = "Send Files"; "ReceiveFiles" = "Receive Files"; diff --git a/NearDrop/NearDrop.entitlements b/NearDrop/NearDrop.entitlements index 0272206..02ce7ec 100644 --- a/NearDrop/NearDrop.entitlements +++ b/NearDrop/NearDrop.entitlements @@ -6,7 +6,7 @@ com.apple.security.files.downloads.read-write - com.apple.security.files.user-selected.read-only + com.apple.security.files.user-selected.read-write com.apple.security.network.client diff --git a/NearDrop/SettingsView.swift b/NearDrop/SettingsView.swift new file mode 100644 index 0000000..41ec53a --- /dev/null +++ b/NearDrop/SettingsView.swift @@ -0,0 +1,84 @@ +// +// SettingsView.swift +// QuickDrop +// +// Created by Leon Böttger on 24.03.25. +// + +import SwiftUI +import AppKit + +struct SettingsView: View { + @AppStorage(UserDefaultsKeys.automaticallyAcceptFiles.rawValue) private var automaticallyAcceptFiles = false + @AppStorage(UserDefaultsKeys.saveFolderBookmark.rawValue) private var saveFolderPath: Data = Data() + + var body: some View { + + LargeAppIconView(title: "Settings") { + Form { + Toggle("AutomaticallyAcceptFiles", isOn: $automaticallyAcceptFiles) + .padding(.top, 10) + + Text("AutomaticallyAcceptFilesFooter") + .font(.footnote) + .foregroundColor(.red) + .padding(.top, 5) + + Divider() + .padding(.vertical, 10) + + HStack { + Text("SaveFilesTo") + Spacer() + Text(getSavedFolderPath() ?? "DownloadsFolder".localized()) + .foregroundColor(saveFolderPath.isEmpty ? .gray : .primary) + Button("SaveFilesToButton") { + selectFolder() + } + } + } + } + } + + private func getSavedFolderPath() -> String? { + if !saveFolderPath.isEmpty { + var isStale = false + do { + let url = try URL(resolvingBookmarkData: saveFolderPath, options: [], relativeTo: nil, bookmarkDataIsStale: &isStale) + return url.lastPathComponent // Returns the folder path as a string + } catch { + print("Failed to resolve bookmark: \(error)") + } + } + return nil // No saved folder + } + + private func selectFolder() { + let panel = NSOpenPanel() + panel.title = "SaveFilesLocation".localized() + panel.canChooseFiles = false + panel.canChooseDirectories = true + panel.allowsMultipleSelection = false + + if panel.runModal() == .OK, let url = panel.urls.first { + do { + print("Selected folder: \(url)") + + let bookmarkData = try url.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) + + print(bookmarkData) + + UserDefaults.standard.set(bookmarkData, forKey: UserDefaultsKeys.saveFolderBookmark.rawValue) + } catch { + print("Failed to save security-scoped bookmark: \(error)") + } + } + } +} + +struct SettingsView_Previews: PreviewProvider { + static var previews: some View { + SettingsView() + .frame(width: 800, height: 500) + } +} diff --git a/NearDrop/TutorialView.swift b/NearDrop/TutorialView.swift index 11719e9..eac0183 100644 --- a/NearDrop/TutorialView.swift +++ b/NearDrop/TutorialView.swift @@ -21,28 +21,9 @@ struct TutorialView: View { @AppStorage(UserDefaultsKeys.plusVersion.rawValue) var isPlusVersion = false var body: some View { - ScrollView { - + + LargeAppIconView(title: title) { VStack { - Image("AppIconHighRes") - .resizable() - .frame(width: 150, height: 150) - .padding(.top, 50) - .onTapGesture { - taps += 1 - print("Tapped \(taps) times") - - if taps == 5 { - let logString = LogManager.sharedInstance.getLogString() - copyToClipboard(logString) - print("Copied log to clipboard") - } - } - - Text(title.localized()) - .font(.largeTitle) - .padding() - Text(text.localized()) .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) @@ -90,6 +71,42 @@ struct TutorialView: View { .padding() } } + } + } +} + + +struct LargeAppIconView: View { + + let title: String + let bottomView: () -> Content + @State var taps = 0 + + var body: some View { + ScrollView { + + VStack { + Image("AppIconHighRes") + .resizable() + .frame(width: 150, height: 150) + .padding(.top, 50) + .onTapGesture { + taps += 1 + print("Tapped \(taps) times") + + if taps == 5 { + let logString = LogManager.sharedInstance.getLogString() + copyToClipboard(logString) + print("Copied log to clipboard") + } + } + + Text(title.localized()) + .font(.largeTitle) + .padding() + + bottomView() + } .frame(maxWidth: 500) .frame(maxWidth: .infinity) } diff --git a/NearDrop/UserDefaultsKeys.swift b/NearDrop/UserDefaultsKeys.swift index 8506928..8436b34 100644 --- a/NearDrop/UserDefaultsKeys.swift +++ b/NearDrop/UserDefaultsKeys.swift @@ -12,4 +12,6 @@ enum UserDefaultsKeys: String, CaseIterable { case appLaunchedBefore = "ShowedWelcomeScreen" case plusVersion = "isPlusVersion" case transmissionCount = "reviewRequestCountKey" + case automaticallyAcceptFiles = "automaticallyAcceptFiles" + case saveFolderBookmark = "saveFolderBookmark" } diff --git a/NearDrop/WelcomeScreen.swift b/NearDrop/WelcomeScreen.swift index 414451c..32e0b66 100644 --- a/NearDrop/WelcomeScreen.swift +++ b/NearDrop/WelcomeScreen.swift @@ -20,21 +20,24 @@ struct WelcomeScreen: View { HStack(spacing: 0) { List(selection: $selection) { - ForEach(Tabs.allCases, id: \.self) { tab in + ForEach(Tabs.allCases.filter({$0 != .settings}), id: \.self) { tab in Label(tab.title, systemImage: tab.systemImage) // .font(.system(size: 13)) .tag(tab) .frame(height: 30) } + + Divider() + + Label(Tabs.settings.title, systemImage: Tabs.settings.systemImage) + .tag(Tabs.settings) + .frame(height: 30) } .frame(width: 220) .listStyle(SidebarListStyle()) Divider() .opacity(colorScheme == .light ? 1 : 0) -// .overlay( -// Color.black.opacity(colorScheme == .dark ? 1 : 0) -// ) .edgesIgnoringSafeArea(.vertical) ZStack { @@ -47,8 +50,11 @@ struct WelcomeScreen: View { case .send: TutorialView(title: "SendFiles", text: "SendFilesDescription", showsLicense: false, openIAP: openIAP) - default: + case .troubleshooting: TutorialView(title: "Troubleshooting", text: "TroubleshootingDescription", showsLicense: false, openIAP: openIAP) + + default: + SettingsView() } } } @@ -61,6 +67,7 @@ enum Tabs: CaseIterable { case receive case send case troubleshooting + case settings var title: String { switch self { @@ -70,17 +77,21 @@ enum Tabs: CaseIterable { return "SendFiles".localized() case .troubleshooting: return "DeviceNotShown".localized() + case .settings: + return "Settings".localized() } } var systemImage: String { switch self { case .receive: - return "tray.and.arrow.down.fill" + return "tray.and.arrow.down" case .send: - return "tray.and.arrow.up.fill" + return "tray.and.arrow.up" case .troubleshooting: - return "exclamationmark.triangle.fill" + return "exclamationmark.triangle" + case .settings: + return "gear" } } } diff --git a/NearDrop/ar.lproj/Localizable.strings b/NearDrop/ar.lproj/Localizable.strings index 06861b5..a675f05 100644 --- a/NearDrop/ar.lproj/Localizable.strings +++ b/NearDrop/ar.lproj/Localizable.strings @@ -43,7 +43,16 @@ "RecommendedApps" = "التطبيقات الموصى بها"; "GetSupport" = "الدعم"; "PrivacyPolicy" = "سياسة الخصوصية"; -"UserManual" = "دليل المستخدم"; +"DeviceCurrentlySendingFiles" = "%1$@ يرسل لك %2$@"; +"UserManual" = "دليل المستخدم والإعدادات"; +"Settings" = "الإعدادات"; +"AutomaticallyAcceptFiles" = "قبول الملفات الواردة تلقائيًا"; +"AutomaticallyAcceptFilesFooter" = "يُوصى بتمكين هذا الإعداد فقط في شبكتك الخاصة. يمكن لأي شخص على نفس الشبكة إرسال الملفات إلى جهازك دون موافقتك."; + +"SaveFilesTo" = "حفظ الملفات الواردة في:"; +"SaveFilesLocation" = "موقع الملفات الواردة"; +"SaveFilesToButton" = "تحديد…"; +"DownloadsFolder" = "التنزيلات"; "SendFiles" = "إرسال الملفات"; "ReceiveFiles" = "استقبال الملفات"; "DeviceNotShown" = "الجهاز غير ظاهر؟"; diff --git a/NearDrop/de.lproj/Localizable.strings b/NearDrop/de.lproj/Localizable.strings index ab3a962..6ad956a 100644 --- a/NearDrop/de.lproj/Localizable.strings +++ b/NearDrop/de.lproj/Localizable.strings @@ -45,7 +45,16 @@ "PrivacyPolicy" = "Datenschutzerklärung"; -"UserManual" = "Benutzerhandbuch"; +"DeviceCurrentlySendingFiles" = "%1$@ sendet dir %2$@"; +"UserManual" = "Benutzerhandbuch & Einstellungen"; +"Settings" = "Einstellungen"; +"AutomaticallyAcceptFiles" = "Eingehende Dateien automatisch akzeptieren"; +"AutomaticallyAcceptFilesFooter" = "Diese Einstellung wird nur in privaten Netzwerken empfohlen. Jede Person im selben Netzwerk kann ohne deine Zustimmung Dateien an diesen Mac senden."; + +"SaveFilesTo" = "Eingehende Dateien speichern in:"; +"SaveFilesLocation" = "Speicherort der eingehenden Dateien"; +"SaveFilesToButton" = "Auswählen…"; +"DownloadsFolder" = "Downloads"; "SendFiles" = "Dateien senden"; "ReceiveFiles" = "Dateien empfangen"; diff --git a/NearDrop/es.lproj/Localizable.strings b/NearDrop/es.lproj/Localizable.strings index 924ca57..f50dd71 100644 --- a/NearDrop/es.lproj/Localizable.strings +++ b/NearDrop/es.lproj/Localizable.strings @@ -43,7 +43,16 @@ "RecommendedApps" = "Apps recomendadas"; "GetSupport" = "Soporte"; "PrivacyPolicy" = "Política de privacidad"; -"UserManual" = "Manual de usuario"; +"DeviceCurrentlySendingFiles" = "%1$@ te está enviando %2$@"; +"UserManual" = "Manual de usuario y Configuración"; +"Settings" = "Configuración"; +"AutomaticallyAcceptFiles" = "Aceptar automáticamente archivos entrantes"; +"AutomaticallyAcceptFilesFooter" = "Se recomienda habilitar esta opción solo en su red privada. Cualquier persona en la misma red puede enviar archivos a su dispositivo sin su consentimiento."; + +"SaveFilesTo" = "Guardar archivos entrantes en:"; +"SaveFilesLocation" = "Ubicación de archivos entrantes"; +"SaveFilesToButton" = "Seleccionar…"; +"DownloadsFolder" = "Descargas"; "SendFiles" = "Enviar archivos"; "ReceiveFiles" = "Recibir archivos"; "DeviceNotShown" = "¿Dispositivo no mostrado?"; diff --git a/NearDrop/fr.lproj/Localizable.strings b/NearDrop/fr.lproj/Localizable.strings index 17c3a19..d203cce 100644 --- a/NearDrop/fr.lproj/Localizable.strings +++ b/NearDrop/fr.lproj/Localizable.strings @@ -43,7 +43,16 @@ "RecommendedApps" = "Applications recommandées"; "GetSupport" = "Support"; "PrivacyPolicy" = "Politique de confidentialité"; -"UserManual" = "Manuel d'utilisateur"; +"DeviceCurrentlySendingFiles" = "%1$@ vous envoie %2$@"; +"UserManual" = "Manuel d'utilisation et Paramètres"; +"Settings" = "Paramètres"; +"AutomaticallyAcceptFiles" = "Accepter automatiquement les fichiers entrants"; +"AutomaticallyAcceptFilesFooter" = "L'activation de ce paramètre est recommandée uniquement sur votre réseau privé. Toute personne sur le même réseau peut envoyer des fichiers sur votre appareil sans votre consentement."; + +"SaveFilesTo" = "Enregistrer les fichiers reçus dans :"; +"SaveFilesLocation" = "Emplacement des fichiers reçus"; +"SaveFilesToButton" = "Sélectionner…"; +"DownloadsFolder" = "Téléchargements"; "SendFiles" = "Envoyer des fichiers"; "ReceiveFiles" = "Recevoir des fichiers"; "DeviceNotShown" = "Appareil non affiché?"; diff --git a/NearDrop/hu.lproj/Localizable.strings b/NearDrop/hu.lproj/Localizable.strings index 16f1a47..dc33370 100644 --- a/NearDrop/hu.lproj/Localizable.strings +++ b/NearDrop/hu.lproj/Localizable.strings @@ -43,7 +43,16 @@ "RecommendedApps" = "Ajánlott alkalmazások"; "GetSupport" = "Támogatás"; "PrivacyPolicy" = "Adatvédelmi irányelvek"; -"UserManual" = "Felhasználói kézikönyv"; +"DeviceCurrentlySendingFiles" = "%1$@ éppen küldi neked: %2$@"; +"UserManual" = "Felhasználói kézikönyv és Beállítások"; +"Settings" = "Beállítások"; +"AutomaticallyAcceptFiles" = "Bejövő fájlok automatikus elfogadása"; +"AutomaticallyAcceptFilesFooter" = "Ennek a beállításnak az engedélyezése csak a saját privát hálózatán ajánlott. Bárki a hálózaton belül fájlokat küldhet az eszközére az ön beleegyezése nélkül."; + +"SaveFilesTo" = "Bejövő fájlok mentése ide:"; +"SaveFilesLocation" = "Bejövő fájlok helye"; +"SaveFilesToButton" = "Kiválasztás…"; +"DownloadsFolder" = "Letöltések"; "SendFiles" = "Fájlok küldése"; "ReceiveFiles" = "Fájlok fogadása"; "DeviceNotShown" = "Az eszköz nem látható?"; diff --git a/NearDrop/it.lproj/Localizable.strings b/NearDrop/it.lproj/Localizable.strings index a5f6ac4..95c215a 100644 --- a/NearDrop/it.lproj/Localizable.strings +++ b/NearDrop/it.lproj/Localizable.strings @@ -43,7 +43,16 @@ "RecommendedApps" = "App consigliate"; "GetSupport" = "Supporto"; "PrivacyPolicy" = "Informativa sulla privacy"; -"UserManual" = "Manuale utente"; +"DeviceCurrentlySendingFiles" = "%1$@ ti sta inviando %2$@"; +"UserManual" = "Manuale utente e Impostazioni"; +"Settings" = "Impostazioni"; +"AutomaticallyAcceptFiles" = "Accetta automaticamente i file in arrivo"; +"AutomaticallyAcceptFilesFooter" = "L'abilitazione di questa impostazione è consigliata solo nella tua rete privata. Chiunque nella stessa rete può inviare file al tuo dispositivo senza il tuo consenso."; + +"SaveFilesTo" = "Salva i file ricevuti in:"; +"SaveFilesLocation" = "Posizione dei file ricevuti"; +"SaveFilesToButton" = "Seleziona…"; +"DownloadsFolder" = "Download"; "SendFiles" = "Invia file"; "ReceiveFiles" = "Ricevi file"; "DeviceNotShown" = "Dispositivo non mostrato?"; diff --git a/NearDrop/ja.lproj/Localizable.strings b/NearDrop/ja.lproj/Localizable.strings index 0629af2..73d9142 100644 --- a/NearDrop/ja.lproj/Localizable.strings +++ b/NearDrop/ja.lproj/Localizable.strings @@ -43,7 +43,16 @@ "RecommendedApps" = "おすすめアプリ"; "GetSupport" = "サポート"; "PrivacyPolicy" = "プライバシーポリシー"; -"UserManual" = "ユーザーマニュアル"; +"DeviceCurrentlySendingFiles" = "%1$@ が %2$@ を送信中"; +"UserManual" = "ユーザーマニュアルと設定"; +"Settings" = "設定"; +"AutomaticallyAcceptFiles" = "受信ファイルを自動的に受け入れる"; +"AutomaticallyAcceptFilesFooter" = "この設定はプライベートネットワークでのみ有効にすることを推奨します。同じネットワーク上の誰でも、あなたの許可なしにファイルを送信できる可能性があります。"; + +"SaveFilesTo" = "受信ファイルの保存先:"; +"SaveFilesLocation" = "受信ファイルの保存場所"; +"SaveFilesToButton" = "選択…"; +"DownloadsFolder" = "ダウンロード"; "SendFiles" = "ファイルを送信"; "ReceiveFiles" = "ファイルを受信"; "DeviceNotShown" = "デバイスが表示されない?"; diff --git a/NearDrop/ko.lproj/Localizable.strings b/NearDrop/ko.lproj/Localizable.strings index c2d968a..c3e3873 100644 --- a/NearDrop/ko.lproj/Localizable.strings +++ b/NearDrop/ko.lproj/Localizable.strings @@ -43,7 +43,16 @@ "RecommendedApps" = "추천 앱"; "GetSupport" = "지원"; "PrivacyPolicy" = "개인정보 처리방침"; -"UserManual" = "사용자 매뉴얼"; +"DeviceCurrentlySendingFiles" = "%1$@이(가) %2$@을(를) 보내는 중입니다"; +"UserManual" = "사용자 매뉴얼 및 설정"; +"Settings" = "설정"; +"AutomaticallyAcceptFiles" = "수신 파일 자동 수락"; +"AutomaticallyAcceptFilesFooter" = "이 설정은 개인 네트워크에서만 활성화하는 것이 좋습니다. 같은 네트워크에 있는 누구나 사용자의 동의 없이 파일을 보낼 수 있습니다."; + +"SaveFilesTo" = "수신 파일 저장 위치:"; +"SaveFilesLocation" = "수신 파일 위치"; +"SaveFilesToButton" = "선택…"; +"DownloadsFolder" = "다운로드"; "SendFiles" = "파일 보내기"; "ReceiveFiles" = "파일 받기"; "DeviceNotShown" = "장치가 표시되지 않음?"; diff --git a/NearDrop/nl.lproj/Localizable.strings b/NearDrop/nl.lproj/Localizable.strings index e65469e..6663f08 100644 --- a/NearDrop/nl.lproj/Localizable.strings +++ b/NearDrop/nl.lproj/Localizable.strings @@ -44,7 +44,16 @@ "GetSupport" = "Ondersteuning"; "PrivacyPolicy" = "Privacybeleid"; -"UserManual" = "Gebruikershandleiding"; +"DeviceCurrentlySendingFiles" = "%1$@ stuurt je %2$@"; +"UserManual" = "Gebruikershandleiding en Instellingen"; +"Settings" = "Instellingen"; +"AutomaticallyAcceptFiles" = "Inkomende bestanden automatisch accepteren"; +"AutomaticallyAcceptFilesFooter" = "Het inschakelen van deze instelling wordt alleen aanbevolen in uw privé-netwerk. Iedereen in hetzelfde netwerk kan bestanden naar uw apparaat sturen zonder uw toestemming."; + +"SaveFilesTo" = "Opslaan inkomende bestanden in:"; +"SaveFilesLocation" = "Locatie van inkomende bestanden"; +"SaveFilesToButton" = "Selecteren…"; +"DownloadsFolder" = "Downloads"; "SendFiles" = "Bestanden verzenden"; "ReceiveFiles" = "Bestanden ontvangen"; "DeviceNotShown" = "Apparaat niet weergegeven?"; diff --git a/NearDrop/pt-BR.lproj/Localizable.strings b/NearDrop/pt-BR.lproj/Localizable.strings index 1d3312b..9a473cc 100644 --- a/NearDrop/pt-BR.lproj/Localizable.strings +++ b/NearDrop/pt-BR.lproj/Localizable.strings @@ -43,7 +43,16 @@ "RecommendedApps" = "Apps recomendados"; "GetSupport" = "Suporte"; "PrivacyPolicy" = "Política de Privacidade"; -"UserManual" = "Manual do Usuário"; +"DeviceCurrentlySendingFiles" = "%1$@ está enviando %2$@ para você"; +"UserManual" = "Manual do Usuário e Configurações"; +"Settings" = "Configurações"; +"AutomaticallyAcceptFiles" = "Aceitar automaticamente arquivos recebidos"; +"AutomaticallyAcceptFilesFooter" = "Ativar esta configuração é recomendado apenas na sua rede privada. Qualquer pessoa na mesma rede pode enviar arquivos para o seu dispositivo sem o seu consentimento."; + +"SaveFilesTo" = "Salvar arquivos recebidos em:"; +"SaveFilesLocation" = "Localização dos arquivos recebidos"; +"SaveFilesToButton" = "Selecionar…"; +"DownloadsFolder" = "Downloads"; "SendFiles" = "Enviar arquivos"; "ReceiveFiles" = "Receber arquivos"; "DeviceNotShown" = "Dispositivo não mostrado?"; diff --git a/NearDrop/pt-PT.lproj/Localizable.strings b/NearDrop/pt-PT.lproj/Localizable.strings index 949b19a..e85b40c 100644 --- a/NearDrop/pt-PT.lproj/Localizable.strings +++ b/NearDrop/pt-PT.lproj/Localizable.strings @@ -43,7 +43,16 @@ "RecommendedApps" = "Aplicativos recomendados"; "GetSupport" = "Suporte"; "PrivacyPolicy" = "Política de privacidade"; -"UserManual" = "Manual do usuário"; +"DeviceCurrentlySendingFiles" = "%1$@ está a enviar-te %2$@"; +"UserManual" = "Manual do Utilizador e Definições"; +"Settings" = "Definições"; +"AutomaticallyAcceptFiles" = "Aceitar automaticamente ficheiros recebidos"; +"AutomaticallyAcceptFilesFooter" = "Ativar esta definição é recomendado apenas na sua rede privada. Qualquer pessoa na mesma rede pode enviar ficheiros para o seu dispositivo sem o seu consentimento."; + +"SaveFilesTo" = "Guardar ficheiros recebidos em:"; +"SaveFilesLocation" = "Localização dos ficheiros recebidos"; +"SaveFilesToButton" = "Selecionar…"; +"DownloadsFolder" = "Transferências"; "SendFiles" = "Enviar ficheiros"; "ReceiveFiles" = "Receber ficheiros"; "DeviceNotShown" = "Dispositivo não mostrado?"; diff --git a/NearDrop/ro.lproj/Localizable.strings b/NearDrop/ro.lproj/Localizable.strings index 070b964..266188d 100644 --- a/NearDrop/ro.lproj/Localizable.strings +++ b/NearDrop/ro.lproj/Localizable.strings @@ -43,7 +43,16 @@ "RecommendedApps" = "Aplicații recomandate"; "GetSupport" = "Suport"; "PrivacyPolicy" = "Politica de confidențialitate"; -"UserManual" = "Manualul utilizatorului"; +"DeviceCurrentlySendingFiles" = "%1$@ îți trimite %2$@"; +"UserManual" = "Manual utilizator & Setări"; +"Settings" = "Setări"; +"AutomaticallyAcceptFiles" = "Acceptă automat fișierele primite"; +"AutomaticallyAcceptFilesFooter" = "Activarea acestei opțiuni este recomandată doar în rețeaua privată. Oricine din aceeași rețea poate trimite fișiere către dispozitivul dvs. fără consimțământ."; + +"SaveFilesTo" = "Salvează fișierele primite în:"; +"SaveFilesLocation" = "Locația fișierelor primite"; +"SaveFilesToButton" = "Selectează…"; +"DownloadsFolder" = "Descărcări"; "SendFiles" = "Trimite fișiere"; "ReceiveFiles" = "Primește fișiere"; "DeviceNotShown" = "Dispozitivul nu este afișat?"; diff --git a/NearDrop/ru.lproj/Localizable.strings b/NearDrop/ru.lproj/Localizable.strings index ee9ddf5..b557ff5 100644 --- a/NearDrop/ru.lproj/Localizable.strings +++ b/NearDrop/ru.lproj/Localizable.strings @@ -45,7 +45,16 @@ "PrivacyPolicy" = "Политика конфиденциальности"; -"UserManual" = "Руководство пользователя"; +"DeviceCurrentlySendingFiles" = "%1$@ отправляет вам %2$@"; +"UserManual" = "Руководство пользователя и Настройки"; +"Settings" = "Настройки"; +"AutomaticallyAcceptFiles" = "Автоматически принимать входящие файлы"; +"AutomaticallyAcceptFilesFooter" = "Включение этой настройки рекомендуется только в вашей частной сети. Любой пользователь в той же сети может отправлять файлы на ваше устройство без вашего согласия."; + +"SaveFilesTo" = "Сохранять входящие файлы в:"; +"SaveFilesLocation" = "Местоположение входящих файлов"; +"SaveFilesToButton" = "Выбрать…"; +"DownloadsFolder" = "Загрузки"; "SendFiles" = "Отправить файлы"; "ReceiveFiles" = "Получить файлы"; "DeviceNotShown" = "Устройство не отображается?"; diff --git a/NearDrop/th.lproj/Localizable.strings b/NearDrop/th.lproj/Localizable.strings index f86aada..bc1daaf 100644 --- a/NearDrop/th.lproj/Localizable.strings +++ b/NearDrop/th.lproj/Localizable.strings @@ -43,7 +43,16 @@ "RecommendedApps" = "แอปแนะนำ"; "GetSupport" = "การสนับสนุน"; "PrivacyPolicy" = "นโยบายความเป็นส่วนตัว"; -"UserManual" = "คู่มือการใช้งาน"; +"DeviceCurrentlySendingFiles" = "%1$@ กำลังส่ง %2$@ ให้คุณ"; +"UserManual" = "คู่มือผู้ใช้และการตั้งค่า"; +"Settings" = "การตั้งค่า"; +"AutomaticallyAcceptFiles" = "ยอมรับไฟล์ขาเข้าโดยอัตโนมัติ"; +"AutomaticallyAcceptFilesFooter" = "แนะนำให้เปิดใช้งานการตั้งค่านี้เฉพาะในเครือข่ายส่วนตัวของคุณเท่านั้น ใครก็ตามที่อยู่ในเครือข่ายเดียวกันสามารถส่งไฟล์ไปยังอุปกรณ์ของคุณได้โดยไม่ต้องได้รับอนุญาต"; + +"SaveFilesTo" = "บันทึกไฟล์ขาเข้าไปที่:"; +"SaveFilesLocation" = "ตำแหน่งไฟล์ขาเข้า"; +"SaveFilesToButton" = "เลือก…"; +"DownloadsFolder" = "ดาวน์โหลด"; "SendFiles" = "ส่งไฟล์"; "ReceiveFiles" = "รับไฟล์"; "DeviceNotShown" = "อุปกรณ์ไม่แสดง?"; diff --git a/NearDrop/uk.lproj/Localizable.strings b/NearDrop/uk.lproj/Localizable.strings index 3edcc0d..8c10535 100644 --- a/NearDrop/uk.lproj/Localizable.strings +++ b/NearDrop/uk.lproj/Localizable.strings @@ -43,7 +43,16 @@ "RecommendedApps" = "Рекомендовані додатки"; "GetSupport" = "Підтримка"; "PrivacyPolicy" = "Політика конфіденційності"; -"UserManual" = "Посібник користувача"; +"DeviceCurrentlySendingFiles" = "%1$@ надсилає вам %2$@"; +"UserManual" = "Керівництво користувача та налаштування"; +"Settings" = "Налаштування"; +"AutomaticallyAcceptFiles" = "Автоматично приймати вхідні файли"; +"AutomaticallyAcceptFilesFooter" = "Увімкнення цього параметра рекомендується лише у вашій приватній мережі. Будь-хто в тій самій мережі може надсилати файли на ваш пристрій без вашої згоди."; + +"SaveFilesTo" = "Зберігати вхідні файли у:"; +"SaveFilesLocation" = "Розташування вхідних файлів"; +"SaveFilesToButton" = "Вибрати…"; +"DownloadsFolder" = "Завантаження"; "SendFiles" = "Надіслати файли"; "ReceiveFiles" = "Отримати файли"; "DeviceNotShown" = "Пристрій не відображається?"; diff --git a/NearDrop/zh-Hans.lproj/Localizable.strings b/NearDrop/zh-Hans.lproj/Localizable.strings index 205ca00..f326d4e 100644 --- a/NearDrop/zh-Hans.lproj/Localizable.strings +++ b/NearDrop/zh-Hans.lproj/Localizable.strings @@ -43,7 +43,16 @@ "RecommendedApps" = "推荐应用"; "GetSupport" = "支持"; "PrivacyPolicy" = "隐私政策"; -"UserManual" = "用户手册"; +"DeviceCurrentlySendingFiles" = "%1$@ 正在向你发送 %2$@"; +"UserManual" = "用户手册与设置"; +"Settings" = "设置"; +"AutomaticallyAcceptFiles" = "自动接受传入文件"; +"AutomaticallyAcceptFilesFooter" = "仅建议在您的私人网络中启用此设置。同一网络中的任何人都可以在未经您同意的情况下向您的设备发送文件。"; + +"SaveFilesTo" = "保存传入文件到:"; +"SaveFilesLocation" = "传入文件位置"; +"SaveFilesToButton" = "选择…"; +"DownloadsFolder" = "下载"; "SendFiles" = "发送文件"; "ReceiveFiles" = "接收文件"; "DeviceNotShown" = "设备未显示?"; diff --git a/NearbyShare/InboundNearbyConnection.swift b/NearbyShare/InboundNearbyConnection.swift index d7ddb2e..6aa29ef 100644 --- a/NearbyShare/InboundNearbyConnection.swift +++ b/NearbyShare/InboundNearbyConnection.swift @@ -22,6 +22,7 @@ class InboundNearbyConnection: NearbyConnection{ private var cipherCommitment:Data? private var textPayloadID:Int64=0 + private var securityScopeUrl: URL? enum State{ case initial, receivedConnectionRequest, sentUkeyServerInit, receivedUkeyClientFinish, sentConnectionResponse, sentPairedKeyResult, receivedPairedKeyResult, waitingForUserConsent, receivingFiles, disconnected @@ -32,6 +33,7 @@ class InboundNearbyConnection: NearbyConnection{ } override func handleConnectionClosure() { + super.handleConnectionClosure() currentState = .disconnected do{ @@ -41,6 +43,11 @@ class InboundNearbyConnection: NearbyConnection{ } DispatchQueue.main.async { self.delegate?.connectionWasTerminated(connection: self, error: self.lastError) + + if let url = self.securityScopeUrl { + log("Stopped accessing security scoped resource.") + url.stopAccessingSecurityScopedResource() + } } } @@ -280,7 +287,7 @@ class InboundNearbyConnection: NearbyConnection{ currentState = .receivedPairedKeyResult } - private func makeFileDestinationURL(_ initialDest:URL) -> URL{ + private func makeFileDestinationURL(_ initialDest:URL) -> URL { var dest=initialDest if FileManager.default.fileExists(atPath: dest.path){ var counter=1 @@ -302,14 +309,18 @@ class InboundNearbyConnection: NearbyConnection{ private func processIntroductionFrame(_ frame:Sharing_Nearby_Frame) throws{ guard frame.hasV1, frame.v1.hasIntroduction else { throw NearbyError.requiredFieldMissing("shareNearbyFrame.v1.introduction") } currentState = .waitingForUserConsent + if frame.v1.introduction.fileMetadata.count>0 && frame.v1.introduction.textMetadata.isEmpty{ - let downloadsDirectory=(try FileManager.default.url(for: .downloadsDirectory, in: .userDomainMask, appropriateFor: nil, create: true)).resolvingSymlinksInPath() - for file in frame.v1.introduction.fileMetadata{ - let dest=makeFileDestinationURL(downloadsDirectory.appendingPathComponent(file.name)) - let info=InternalFileInfo(meta: FileMetadata(name: file.name, size: file.size, mimeType: file.mimeType), + + let saveDirectory = getSaveDirectory() + + for file in frame.v1.introduction.fileMetadata { + + let dest = makeFileDestinationURL(saveDirectory.appendingPathComponent(file.name)) + let info = InternalFileInfo(meta: FileMetadata(name: file.name, size: file.size, mimeType: file.mimeType), payloadID: file.payloadID, destinationURL: dest) - transferredFiles[file.payloadID]=info + transferredFiles[file.payloadID] = info } let metadata=TransferMetadata(files: transferredFiles.map({$0.value.meta}), id: id, pinCode: pinCode) DispatchQueue.main.async { @@ -324,10 +335,12 @@ class InboundNearbyConnection: NearbyConnection{ self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self) } }else if case .text=meta.type{ - let downloadsDirectory=(try FileManager.default.url(for: .downloadsDirectory, in: .userDomainMask, appropriateFor: nil, create: true)).resolvingSymlinksInPath() + + let saveDirectory = getSaveDirectory() + let dateFormatter=DateFormatter() dateFormatter.dateFormat="yyyy-MM-dd HH.mm.ss" - let dest=makeFileDestinationURL(downloadsDirectory.appendingPathComponent("\(dateFormatter.string(from: Date())).txt")) + let dest=makeFileDestinationURL(saveDirectory.appendingPathComponent("\(dateFormatter.string(from: Date())).txt")) let info=InternalFileInfo(meta: FileMetadata(name: dest.lastPathComponent, size: meta.size, mimeType: "text/plain"), payloadID: meta.payloadID, destinationURL: dest) @@ -342,6 +355,32 @@ class InboundNearbyConnection: NearbyConnection{ rejectTransfer(with: .unsupportedAttachmentType) } } + + private func getSaveDirectory() -> URL { + if let bookmarkData = UserDefaults.standard.data(forKey: UserDefaultsKeys.saveFolderBookmark.rawValue) { + var isStale = false + do { + let url = try URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale) + if !isStale { + if url.startAccessingSecurityScopedResource() { + self.securityScopeUrl = url + return url + } + } else { + print("Bookmark is stale, using default downloads folder.") + } + + } catch { + print("Failed to resolve bookmark: \(error), using default downloads folder.") + } + } + + do { + return try FileManager.default.url(for: .downloadsDirectory, in: .userDomainMask, appropriateFor: nil, create: true).resolvingSymlinksInPath() + } catch { + fatalError("Failed to get downloads directory: \(error)") + } + } func submitUserConsent(accepted:Bool){ DispatchQueue.global(qos: .utility).async { diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index a92b4a1..55e4258 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -36,6 +36,8 @@ 69DCF49E2AB8C9A500CBE2CC /* libNearbyShare.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; EB3643602D73A2A2009CDFF8 /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB36435F2D73A29F009CDFF8 /* AudioManager.swift */; }; EB3643622D73A2CA009CDFF8 /* timeIsNow.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EB3643612D73A2CA009CDFF8 /* timeIsNow.mp3 */; }; + EB6F62D72D91F4AF00534EC2 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */; }; + EB6F62DE2D932B4800534EC2 /* UserDefaultsKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1422D6FAEAF00CB0CB5 /* UserDefaultsKeys.swift */; }; EB7CD1432D6FAEB400CB0CB5 /* UserDefaultsKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1422D6FAEAF00CB0CB5 /* UserDefaultsKeys.swift */; }; EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; }; EB7CD1462D6FB0E800CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; }; @@ -196,6 +198,7 @@ E9C8C6D42CFF66650053474D /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Localizable.strings; sourceTree = ""; }; EB36435F2D73A29F009CDFF8 /* AudioManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioManager.swift; sourceTree = ""; }; EB3643612D73A2CA009CDFF8 /* timeIsNow.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = timeIsNow.mp3; sourceTree = ""; }; + EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; EB7CD1422D6FAEAF00CB0CB5 /* UserDefaultsKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsKeys.swift; sourceTree = ""; }; EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; EB7F540F2D78E6DD00989564 /* Sidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sidebar.swift; sourceTree = ""; }; @@ -304,6 +307,7 @@ EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */, EB7F54112D78E95C00989564 /* TutorialView.swift */, EB7F54132D78E9E400989564 /* LicenseWindow.swift */, + EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */, EB36435F2D73A29F009CDFF8 /* AudioManager.swift */, EB96ED8E2D7F8103004B3FF1 /* Localized.swift */, EB7F540F2D78E6DD00989564 /* Sidebar.swift */, @@ -538,6 +542,7 @@ EB7CD1432D6FAEB400CB0CB5 /* UserDefaultsKeys.swift in Sources */, 69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */, EB7F54102D78E6DF00989564 /* Sidebar.swift in Sources */, + EB6F62D72D91F4AF00534EC2 /* SettingsView.swift in Sources */, EB7F54122D78E96000989564 /* TutorialView.swift in Sources */, EB96ED8F2D7F8105004B3FF1 /* Localized.swift in Sources */, EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */, @@ -550,6 +555,7 @@ files = ( 69DCF48D2AB70E8C00CBE2CC /* offline_wire_formats.pb.swift in Sources */, 69DCF48F2AB70E8C00CBE2CC /* securemessage.pb.swift in Sources */, + EB6F62DE2D932B4800534EC2 /* UserDefaultsKeys.swift in Sources */, 69DCF4912AB70E9700CBE2CC /* NearbyConnection.swift in Sources */, 69DCF4922AB70E9700CBE2CC /* NearbyConnectionManager.swift in Sources */, 69DCF48E2AB70E8C00CBE2CC /* securegcm.pb.swift in Sources */, diff --git a/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDrop.xcscheme b/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDrop.xcscheme index 3d12e57..0b60244 100644 --- a/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDrop.xcscheme +++ b/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDrop.xcscheme @@ -34,7 +34,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "en" + language = "de" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO"