mirror of
https://github.com/leonboe1/QuickDrop.git
synced 2026-11-11 02:51:11 +00:00
Add notification sync support
This commit is contained in:
1 parent
a352e9ed1c
commit
48eb5be4a5
8 files changed
+1014
-9
No files matched your search
@@ -31,8 +31,10 @@ class InboundNearbyConnection: NearbyConnection {
|
||||
private var isAuthenticated = false
|
||||
private var peerCertificate: Sharing_Nearby_PublicCertificate? = nil
|
||||
var isPlainTextTransfer = false
|
||||
private var isMirroredNotificationTransfer = false
|
||||
|
||||
var wasUserRejected = false
|
||||
var shouldSuppressAppDelegates = false
|
||||
var delegate: InboundNearbyConnectionDelegate?
|
||||
|
||||
enum State {
|
||||
@@ -187,6 +189,20 @@ class InboundNearbyConnection: NearbyConnection {
|
||||
|
||||
override func processBytesPayload(payload: Data, id: Int64) throws -> Bool {
|
||||
if id == textPayloadID {
|
||||
#if os(macOS)
|
||||
if isMirroredNotificationTransfer {
|
||||
if let payloadString = String(data: payload, encoding: .utf8) {
|
||||
log("[InboundNearbyConnection \(self.id)] Received mirrored notification payload (\(payload.count) bytes)")
|
||||
MirroredNotificationPresenter.shared.present(rawPayload: payloadString, senderDeviceName: remoteDeviceInfo?.name)
|
||||
} else {
|
||||
log("[InboundNearbyConnection \(self.id)] Failed to decode mirrored notification payload as UTF-8")
|
||||
}
|
||||
|
||||
try sendDisconnectionAndDisconnect()
|
||||
return true
|
||||
}
|
||||
#endif
|
||||
|
||||
if let urlStr = String(data: payload, encoding: .utf8) {
|
||||
|
||||
if isPlainTextTransfer {
|
||||
@@ -507,14 +523,43 @@ class InboundNearbyConnection: NearbyConnection {
|
||||
}
|
||||
else if let textMetadata = frame.v1.introduction.textMetadata.first {
|
||||
let isURL = textMetadata.type == .url
|
||||
|
||||
let metadata = TransferMetadata(files: [], id: id, pinCode: pinCode, textDescription: textMetadata.textTitle, transferType: isURL ? .url : .text, allowsToBeAddedAsTrustedDevice: self.peerCertificate != nil)
|
||||
textPayloadID = textMetadata.payloadID
|
||||
|
||||
|
||||
#if os(macOS)
|
||||
if textMetadata.type == .text && textMetadata.textTitle == MirroredNotificationProtocol.introductionTitle {
|
||||
isMirroredNotificationTransfer = true
|
||||
shouldSuppressAppDelegates = true
|
||||
|
||||
if !isAuthenticated {
|
||||
guard let device = self.remoteDeviceInfo else {
|
||||
self.rejectTransfer(with: .reject)
|
||||
return
|
||||
}
|
||||
let metadata = TransferMetadata(
|
||||
files: [],
|
||||
id: id,
|
||||
pinCode: pinCode,
|
||||
transferType: .notificationSync,
|
||||
allowsToBeAddedAsTrustedDevice: false
|
||||
)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.obtainUserConsent(transfer: metadata, device: device, connection: self)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
submitUserConsent(accepted: true, trustDevice: false)
|
||||
return
|
||||
}
|
||||
#endif
|
||||
|
||||
let metadata = TransferMetadata(files: [], id: id, pinCode: pinCode, textDescription: textMetadata.textTitle, transferType: isURL ? .url : .text, allowsToBeAddedAsTrustedDevice: self.peerCertificate != nil)
|
||||
|
||||
if !isURL {
|
||||
isPlainTextTransfer = true
|
||||
}
|
||||
|
||||
|
||||
checkIfCanProceed(metadata: metadata)
|
||||
}
|
||||
else if let wifiMetadata = frame.v1.introduction.wifiCredentialsMetadata.first {
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
//
|
||||
// MirroredNotificationSupport.swift
|
||||
// QuickDrop
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
import UserNotifications
|
||||
import LUI
|
||||
|
||||
enum MirroredNotificationProtocol {
|
||||
static let introductionTitle = "__quickdrop_mirror_notification_v1__"
|
||||
}
|
||||
|
||||
final class MirroredNotificationPresenter {
|
||||
|
||||
static let shared = MirroredNotificationPresenter()
|
||||
|
||||
private let center = UNUserNotificationCenter.current()
|
||||
private let queue = DispatchQueue(label: "MirroredNotificationPresenter")
|
||||
private var pendingNotifications: [(MirroredNotificationEnvelope, String?)] = []
|
||||
private var authorizationRequestInFlight = false
|
||||
|
||||
private init() {}
|
||||
|
||||
func present(rawPayload: String, senderDeviceName: String?) {
|
||||
guard let envelope = decodeEnvelope(from: rawPayload) else {
|
||||
log("[MirroredNotificationPresenter] Failed to decode mirrored notification payload")
|
||||
return
|
||||
}
|
||||
|
||||
queue.async {
|
||||
self.postNotificationIfAuthorized(envelope: envelope, senderDeviceName: senderDeviceName)
|
||||
}
|
||||
}
|
||||
|
||||
private func decodeEnvelope(from rawPayload: String) -> MirroredNotificationEnvelope? {
|
||||
guard let data = rawPayload.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
do {
|
||||
return try JSONDecoder().decode(MirroredNotificationEnvelope.self, from: data)
|
||||
} catch {
|
||||
log("[MirroredNotificationPresenter] Failed to decode envelope JSON: \(error.localizedDescription)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func postNotificationIfAuthorized(envelope: MirroredNotificationEnvelope, senderDeviceName: String?) {
|
||||
center.getNotificationSettings { settings in
|
||||
self.queue.async {
|
||||
self.handleAuthorizationStatus(
|
||||
settings.authorizationStatus,
|
||||
envelope: envelope,
|
||||
senderDeviceName: senderDeviceName
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleAuthorizationStatus(
|
||||
_ status: UNAuthorizationStatus,
|
||||
envelope: MirroredNotificationEnvelope,
|
||||
senderDeviceName: String?
|
||||
) {
|
||||
switch status {
|
||||
case .authorized, .provisional, .ephemeral:
|
||||
post(envelope: envelope, senderDeviceName: senderDeviceName)
|
||||
case .notDetermined:
|
||||
pendingNotifications.append((envelope, senderDeviceName))
|
||||
requestAuthorizationIfNeeded()
|
||||
case .denied:
|
||||
log("[MirroredNotificationPresenter] Notifications are denied in system settings; cannot mirror.")
|
||||
@unknown default:
|
||||
log("[MirroredNotificationPresenter] Unknown notification authorization status; skipping mirrored notification.")
|
||||
}
|
||||
}
|
||||
|
||||
private func requestAuthorizationIfNeeded() {
|
||||
if authorizationRequestInFlight {
|
||||
return
|
||||
}
|
||||
|
||||
authorizationRequestInFlight = true
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
|
||||
self.queue.async {
|
||||
self.authorizationRequestInFlight = false
|
||||
|
||||
if let error = error {
|
||||
log("[MirroredNotificationPresenter] Authorization request failed: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
guard granted else {
|
||||
log("[MirroredNotificationPresenter] Authorization request not granted; dropping queued mirrored notifications.")
|
||||
self.pendingNotifications.removeAll()
|
||||
return
|
||||
}
|
||||
|
||||
let queued = self.pendingNotifications
|
||||
self.pendingNotifications.removeAll()
|
||||
for (queuedEnvelope, queuedSender) in queued {
|
||||
self.post(envelope: queuedEnvelope, senderDeviceName: queuedSender)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func post(envelope: MirroredNotificationEnvelope, senderDeviceName: String?) {
|
||||
let senderName = senderDeviceName?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let hasSenderName = senderName?.isEmpty == false
|
||||
let displaySender = hasSenderName ? senderName! : "NotificationSyncFallbackSenderAndroid".localized()
|
||||
let identifierSender = hasSenderName ? senderName! : "android"
|
||||
let cleanAppName = envelope.appName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let cleanTitle = envelope.title.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let cleanBody = envelope.body.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = cleanTitle.isEmpty ? (cleanAppName.isEmpty ? "NotificationSyncFallbackTitle".localized() : cleanAppName) : cleanTitle
|
||||
content.body = cleanBody
|
||||
content.subtitle = cleanAppName.isEmpty ? displaySender : "\(cleanAppName) · \(displaySender)"
|
||||
content.sound = .default
|
||||
content.threadIdentifier = "quickdrop-mirror-\(sanitizeIdentifierComponent(identifierSender))"
|
||||
|
||||
let identifier = makeIdentifier(sender: identifierSender, notificationKey: envelope.notificationKey)
|
||||
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: nil)
|
||||
|
||||
center.add(request) { error in
|
||||
if let error = error {
|
||||
log("[MirroredNotificationPresenter] Could not post notification: \(error.localizedDescription)")
|
||||
} else {
|
||||
log("[MirroredNotificationPresenter] Posted mirrored notification (\(identifier))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func makeIdentifier(sender: String, notificationKey: String) -> String {
|
||||
let raw = "\(sender)|\(notificationKey)"
|
||||
let digest = SHA256.hash(data: Data(raw.utf8))
|
||||
let prefix = digest.prefix(12).map { String(format: "%02x", $0) }.joined()
|
||||
return "quickdrop-mirror-\(prefix)"
|
||||
}
|
||||
|
||||
private func sanitizeIdentifierComponent(_ value: String) -> String {
|
||||
let allowed = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-_"))
|
||||
let scalarView = value.unicodeScalars.map { allowed.contains($0) ? Character($0) : "_" }
|
||||
let sanitized = String(scalarView)
|
||||
return sanitized.isEmpty ? "android" : sanitized
|
||||
}
|
||||
}
|
||||
|
||||
private struct MirroredNotificationEnvelope: Codable {
|
||||
let version: Int
|
||||
let notificationKey: String
|
||||
let packageName: String
|
||||
let appName: String
|
||||
let title: String
|
||||
let body: String
|
||||
let postedAtEpochMs: Int64
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case version
|
||||
case notificationKey
|
||||
case packageName
|
||||
case appName
|
||||
case title
|
||||
case body
|
||||
case postedAtEpochMs
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
version = try container.decodeIfPresent(Int.self, forKey: .version) ?? 1
|
||||
notificationKey = try container.decode(String.self, forKey: .notificationKey)
|
||||
packageName = try container.decode(String.self, forKey: .packageName)
|
||||
appName = try container.decode(String.self, forKey: .appName)
|
||||
title = try container.decode(String.self, forKey: .title)
|
||||
body = try container.decode(String.self, forKey: .body)
|
||||
postedAtEpochMs = try container.decode(Int64.self, forKey: .postedAtEpochMs)
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,10 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
private var securityScopeUrl: URL?
|
||||
private static let customDeviceNameKey = "com.leonboettger.quickdrop.deviceName"
|
||||
private var defaultPort: NWEndpoint.Port = 50362
|
||||
private let txtKeyEndpointInfo = "n"
|
||||
private let txtKeyNotificationSyncCapabilities = "qd_caps"
|
||||
private let txtKeyNotificationSyncKeyFingerprint = "qd_kid"
|
||||
private let notificationSyncCapabilityToken = "nsync1"
|
||||
|
||||
|
||||
// MARK: Shared Instance
|
||||
@@ -307,14 +311,29 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
mdnsServices = serviceTypes.map { serviceType in
|
||||
let service = NetService(domain: "", type: serviceType, name: name, port: port)
|
||||
service.delegate = self
|
||||
service.setTXTRecord(NetService.data(fromTXTRecord: [
|
||||
"n": deviceInfo.serialize().urlSafeBase64EncodedString().data(using: .utf8)!,
|
||||
]))
|
||||
var txtRecord: [String: Data] = [
|
||||
txtKeyEndpointInfo: deviceInfo.serialize().urlSafeBase64EncodedString().data(using: .utf8)!,
|
||||
]
|
||||
#if os(macOS)
|
||||
txtRecord[txtKeyNotificationSyncCapabilities] = Data(notificationSyncCapabilityToken.utf8)
|
||||
if let keyFingerprint = notificationSyncKeyFingerprintHex() {
|
||||
txtRecord[txtKeyNotificationSyncKeyFingerprint] = Data(keyFingerprint.utf8)
|
||||
}
|
||||
#endif
|
||||
service.setTXTRecord(NetService.data(fromTXTRecord: txtRecord))
|
||||
service.publish()
|
||||
return service
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func notificationSyncKeyFingerprintHex() -> String? {
|
||||
guard let keyID = IdentityManager.shared.getPublicKey()?.toGenericPublicKey().id() else {
|
||||
return nil
|
||||
}
|
||||
return keyID.hex.lowercased()
|
||||
}
|
||||
|
||||
|
||||
private func stopMDNS() {
|
||||
// Gracefully stop each published service
|
||||
@@ -341,7 +360,11 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
|
||||
func connectionWasTerminated(connection: InboundNearbyConnection, savedFiles: [URL], error: Error?) {
|
||||
incomingConnections.removeValue(forKey: connection.id)
|
||||
|
||||
|
||||
if connection.shouldSuppressAppDelegates {
|
||||
return
|
||||
}
|
||||
|
||||
if !connection.wasUserRejected {
|
||||
inboundAppDelegates.forEach { delegate in
|
||||
delegate.connectionWasTerminated(connectionID: connection.id, from: connection.remoteDeviceInfo, savedFiles: savedFiles, wasPlainTextTransfer: connection.isPlainTextTransfer, error: error)
|
||||
@@ -513,7 +536,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb
|
||||
var foundService = FoundServiceInfo(service: service)
|
||||
|
||||
guard case let NWBrowser.Result.Metadata.bonjour(txtRecord) = service.metadata else { return }
|
||||
guard let endpointInfoEncoded = txtRecord.dictionary["n"] else { return }
|
||||
guard let endpointInfoEncoded = txtRecord.dictionary[txtKeyEndpointInfo] else { return }
|
||||
guard let endpointInfoData = Data.dataFromUrlSafeBase64(endpointInfoEncoded) else { return }
|
||||
guard var endpointInfo = EndpointInfo(data: endpointInfoData) else { return }
|
||||
var deviceInfo: RemoteDeviceInfo?
|
||||
|
||||
@@ -269,6 +269,15 @@ class OutboundNearbyConnection: NearbyConnection {
|
||||
private func processConnectionResponse(frame: Location_Nearby_Connections_OfflineFrame) throws {
|
||||
|
||||
guard frame.version == .v1 else { throw NearbyError.protocolError("Unexpected offline frame version \(frame.version)") }
|
||||
guard frame.hasV1, frame.v1.hasType else { throw NearbyError.requiredFieldMissing("offlineFrame.v1.type") }
|
||||
|
||||
if frame.v1.type == .keepAlive {
|
||||
log("[OutboundNearbyConnection \(self.id)] Received keep-alive while waiting for connection response")
|
||||
if frame.v1.hasKeepAlive, !frame.v1.keepAlive.ack {
|
||||
sendKeepAlive(ack: true)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard frame.v1.type == .connectionResponse else { throw NearbyError.protocolError("Unexpected frame type \(frame.v1.type)") }
|
||||
|
||||
|
||||
@@ -139,6 +139,7 @@ public struct TransferMetadata {
|
||||
case text
|
||||
case url
|
||||
case wifiPassword
|
||||
case notificationSync
|
||||
}
|
||||
|
||||
private func getSummary() -> String {
|
||||
@@ -196,6 +197,12 @@ public struct TransferMetadata {
|
||||
|
||||
case .wifiPassword:
|
||||
return "DeviceSendingWifiPassword".localized(with: deviceName)
|
||||
|
||||
case .notificationSync:
|
||||
if let textDescription, !textDescription.isEmpty {
|
||||
return textDescription
|
||||
}
|
||||
return "NotificationSyncConsentPromptFromDevice".localized(with: deviceName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
EB3B9F152EF7150400D6704E /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; };
|
||||
EB3B9F162EF7150400D6704E /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; };
|
||||
EB3B9F172EF7150400D6704E /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
|
||||
A1B2C3D62F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3DA2F33AA0100ABC001 /* MirroredNotificationSupport.swift */; };
|
||||
EB3B9F182EF7150400D6704E /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
|
||||
EB3B9F192EF7150400D6704E /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F8D2E50CA23008B8C5C /* Configuration.swift */; };
|
||||
EB3B9F1A2EF7150400D6704E /* AttachmentDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B762E521712002D68FA /* AttachmentDetails.swift */; };
|
||||
@@ -136,6 +137,7 @@
|
||||
EB3B9F6B2EF7151900D6704E /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; };
|
||||
EB3B9F6C2EF7151900D6704E /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
|
||||
EB3B9F6D2EF7151900D6704E /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
|
||||
A1B2C3D72F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3DA2F33AA0100ABC001 /* MirroredNotificationSupport.swift */; };
|
||||
EB3B9F6E2EF7151900D6704E /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
|
||||
EB3B9F6F2EF7151900D6704E /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; };
|
||||
EB3B9F702EF7151900D6704E /* ApIsolationTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB1325E92E7025D900FD749C /* ApIsolationTester.swift */; };
|
||||
@@ -188,11 +190,13 @@
|
||||
EB5C37CC2E377A7F0052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; };
|
||||
EB5C37CD2E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; };
|
||||
EB5C37CE2E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
|
||||
A1B2C3D52F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3DA2F33AA0100ABC001 /* MirroredNotificationSupport.swift */; };
|
||||
EB5C37CF2E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
|
||||
EB5C37D02E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; };
|
||||
EB5C37D12E377A7F0052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; };
|
||||
EB5C37D22E377A7F0052EFD7 /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
|
||||
EB5C37D32E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
|
||||
A1B2C3D42F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3DA2F33AA0100ABC001 /* MirroredNotificationSupport.swift */; };
|
||||
EB5C37D42E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
|
||||
EB5C37D62E377ACF0052EFD7 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37D52E377ACF0052EFD7 /* SwiftProtobuf */; };
|
||||
EB5C37D82E377ACF0052EFD7 /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37D72E377ACF0052EFD7 /* SwiftECC */; };
|
||||
@@ -209,6 +213,7 @@
|
||||
EB5C37F92E377CE70052EFD7 /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; };
|
||||
EB5C37FA2E377CE70052EFD7 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; };
|
||||
EB5C37FB2E377CE70052EFD7 /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
|
||||
A1B2C3D82F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3DA2F33AA0100ABC001 /* MirroredNotificationSupport.swift */; };
|
||||
EB5C37FC2E377CE70052EFD7 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; };
|
||||
EB5C37FE2E377CE70052EFD7 /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; };
|
||||
EB5C37FF2E377CE70052EFD7 /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; };
|
||||
@@ -271,6 +276,7 @@
|
||||
EB950B4A2E520436002D68FA /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; };
|
||||
EB950B4B2E520436002D68FA /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; };
|
||||
EB950B4C2E520436002D68FA /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
|
||||
A1B2C3D92F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3DA2F33AA0100ABC001 /* MirroredNotificationSupport.swift */; };
|
||||
EB950B4D2E520436002D68FA /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
|
||||
EB950B502E520436002D68FA /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; };
|
||||
EB950B512E520436002D68FA /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; };
|
||||
@@ -486,6 +492,7 @@
|
||||
69DA9A1829E0BF5200A442DA /* QuickDrop.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = QuickDrop.entitlements; sourceTree = "<group>"; };
|
||||
69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NearbyConnectionManager.swift; sourceTree = "<group>"; };
|
||||
69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboundNearbyConnection.swift; sourceTree = "<group>"; };
|
||||
A1B2C3DA2F33AA0100ABC001 /* MirroredNotificationSupport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MirroredNotificationSupport.swift; sourceTree = "<group>"; };
|
||||
69DA9A2829E18CB500A442DA /* ukey.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ukey.pb.swift; sourceTree = "<group>"; };
|
||||
69DA9A2929E18CB500A442DA /* wire_format.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = wire_format.pb.swift; sourceTree = "<group>"; };
|
||||
69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = device_to_device_messages.pb.swift; sourceTree = "<group>"; };
|
||||
@@ -767,6 +774,7 @@
|
||||
children = (
|
||||
69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */,
|
||||
69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */,
|
||||
A1B2C3DA2F33AA0100ABC001 /* MirroredNotificationSupport.swift */,
|
||||
691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */,
|
||||
698DFAE529E2F91A0064F247 /* NearbyConnection.swift */,
|
||||
);
|
||||
@@ -1289,6 +1297,7 @@
|
||||
EB5C37D12E377A7F0052EFD7 /* DataModel.swift in Sources */,
|
||||
EB5C37D22E377A7F0052EFD7 /* NearbyConnection.swift in Sources */,
|
||||
EB5C37D32E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */,
|
||||
A1B2C3D42F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */,
|
||||
EB5C37D42E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */,
|
||||
EB5C37BD2E377A700052EFD7 /* Helpers.swift in Sources */,
|
||||
EB1325EB2E7025D900FD749C /* ApIsolationTester.swift in Sources */,
|
||||
@@ -1325,6 +1334,7 @@
|
||||
EB5C37CC2E377A7F0052EFD7 /* DataModel.swift in Sources */,
|
||||
EB5C37CD2E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */,
|
||||
EB5C37CE2E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */,
|
||||
A1B2C3D52F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */,
|
||||
EB5C37CF2E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */,
|
||||
EBE5FF422EDB6F6F0019B380 /* Configuration.swift in Sources */,
|
||||
EB7654A12E53252D006B4AA3 /* AttachmentDetails.swift in Sources */,
|
||||
@@ -1379,6 +1389,7 @@
|
||||
EB3B9F152EF7150400D6704E /* DataModel.swift in Sources */,
|
||||
EB3B9F162EF7150400D6704E /* OutboundNearbyConnection.swift in Sources */,
|
||||
EB3B9F172EF7150400D6704E /* InboundNearbyConnection.swift in Sources */,
|
||||
A1B2C3D62F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */,
|
||||
EB3B9F182EF7150400D6704E /* NearbyConnectionManager.swift in Sources */,
|
||||
EB3B9F192EF7150400D6704E /* Configuration.swift in Sources */,
|
||||
EB3B9F1A2EF7150400D6704E /* AttachmentDetails.swift in Sources */,
|
||||
@@ -1443,6 +1454,7 @@
|
||||
EB3B9F6B2EF7151900D6704E /* DataModel.swift in Sources */,
|
||||
EB3B9F6C2EF7151900D6704E /* NearbyConnection.swift in Sources */,
|
||||
EB3B9F6D2EF7151900D6704E /* InboundNearbyConnection.swift in Sources */,
|
||||
A1B2C3D72F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */,
|
||||
EB3B9F6E2EF7151900D6704E /* NearbyConnectionManager.swift in Sources */,
|
||||
EB3B9F6F2EF7151900D6704E /* Helpers.swift in Sources */,
|
||||
EB3B9F702EF7151900D6704E /* ApIsolationTester.swift in Sources */,
|
||||
@@ -1471,6 +1483,7 @@
|
||||
EB5C37F92E377CE70052EFD7 /* securegcm.pb.swift in Sources */,
|
||||
EB5C37FA2E377CE70052EFD7 /* Helpers.swift in Sources */,
|
||||
EB5C37FB2E377CE70052EFD7 /* InboundNearbyConnection.swift in Sources */,
|
||||
A1B2C3D82F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */,
|
||||
EB9925802E45D53E00357055 /* sharing_enums.pb.swift in Sources */,
|
||||
EB5C37FC2E377CE70052EFD7 /* OutboundNearbyConnection.swift in Sources */,
|
||||
EB1325D62E701D8E00FD749C /* Settings.swift in Sources */,
|
||||
@@ -1510,6 +1523,7 @@
|
||||
EB950B4A2E520436002D68FA /* securegcm.pb.swift in Sources */,
|
||||
EB950B4B2E520436002D68FA /* offline_wire_formats.pb.swift in Sources */,
|
||||
EB950B4C2E520436002D68FA /* InboundNearbyConnection.swift in Sources */,
|
||||
A1B2C3D92F33AA0100ABC001 /* MirroredNotificationSupport.swift in Sources */,
|
||||
EB16B6132E58B4B9007D9792 /* IdentityManager.swift in Sources */,
|
||||
EB950B4D2E520436002D68FA /* NearbyConnection.swift in Sources */,
|
||||
EB950B502E520436002D68FA /* wire_format.pb.swift in Sources */,
|
||||
|
||||
@@ -21890,6 +21890,705 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"NotificationSyncConsentPromptFromDevice" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "هل تريد تفعيل مزامنة الإشعارات من %1$@؟"
|
||||
}
|
||||
},
|
||||
"ca" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Voleu activar la sincronització de notificacions des de %1$@?"
|
||||
}
|
||||
},
|
||||
"cs" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Chcete povolit synchronizaci oznámení z %1$@?"
|
||||
}
|
||||
},
|
||||
"da" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Vil du aktivere synkronisering af notifikationer fra %1$@?"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Möchtest du die Benachrichtigungssynchronisierung von %1$@ aktivieren?"
|
||||
}
|
||||
},
|
||||
"el" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Θέλετε να ενεργοποιήσετε τον συγχρονισμό ειδοποιήσεων από το %1$@;"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Do you want to enable notification sync from %1$@?"
|
||||
}
|
||||
},
|
||||
"en-AU" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Do you want to enable notification sync from %1$@?"
|
||||
}
|
||||
},
|
||||
"en-CA" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Do you want to enable notification sync from %1$@?"
|
||||
}
|
||||
},
|
||||
"en-GB" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Do you want to enable notification sync from %1$@?"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "¿Quieres activar la sincronización de notificaciones desde %1$@?"
|
||||
}
|
||||
},
|
||||
"es-419" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "¿Quieres activar la sincronización de notificaciones desde %1$@?"
|
||||
}
|
||||
},
|
||||
"fi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Haluatko ottaa käyttöön ilmoitusten synkronoinnin laitteesta %1$@?"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Autoriser la synchronisation des notifications depuis %1$@ ?"
|
||||
}
|
||||
},
|
||||
"fr-CA" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Voulez-vous activer la synchronisation des notifications à partir de %1$@ ?"
|
||||
}
|
||||
},
|
||||
"he" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "האם להפעיל סנכרון התראות מ-%1$@?"
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "क्या आप %1$@ से नोटिफिकेशन सिंक चालू करना चाहते हैं?"
|
||||
}
|
||||
},
|
||||
"hr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Želite li omogućiti sinkronizaciju obavijesti s %1$@?"
|
||||
}
|
||||
},
|
||||
"hu" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Szeretnéd engedélyezni az értesítések szinkronizálását innen: %1$@?"
|
||||
}
|
||||
},
|
||||
"id" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Apakah Anda ingin mengaktifkan sinkronisasi notifikasi dari %1$@?"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Vuoi attivare la sincronizzazione delle notifiche da %1$@?"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ からの通知同期を有効にしますか?"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@에서 알림 동기화를 활성화할까요?"
|
||||
}
|
||||
},
|
||||
"ms" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Adakah anda ingin mengaktifkan penyegerakan notifikasi dari %1$@?"
|
||||
}
|
||||
},
|
||||
"nb" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Vil du aktivere varselsynkronisering fra %1$@?"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Wil je meldingssynchronisatie van %1$@ inschakelen?"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Chcesz włączyć synchronizację powiadomień z %1$@?"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Deseja ativar a sincronização de notificações de %1$@?"
|
||||
}
|
||||
},
|
||||
"pt-PT" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Pretende ativar a sincronização de notificações de %1$@?"
|
||||
}
|
||||
},
|
||||
"ro" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Dorești să activezi sincronizarea notificărilor de la %1$@?"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Включить синхронизацию уведомлений с устройства %1$@?"
|
||||
}
|
||||
},
|
||||
"sv" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Vill du aktivera synkronisering av aviseringar från %1$@?"
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "ต้องการเปิดใช้งานการซิงค์การแจ้งเตือนจาก %1$@ หรือไม่?"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "%1$@ cihazından bildirim senkronizasyonunu etkinleştirmek istiyor musunuz?"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Увімкнути синхронізацію сповіщень з %1$@?"
|
||||
}
|
||||
},
|
||||
"vi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Bạn có muốn bật đồng bộ thông báo từ %1$@ không?"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "要启用来自 %1$@ 的通知同步吗?"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "你想啟用從 %1$@ 同步通知嗎?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"NotificationSyncFallbackSenderAndroid" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"ca" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"cs" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"da" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"el" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"en-AU" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"en-CA" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"en-GB" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"es-419" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"fi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"fr-CA" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"he" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "אנדרואיד"
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "एंड्रॉइड"
|
||||
}
|
||||
},
|
||||
"hr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"hu" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"id" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"ms" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"nb" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"pt-PT" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"ro" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"sv" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"vi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Android"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"NotificationSyncFallbackTitle" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "إشعار"
|
||||
}
|
||||
},
|
||||
"ca" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notificació"
|
||||
}
|
||||
},
|
||||
"cs" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Oznámení"
|
||||
}
|
||||
},
|
||||
"da" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notifikation"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Benachrichtigung"
|
||||
}
|
||||
},
|
||||
"el" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ειδοποίηση"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notification"
|
||||
}
|
||||
},
|
||||
"en-AU" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notification"
|
||||
}
|
||||
},
|
||||
"en-CA" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notification"
|
||||
}
|
||||
},
|
||||
"en-GB" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notification"
|
||||
}
|
||||
},
|
||||
"es" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notificación"
|
||||
}
|
||||
},
|
||||
"es-419" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notificación"
|
||||
}
|
||||
},
|
||||
"fi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Ilmoitus"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notification"
|
||||
}
|
||||
},
|
||||
"fr-CA" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notification"
|
||||
}
|
||||
},
|
||||
"he" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "התראה"
|
||||
}
|
||||
},
|
||||
"hi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "सूचना"
|
||||
}
|
||||
},
|
||||
"hr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Obavijest"
|
||||
}
|
||||
},
|
||||
"hu" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Értesítés"
|
||||
}
|
||||
},
|
||||
"id" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notifikasi"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notifica"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "通知"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "알림"
|
||||
}
|
||||
},
|
||||
"ms" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notifikasi"
|
||||
}
|
||||
},
|
||||
"nb" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Varsel"
|
||||
}
|
||||
},
|
||||
"nl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Melding"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Powiadomienie"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notificação"
|
||||
}
|
||||
},
|
||||
"pt-PT" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notificação"
|
||||
}
|
||||
},
|
||||
"ro" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Notificare"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Уведомление"
|
||||
}
|
||||
},
|
||||
"sv" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Avisering"
|
||||
}
|
||||
},
|
||||
"th" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "การแจ้งเตือน"
|
||||
}
|
||||
},
|
||||
"tr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Bildirim"
|
||||
}
|
||||
},
|
||||
"uk" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Сповіщення"
|
||||
}
|
||||
},
|
||||
"vi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Thông báo"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "通知"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "通知"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"OpenFinderAfterReceiving" : {
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
|
||||
@@ -81,6 +81,31 @@ class ReceiveModel: ObservableObject, InboundAppDelegate {
|
||||
|
||||
#if os(macOS)
|
||||
DispatchQueue.main.async {
|
||||
if transfer.type == .notificationSync {
|
||||
self.hideQuickDropToast()
|
||||
self.toastActions = nil
|
||||
self.progress = nil
|
||||
self.consentState = nil
|
||||
let senderName = device.name?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let resolvedSenderName = (senderName?.isEmpty == false) ? senderName! : "UnknownDevice".localized()
|
||||
self.activeDeviceName = resolvedSenderName
|
||||
|
||||
let alert = NSAlert()
|
||||
alert.alertStyle = .informational
|
||||
alert.messageText = "NotificationSyncConsentPromptFromDevice".localized(with: resolvedSenderName)
|
||||
alert.informativeText = pinCodeMessage
|
||||
alert.addButton(withTitle: "Accept".localized())
|
||||
alert.addButton(withTitle: "Decline".localized())
|
||||
|
||||
let result = alert.runModal()
|
||||
if result == .alertFirstButtonReturn {
|
||||
primaryButtonAction(true)
|
||||
} else {
|
||||
secondaryButtonAction()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
self.toastActions = nil
|
||||
self.progress = nil
|
||||
self.activeDeviceName = device.name ?? "UnknownDevice".localized()
|
||||
|
||||
Reference in new issue
Block a user