Add option to choose save directory & automatically accept files

This commit is contained in:
Leon Böttger committed 2025-03-25 21:06:01 +01:00
1 parent 164d351c4d
commit ea324ea7d8
26 files changed
+436 -77

No files matched your search

+68 -21
View File
@@ -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 {
+10 -1
View File
@@ -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";
+1 -1
View File
@@ -6,7 +6,7 @@
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
+84
View File
@@ -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)
}
}
+38 -21
View File
@@ -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<Content: View>: 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)
}
+2
View File
@@ -12,4 +12,6 @@ enum UserDefaultsKeys: String, CaseIterable {
case appLaunchedBefore = "ShowedWelcomeScreen"
case plusVersion = "isPlusVersion"
case transmissionCount = "reviewRequestCountKey"
case automaticallyAcceptFiles = "automaticallyAcceptFiles"
case saveFolderBookmark = "saveFolderBookmark"
}
+19 -8
View File
@@ -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"
}
}
}
+10 -1
View File
@@ -43,7 +43,16 @@
"RecommendedApps" = "التطبيقات الموصى بها";
"GetSupport" = "الدعم";
"PrivacyPolicy" = "سياسة الخصوصية";
"UserManual" = "دليل المستخدم";
"DeviceCurrentlySendingFiles" = "%1$@ يرسل لك %2$@";
"UserManual" = "دليل المستخدم والإعدادات";
"Settings" = "الإعدادات";
"AutomaticallyAcceptFiles" = "قبول الملفات الواردة تلقائيًا";
"AutomaticallyAcceptFilesFooter" = "يُوصى بتمكين هذا الإعداد فقط في شبكتك الخاصة. يمكن لأي شخص على نفس الشبكة إرسال الملفات إلى جهازك دون موافقتك.";
"SaveFilesTo" = "حفظ الملفات الواردة في:";
"SaveFilesLocation" = "موقع الملفات الواردة";
"SaveFilesToButton" = "تحديد…";
"DownloadsFolder" = "التنزيلات";
"SendFiles" = "إرسال الملفات";
"ReceiveFiles" = "استقبال الملفات";
"DeviceNotShown" = "الجهاز غير ظاهر؟";
+10 -1
View File
@@ -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";
+10 -1
View File
@@ -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?";
+10 -1
View File
@@ -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é?";
+10 -1
View File
@@ -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ó?";
+10 -1
View File
@@ -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?";
+10 -1
View File
@@ -43,7 +43,16 @@
"RecommendedApps" = "おすすめアプリ";
"GetSupport" = "サポート";
"PrivacyPolicy" = "プライバシーポリシー";
"UserManual" = "ユーザーマニュアル";
"DeviceCurrentlySendingFiles" = "%1$@ が %2$@ を送信中";
"UserManual" = "ユーザーマニュアルと設定";
"Settings" = "設定";
"AutomaticallyAcceptFiles" = "受信ファイルを自動的に受け入れる";
"AutomaticallyAcceptFilesFooter" = "この設定はプライベートネットワークでのみ有効にすることを推奨します。同じネットワーク上の誰でも、あなたの許可なしにファイルを送信できる可能性があります。";
"SaveFilesTo" = "受信ファイルの保存先:";
"SaveFilesLocation" = "受信ファイルの保存場所";
"SaveFilesToButton" = "選択…";
"DownloadsFolder" = "ダウンロード";
"SendFiles" = "ファイルを送信";
"ReceiveFiles" = "ファイルを受信";
"DeviceNotShown" = "デバイスが表示されない?";
+10 -1
View File
@@ -43,7 +43,16 @@
"RecommendedApps" = "추천 앱";
"GetSupport" = "지원";
"PrivacyPolicy" = "개인정보 처리방침";
"UserManual" = "사용자 매뉴얼";
"DeviceCurrentlySendingFiles" = "%1$@이(가) %2$@을(를) 보내는 중입니다";
"UserManual" = "사용자 매뉴얼 및 설정";
"Settings" = "설정";
"AutomaticallyAcceptFiles" = "수신 파일 자동 수락";
"AutomaticallyAcceptFilesFooter" = "이 설정은 개인 네트워크에서만 활성화하는 것이 좋습니다. 같은 네트워크에 있는 누구나 사용자의 동의 없이 파일을 보낼 수 있습니다.";
"SaveFilesTo" = "수신 파일 저장 위치:";
"SaveFilesLocation" = "수신 파일 위치";
"SaveFilesToButton" = "선택…";
"DownloadsFolder" = "다운로드";
"SendFiles" = "파일 보내기";
"ReceiveFiles" = "파일 받기";
"DeviceNotShown" = "장치가 표시되지 않음?";
+10 -1
View File
@@ -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?";
+10 -1
View File
@@ -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?";
+10 -1
View File
@@ -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?";
+10 -1
View File
@@ -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?";
+10 -1
View File
@@ -45,7 +45,16 @@
"PrivacyPolicy" = "Политика конфиденциальности";
"UserManual" = "Руководство пользователя";
"DeviceCurrentlySendingFiles" = "%1$@ отправляет вам %2$@";
"UserManual" = "Руководство пользователя и Настройки";
"Settings" = "Настройки";
"AutomaticallyAcceptFiles" = "Автоматически принимать входящие файлы";
"AutomaticallyAcceptFilesFooter" = "Включение этой настройки рекомендуется только в вашей частной сети. Любой пользователь в той же сети может отправлять файлы на ваше устройство без вашего согласия.";
"SaveFilesTo" = "Сохранять входящие файлы в:";
"SaveFilesLocation" = "Местоположение входящих файлов";
"SaveFilesToButton" = "Выбрать…";
"DownloadsFolder" = "Загрузки";
"SendFiles" = "Отправить файлы";
"ReceiveFiles" = "Получить файлы";
"DeviceNotShown" = "Устройство не отображается?";
+10 -1
View File
@@ -43,7 +43,16 @@
"RecommendedApps" = "แอปแนะนำ";
"GetSupport" = "การสนับสนุน";
"PrivacyPolicy" = "นโยบายความเป็นส่วนตัว";
"UserManual" = "คู่มือการใช้งาน";
"DeviceCurrentlySendingFiles" = "%1$@ กำลังส่ง %2$@ ให้คุณ";
"UserManual" = "คู่มือผู้ใช้และการตั้งค่า";
"Settings" = "การตั้งค่า";
"AutomaticallyAcceptFiles" = "ยอมรับไฟล์ขาเข้าโดยอัตโนมัติ";
"AutomaticallyAcceptFilesFooter" = "แนะนำให้เปิดใช้งานการตั้งค่านี้เฉพาะในเครือข่ายส่วนตัวของคุณเท่านั้น ใครก็ตามที่อยู่ในเครือข่ายเดียวกันสามารถส่งไฟล์ไปยังอุปกรณ์ของคุณได้โดยไม่ต้องได้รับอนุญาต";
"SaveFilesTo" = "บันทึกไฟล์ขาเข้าไปที่:";
"SaveFilesLocation" = "ตำแหน่งไฟล์ขาเข้า";
"SaveFilesToButton" = "เลือก…";
"DownloadsFolder" = "ดาวน์โหลด";
"SendFiles" = "ส่งไฟล์";
"ReceiveFiles" = "รับไฟล์";
"DeviceNotShown" = "อุปกรณ์ไม่แสดง?";
+10 -1
View File
@@ -43,7 +43,16 @@
"RecommendedApps" = "Рекомендовані додатки";
"GetSupport" = "Підтримка";
"PrivacyPolicy" = "Політика конфіденційності";
"UserManual" = "Посібник користувача";
"DeviceCurrentlySendingFiles" = "%1$@ надсилає вам %2$@";
"UserManual" = "Керівництво користувача та налаштування";
"Settings" = "Налаштування";
"AutomaticallyAcceptFiles" = "Автоматично приймати вхідні файли";
"AutomaticallyAcceptFilesFooter" = "Увімкнення цього параметра рекомендується лише у вашій приватній мережі. Будь-хто в тій самій мережі може надсилати файли на ваш пристрій без вашої згоди.";
"SaveFilesTo" = "Зберігати вхідні файли у:";
"SaveFilesLocation" = "Розташування вхідних файлів";
"SaveFilesToButton" = "Вибрати…";
"DownloadsFolder" = "Завантаження";
"SendFiles" = "Надіслати файли";
"ReceiveFiles" = "Отримати файли";
"DeviceNotShown" = "Пристрій не відображається?";
+10 -1
View File
@@ -43,7 +43,16 @@
"RecommendedApps" = "推荐应用";
"GetSupport" = "支持";
"PrivacyPolicy" = "隐私政策";
"UserManual" = "用户手册";
"DeviceCurrentlySendingFiles" = "%1$@ 正在向你发送 %2$@";
"UserManual" = "用户手册与设置";
"Settings" = "设置";
"AutomaticallyAcceptFiles" = "自动接受传入文件";
"AutomaticallyAcceptFilesFooter" = "仅建议在您的私人网络中启用此设置。同一网络中的任何人都可以在未经您同意的情况下向您的设备发送文件。";
"SaveFilesTo" = "保存传入文件到:";
"SaveFilesLocation" = "传入文件位置";
"SaveFilesToButton" = "选择…";
"DownloadsFolder" = "下载";
"SendFiles" = "发送文件";
"ReceiveFiles" = "接收文件";
"DeviceNotShown" = "设备未显示?";
+47 -8
View File
@@ -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 {
+6
View File
@@ -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 = "<group>"; };
EB36435F2D73A29F009CDFF8 /* AudioManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioManager.swift; sourceTree = "<group>"; };
EB3643612D73A2CA009CDFF8 /* timeIsNow.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = timeIsNow.mp3; sourceTree = "<group>"; };
EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
EB7CD1422D6FAEAF00CB0CB5 /* UserDefaultsKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsKeys.swift; sourceTree = "<group>"; };
EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
EB7F540F2D78E6DD00989564 /* Sidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sidebar.swift; sourceTree = "<group>"; };
@@ -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 */,
@@ -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"