Add StoreKit 2

This commit is contained in:
Leon Böttger committed 2025-12-02 19:34:38 +01:00
1 parent 488f898d7b
commit f5e3c88daf
43 files changed
+2389 -5303

No files matched your search

+94
View File
@@ -0,0 +1,94 @@
{
"appPolicies" : {
"eula" : "",
"policies" : [
{
"locale" : "en_US",
"policyText" : "",
"policyURL" : ""
}
]
},
"identifier" : "E6E18FE5",
"nonRenewingSubscriptions" : [
],
"products" : [
{
"displayPrice" : "4.99",
"familyShareable" : true,
"internalID" : "E7870357",
"localizations" : [
{
"description" : "Unlock all settings!",
"displayName" : "QuickDrop+ Lifetime",
"locale" : "en_US"
}
],
"productID" : "plusversion",
"referenceName" : "QuickDrop+",
"type" : "NonConsumable"
}
],
"settings" : {
"_askToBuyEnabled" : false,
"_billingGracePeriodEnabled" : false,
"_billingIssuesEnabled" : false,
"_disableDialogs" : false,
"_failTransactionsEnabled" : false,
"_locale" : "en_US",
"_renewalBillingIssuesEnabled" : false,
"_storefront" : "USA",
"_storeKitErrors" : [
],
"_timeRate" : 0
},
"subscriptionGroups" : [
{
"id" : "DBA408BD",
"localizations" : [
],
"name" : "QuickDrop+",
"subscriptions" : [
{
"adHocOffers" : [
],
"codeOffers" : [
],
"displayPrice" : "4.99",
"familyShareable" : true,
"groupNumber" : 1,
"internalID" : "514B2EA4",
"introductoryOffer" : {
"internalID" : "D44066CC",
"paymentMode" : "free",
"subscriptionPeriod" : "P3D"
},
"localizations" : [
{
"description" : "Unlock all settings!",
"displayName" : "QuickDrop+ (1 Year)",
"locale" : "en_US"
}
],
"productID" : "quickdrop.yearly",
"recurringSubscriptionPeriod" : "P1Y",
"referenceName" : "QuickDrop+",
"subscriptionGroupID" : "DBA408BD",
"type" : "RecurringSubscription",
"winbackOffers" : [
]
}
]
}
],
"version" : {
"major" : 4,
"minor" : 0
}
}
@@ -12,6 +12,7 @@ import Network
import System
import BigInt
import SwiftECC
import LUI
#if os(macOS)
import AppKit
@@ -12,6 +12,7 @@ import Network
import System
import BigInt
import SwiftECC
import LUI
class NearbyConnection {
@@ -17,6 +17,7 @@ import SwiftECC
import CryptoKit
import SwiftUI
import QRCode
import LUI
public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate, ObservableObject {
@@ -14,7 +14,8 @@ import UniformTypeIdentifiers
import BigInt
import SwiftECC
import ASN1
import BigInt // required dependency of ASN1
import BigInt
import LUI
class OutboundNearbyConnection: NearbyConnection {
+1
View File
@@ -7,6 +7,7 @@
import Foundation
import ImageIO
import LUI
struct EXIFUtils {
+1 -2
View File
@@ -111,8 +111,7 @@ extension String {
public func fullVersion() -> Bool {
// Enable full functionality if app distributed directly
return Settings.shared.gotPlus || DistributionDetector.isDirectDistributionEnabled
return Settings.shared.gotPlus
}
+1
View File
@@ -10,6 +10,7 @@ import Foundation
import SwiftECC
import Security
import CryptoKit
import LUI
class IdentityManager {
-126
View File
@@ -1,126 +0,0 @@
//
// Logger.swift
// QuickDrop
//
// Created by Leon Böttger on 26.02.25.
//
import Foundation
import os
fileprivate let loggerQueue = DispatchQueue(label: "loggerQueue", qos: .utility)
/// Manages logging functionality
final class LogManager {
/// The URL of the log file
let logFileURL: URL?
/// Shared instance
public static let sharedInstance = LogManager()
@Published var loggingEnabled = true
/// Private initializer
private init() {
// Use the App Group container
let directory = AppGroup.appGroupDirectory
// Create file name from date
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
let dateString = formatter.string(from: Date())
let fileName = "\(dateString).log"
// Full path in app group container
self.logFileURL = directory?.appendingPathComponent(fileName)
}
/// Writes a specified string to the log file.
func writeToFile(string: String) {
guard let logFileURL = logFileURL else { return }
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"
let timestamp = formatter.string(from: Date())
guard let data = (timestamp + " - " + string + "\n").data(using: .utf8) else { return }
loggerQueue.async {
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: logFileURL.path) {
// Clean up older logs
self.deleteLogs()
// Create file atomically
try? data.write(to: logFileURL, options: .atomicWrite)
return
}
// Atomic append with file locking using FileHandle
do {
let fileHandle = try FileHandle(forWritingTo: logFileURL)
defer { try? fileHandle.close() }
// Locking is handled by the serial dispatch queue
try fileHandle.seekToEnd()
try fileHandle.write(contentsOf: data)
} catch {
print("[Logger] Failed to write to log file: \(error.localizedDescription)")
}
}
}
/// Gets the contents of the log file.
func getLogString() -> String {
guard let logFileURL = logFileURL else { return "" }
do {
return try String(contentsOf: logFileURL, encoding: .utf8)
} catch {
log("[LUI] Error reading file: \(error.localizedDescription)")
return ""
}
}
/// Deletes all log files in the shared container.
func deleteLogs() {
log("[LUI] Deleting all logs...")
guard let directory = AppGroup.appGroupDirectory else { return }
let fileManager = FileManager.default
guard let allFiles = try? fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) else {
return
}
let logFiles = allFiles.filter { $0.pathExtension == "log" }
for file in logFiles {
do {
log("[LUI] Removing \(file.lastPathComponent)")
try fileManager.removeItem(at: file)
} catch {
print("[Logger] Failed to delete file: \(error.localizedDescription)")
}
}
}
}
/// Logs a specified string
public func log(_ text: String) {
let logger = LogManager.sharedInstance
if logger.loggingEnabled {
loggerQueue.async {
print(text)
#if !os(tvOS)
let isAppExtension = Bundle.main.bundlePath.hasSuffix(".appex")
let prefix = isAppExtension ? "[EXT]" : "[APP]"
logger.writeToFile(string: "\(prefix) \(text)")
#endif
}
}
}
+1
View File
@@ -13,6 +13,7 @@ import IOKit.pwr_mgt
#elseif os(iOS)
import UIKit
#endif
import LUI
class SleepManager {
public static let shared = SleepManager()
+39 -48
View File
@@ -45,7 +45,6 @@
EB16B77A2E59BAA1007D9792 /* AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B7762E59BA9F007D9792 /* AppGroup.swift */; };
EB2F9BFE2DA05F48004010A2 /* BezelNotification in Frameworks */ = {isa = PBXBuildFile; productRef = EB2F9BFD2DA05F48004010A2 /* BezelNotification */; };
EB3643602D73A2A2009CDFF8 /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB36435F2D73A29F009CDFF8 /* AudioManager.swift */; };
EB4F9D1D2EC113EA00A74A0F /* DistributionDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB704A812E4A7BAE008E608F /* DistributionDetector.swift */; };
EB4F9D1F2EC26C8100A74A0F /* EXIFUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB4F9D1E2EC26C7C00A74A0F /* EXIFUtils.swift */; };
EB4F9D202EC26C8100A74A0F /* EXIFUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB4F9D1E2EC26C7C00A74A0F /* EXIFUtils.swift */; };
EB4F9D212EC26C8100A74A0F /* EXIFUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB4F9D1E2EC26C7C00A74A0F /* EXIFUtils.swift */; };
@@ -98,7 +97,6 @@
EB5C37FA2E377CE70052EFD7 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; };
EB5C37FB2E377CE70052EFD7 /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; };
EB5C37FC2E377CE70052EFD7 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; };
EB5C37FD2E377CE70052EFD7 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.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 */; };
EB5C38002E377CE70052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; };
@@ -115,17 +113,12 @@
EB6E6B212ECBA95000E71D57 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB6E6B202ECBA95000E71D57 /* InfoPlist.xcstrings */; };
EB6E6B222ECBA95000E71D57 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB6E6B202ECBA95000E71D57 /* InfoPlist.xcstrings */; };
EB6F62D72D91F4AF00534EC2 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */; };
EB704A822E4A7BB3008E608F /* DistributionDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB704A812E4A7BAE008E608F /* DistributionDetector.swift */; };
EB704A832E4B10D4008E608F /* DistributionDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB704A812E4A7BAE008E608F /* DistributionDetector.swift */; };
EB704A842E4B10D4008E608F /* DistributionDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB704A812E4A7BAE008E608F /* DistributionDetector.swift */; };
EB721F242E75AFA900F2099F /* QuickDropIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = EB721F232E75AFA900F2099F /* QuickDropIcon.icon */; };
EB721F252E75AFA900F2099F /* QuickDropIcon.icon in Resources */ = {isa = PBXBuildFile; fileRef = EB721F232E75AFA900F2099F /* QuickDropIcon.icon */; };
EB7654782E525ACC006B4AA3 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = EB7654772E525ACC006B4AA3 /* MainMenu.xib */; };
EB7654A12E53252D006B4AA3 /* AttachmentDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB950B762E521712002D68FA /* AttachmentDetails.swift */; };
EB7654C02E534A8E006B4AA3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; };
EB7654C12E534A8E006B4AA3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; };
EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; };
EB7CD1462D6FB0E800CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; };
EB7F54122D78E96000989564 /* TutorialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7F54112D78E95C00989564 /* TutorialView.swift */; };
EB7F54142D78E9E700989564 /* LicenseWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7F54132D78E9E400989564 /* LicenseWindow.swift */; };
EB821C992E76FE5300036963 /* SmallSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8A2D7F7CF4004B3FF1 /* SmallSheetView.swift */; };
@@ -157,7 +150,6 @@
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 */; };
EB950B4D2E520436002D68FA /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; };
EB950B4F2E520436002D68FA /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.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 */; };
EB950B522E520436002D68FA /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; };
@@ -211,9 +203,6 @@
EBBEE1462EC9D93400AA7831 /* PhotoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBBEE1452EC9D92F00AA7831 /* PhotoManager.swift */; };
EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */; };
EBC05C5C2E3394EF00EECC18 /* ErrorAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */; };
EBC05C712E33B57D00EECC18 /* Converter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C6D2E33B57D00EECC18 /* Converter.swift */; };
EBC05C722E33B57D00EECC18 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C6E2E33B57D00EECC18 /* Helper.swift */; };
EBC05C732E33B57D00EECC18 /* PlusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C6F2E33B57D00EECC18 /* PlusView.swift */; };
EBC05C822E33B58300EECC18 /* ApIsolationIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C7D2E33B58300EECC18 /* ApIsolationIssueView.swift */; };
EBC05C832E33B58300EECC18 /* FirewallIssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C7E2E33B58300EECC18 /* FirewallIssueView.swift */; };
EBC05C842E33B58300EECC18 /* IssueView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C7F2E33B58300EECC18 /* IssueView.swift */; };
@@ -225,6 +214,15 @@
EBC05C8C2E33B59E00EECC18 /* timeIsNow.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EBC05C8A2E33B59E00EECC18 /* timeIsNow.mp3 */; };
EBCC27F92D80C9F90079C478 /* Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBCC27F82D80C9F70079C478 /* Zip.swift */; };
EBD2A2172D2860C100BCB8E2 /* WelcomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */; };
EBE5FF3F2EDB6F550019B380 /* LUI in Frameworks */ = {isa = PBXBuildFile; productRef = EBE5FF3E2EDB6F550019B380 /* LUI */; };
EBE5FF412EDB6F5D0019B380 /* LUI in Frameworks */ = {isa = PBXBuildFile; productRef = EBE5FF402EDB6F5D0019B380 /* LUI */; };
EBE5FF422EDB6F6F0019B380 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F8D2E50CA23008B8C5C /* Configuration.swift */; };
EBE5FF432EDB71810019B380 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB979F8D2E50CA23008B8C5C /* Configuration.swift */; };
EBE5FF442EDB877C0019B380 /* License.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB886E662E4F2F7C0052B7D9 /* License.swift */; };
EBE5FF4B2EDB8D4B0019B380 /* Config.storekit in Resources */ = {isa = PBXBuildFile; fileRef = EBE5FF4A2EDB8D4B0019B380 /* Config.storekit */; };
EBE5FF4C2EDB8D4B0019B380 /* Config.storekit in Resources */ = {isa = PBXBuildFile; fileRef = EBE5FF4A2EDB8D4B0019B380 /* Config.storekit */; };
EBE5FF4D2EDB8D4B0019B380 /* Config.storekit in Resources */ = {isa = PBXBuildFile; fileRef = EBE5FF4A2EDB8D4B0019B380 /* Config.storekit */; };
EBE5FF4E2EDB8D4B0019B380 /* Config.storekit in Resources */ = {isa = PBXBuildFile; fileRef = EBE5FF4A2EDB8D4B0019B380 /* Config.storekit */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -347,10 +345,8 @@
EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLogin.swift; sourceTree = "<group>"; };
EB6E6B202ECBA95000E71D57 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = "<group>"; };
EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
EB704A812E4A7BAE008E608F /* DistributionDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistributionDetector.swift; sourceTree = "<group>"; };
EB721F232E75AFA900F2099F /* QuickDropIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = QuickDropIcon.icon; sourceTree = "<group>"; };
EB7654772E525ACC006B4AA3 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = "<group>"; };
EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = "<group>"; };
EB7F54112D78E95C00989564 /* TutorialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialView.swift; sourceTree = "<group>"; };
EB7F54132D78E9E400989564 /* LicenseWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseWindow.swift; sourceTree = "<group>"; };
EB869DB42E92AA33009AAAD4 /* QuickDropToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickDropToastView.swift; sourceTree = "<group>"; };
@@ -378,9 +374,6 @@
EBBC93B12EC8B75F00F541A1 /* LocalNetworkPermissionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNetworkPermissionView.swift; sourceTree = "<group>"; };
EBBEE1452EC9D92F00AA7831 /* PhotoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoManager.swift; sourceTree = "<group>"; };
EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlertHandler.swift; sourceTree = "<group>"; };
EBC05C6D2E33B57D00EECC18 /* Converter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Converter.swift; sourceTree = "<group>"; };
EBC05C6E2E33B57D00EECC18 /* Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = "<group>"; };
EBC05C6F2E33B57D00EECC18 /* PlusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlusView.swift; sourceTree = "<group>"; };
EBC05C7D2E33B58300EECC18 /* ApIsolationIssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApIsolationIssueView.swift; sourceTree = "<group>"; };
EBC05C7E2E33B58300EECC18 /* FirewallIssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirewallIssueView.swift; sourceTree = "<group>"; };
EBC05C7F2E33B58300EECC18 /* IssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueView.swift; sourceTree = "<group>"; };
@@ -388,6 +381,7 @@
EBC05C8A2E33B59E00EECC18 /* timeIsNow.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = timeIsNow.mp3; sourceTree = "<group>"; };
EBCC27F82D80C9F70079C478 /* Zip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Zip.swift; sourceTree = "<group>"; };
EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = "<group>"; };
EBE5FF4A2EDB8D4B0019B380 /* Config.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Config.storekit; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -396,6 +390,7 @@
buildActionMask = 2147483647;
files = (
EB5C37D82E377ACF0052EFD7 /* SwiftECC in Frameworks */,
EBE5FF412EDB6F5D0019B380 /* LUI in Frameworks */,
EB5C37D62E377ACF0052EFD7 /* SwiftProtobuf in Frameworks */,
EB9925852E45EF0900357055 /* QRCode in Frameworks */,
);
@@ -409,6 +404,7 @@
EB2F9BFE2DA05F48004010A2 /* BezelNotification in Frameworks */,
EB5C37DC2E377AD60052EFD7 /* SwiftECC in Frameworks */,
EB5C37DA2E377AD60052EFD7 /* SwiftProtobuf in Frameworks */,
EBE5FF3F2EDB6F550019B380 /* LUI in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -465,6 +461,7 @@
isa = PBXGroup;
children = (
EB721F232E75AFA900F2099F /* QuickDropIcon.icon */,
EBE5FF4A2EDB8D4B0019B380 /* Config.storekit */,
69DA9A1029E0BF5100A442DA /* QuickDrop */,
699DEBA22AB0573200115D22 /* ShareExtension */,
69DCF4822AB70D0600CBE2CC /* NearbyShare */,
@@ -495,7 +492,6 @@
EB644B852EA507CF006C2E6C /* ViewModel */,
EBC05C6A2E33B55100EECC18 /* View */,
EBC05C6B2E33B57100EECC18 /* Util */,
EBC05C702E33B57D00EECC18 /* Plus */,
69DA9A1329E0BF5200A442DA /* Assets.xcassets */,
EB6E6B202ECBA95000E71D57 /* InfoPlist.xcstrings */,
EB5C36922E34EBBD0052EFD7 /* Localizable.xcstrings */,
@@ -550,7 +546,6 @@
EB886E512E4E19390052B7D9 /* HKDF.swift */,
EB4F9D232EC27CF000A74A0F /* FileManager+DocumentsOpener.swift */,
EB4F9D1E2EC26C7C00A74A0F /* EXIFUtils.swift */,
EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */,
EB2F9BFA2DA04B67004010A2 /* Helpers.swift */,
);
path = Util;
@@ -668,17 +663,6 @@
path = Util;
sourceTree = "<group>";
};
EBC05C702E33B57D00EECC18 /* Plus */ = {
isa = PBXGroup;
children = (
EBC05C6D2E33B57D00EECC18 /* Converter.swift */,
EB704A812E4A7BAE008E608F /* DistributionDetector.swift */,
EBC05C6E2E33B57D00EECC18 /* Helper.swift */,
EBC05C6F2E33B57D00EECC18 /* PlusView.swift */,
);
path = Plus;
sourceTree = "<group>";
};
EBC05C812E33B58300EECC18 /* IssueViews */ = {
isa = PBXGroup;
children = (
@@ -743,6 +727,7 @@
EB5C37D92E377AD60052EFD7 /* SwiftProtobuf */,
EB5C37DB2E377AD60052EFD7 /* SwiftECC */,
EB9925862E45F12F00357055 /* QRCode */,
EBE5FF3E2EDB6F550019B380 /* LUI */,
);
productName = NearDrop;
productReference = 69DA9A0E29E0BF5100A442DA /* QuickDrop.app */;
@@ -873,6 +858,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EBE5FF4D2EDB8D4B0019B380 /* Config.storekit in Resources */,
EB5C36942E34EBBD0052EFD7 /* Localizable.xcstrings in Resources */,
EB5C36E52E34EC0E0052EFD7 /* License.txt in Resources */,
691F53BC2ABB70840089FD92 /* DeviceListCell.xib in Resources */,
@@ -888,6 +874,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EBE5FF4C2EDB8D4B0019B380 /* Config.storekit in Resources */,
69DA9A1429E0BF5200A442DA /* Assets.xcassets in Resources */,
EB5C36E42E34EC0E0052EFD7 /* License.txt in Resources */,
EB5C36932E34EBBD0052EFD7 /* Localizable.xcstrings in Resources */,
@@ -903,6 +890,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EBE5FF4B2EDB8D4B0019B380 /* Config.storekit in Resources */,
EB5C383B2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */,
EB1325FD2E7034B000FD749C /* InfoPlist.xcstrings in Resources */,
EB97A0142E51DD00008B8C5C /* License.txt in Resources */,
@@ -919,6 +907,7 @@
files = (
EB950B5B2E52065A002D68FA /* timeIsNow.mp3 in Resources */,
EB1325FE2E7034B000FD749C /* InfoPlist.xcstrings in Resources */,
EBE5FF4E2EDB8D4B0019B380 /* Config.storekit in Resources */,
EB950B702E520734002D68FA /* Localizable.xcstrings in Resources */,
EB950B5C2E52065A002D68FA /* justMaybe.mp3 in Resources */,
EB950B5D2E52065A002D68FA /* License.txt in Resources */,
@@ -933,19 +922,19 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EB704A832E4B10D4008E608F /* DistributionDetector.swift in Sources */,
EBE5FF442EDB877C0019B380 /* License.swift in Sources */,
EB96ED902D7F8114004B3FF1 /* Localized.swift in Sources */,
EB1325F82E70288300FD749C /* SendToExtension.swift in Sources */,
EB16B6172E58B4C8007D9792 /* TrustStore.swift in Sources */,
EB886E542E4E19390052B7D9 /* HKDF.swift in Sources */,
EBC05C862E33B58300EECC18 /* NetworkFilterIssueView.swift in Sources */,
EBE5FF432EDB71810019B380 /* Configuration.swift in Sources */,
EBC05C872E33B58300EECC18 /* IssueView.swift in Sources */,
EBC05C882E33B58300EECC18 /* FirewallIssueView.swift in Sources */,
EBC05C892E33B58300EECC18 /* ApIsolationIssueView.swift in Sources */,
699DEBA62AB0573200115D22 /* ShareViewController.swift in Sources */,
EB96ED8B2D7F7CF7004B3FF1 /* SmallSheetView.swift in Sources */,
EBCC27F92D80C9F90079C478 /* Zip.swift in Sources */,
EB7CD1462D6FB0E800CB0CB5 /* Logger.swift in Sources */,
EB5C37C52E377A780052EFD7 /* securemessage.pb.swift in Sources */,
EB5C37C62E377A780052EFD7 /* securegcm.pb.swift in Sources */,
EB5C37C72E377A780052EFD7 /* wire_format.pb.swift in Sources */,
@@ -990,10 +979,7 @@
EB16B6182E58B4C8007D9792 /* TrustStore.swift in Sources */,
EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */,
EB1325D42E701D8E00FD749C /* Settings.swift in Sources */,
EBC05C712E33B57D00EECC18 /* Converter.swift in Sources */,
EB886E5D2E4F13EB0052B7D9 /* Zip.swift in Sources */,
EBC05C722E33B57D00EECC18 /* Helper.swift in Sources */,
EBC05C732E33B57D00EECC18 /* PlusView.swift in Sources */,
EB5C37BF2E377A780052EFD7 /* ukey.pb.swift in Sources */,
EB5C37C02E377A780052EFD7 /* securemessage.pb.swift in Sources */,
EB5C37C12E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */,
@@ -1007,6 +993,7 @@
EB5C37CD2E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */,
EB5C37CE2E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */,
EB5C37CF2E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */,
EBE5FF422EDB6F6F0019B380 /* Configuration.swift in Sources */,
EB7654A12E53252D006B4AA3 /* AttachmentDetails.swift in Sources */,
EB886E522E4E19390052B7D9 /* HKDF.swift in Sources */,
EB96ED8C2D7F7E7B004B3FF1 /* SmallSheetView.swift in Sources */,
@@ -1016,11 +1003,9 @@
69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */,
EB6F62D72D91F4AF00534EC2 /* SettingsView.swift in Sources */,
EB7F54122D78E96000989564 /* TutorialView.swift in Sources */,
EB704A822E4A7BB3008E608F /* DistributionDetector.swift in Sources */,
EB9925812E45D53E00357055 /* sharing_enums.pb.swift in Sources */,
EB6E6AB42ECBA46D00E71D57 /* PhotoManager.swift in Sources */,
EB96ED8F2D7F8105004B3FF1 /* Localized.swift in Sources */,
EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */,
EB16B77A2E59BAA1007D9792 /* AppGroup.swift in Sources */,
EBC05C822E33B58300EECC18 /* ApIsolationIssueView.swift in Sources */,
EBC05C832E33B58300EECC18 /* FirewallIssueView.swift in Sources */,
@@ -1047,7 +1032,6 @@
EB5C37F42E377CE70052EFD7 /* NearbyConnectionManager.swift in Sources */,
EB5C37F52E377CE70052EFD7 /* securemessage.pb.swift in Sources */,
EB5C37F62E377CE70052EFD7 /* device_to_device_messages.pb.swift in Sources */,
EB704A842E4B10D4008E608F /* DistributionDetector.swift in Sources */,
EB886E532E4E19390052B7D9 /* HKDF.swift in Sources */,
EB5C37F82E377CE70052EFD7 /* SleepManager.swift in Sources */,
EB5C38012E377DE60052EFD7 /* Localized.swift in Sources */,
@@ -1057,7 +1041,6 @@
EB9925802E45D53E00357055 /* sharing_enums.pb.swift in Sources */,
EB5C37FC2E377CE70052EFD7 /* OutboundNearbyConnection.swift in Sources */,
EB1325D62E701D8E00FD749C /* Settings.swift in Sources */,
EB5C37FD2E377CE70052EFD7 /* Logger.swift in Sources */,
EBBC93B32EC8B76600F541A1 /* LocalNetworkPermissionView.swift in Sources */,
EB97A00F2E511124008B8C5C /* ReceiveModel.swift in Sources */,
EB5C37FE2E377CE70052EFD7 /* offline_wire_formats.pb.swift in Sources */,
@@ -1098,7 +1081,6 @@
EB16B6132E58B4B9007D9792 /* IdentityManager.swift in Sources */,
EB4F9D212EC26C8100A74A0F /* EXIFUtils.swift in Sources */,
EB950B4D2E520436002D68FA /* NearbyConnection.swift in Sources */,
EB950B4F2E520436002D68FA /* Logger.swift in Sources */,
EB950B502E520436002D68FA /* wire_format.pb.swift in Sources */,
EB950B582E5205B4002D68FA /* Zip.swift in Sources */,
EB950B512E520436002D68FA /* securemessage.pb.swift in Sources */,
@@ -1123,7 +1105,6 @@
EB950B6F2E5206E2002D68FA /* ShareViewController.swift in Sources */,
EB950B432E52038A002D68FA /* ContentView.swift in Sources */,
EB950B442E520414002D68FA /* License.swift in Sources */,
EB4F9D1D2EC113EA00A74A0F /* DistributionDetector.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1187,7 +1168,7 @@
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = "${SHARED_VERSION}";
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.leonboettger.neardrop.ShareExtension;
@@ -1239,7 +1220,7 @@
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = "${SHARED_VERSION}";
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.leonboettger.neardrop.ShareExtension;
@@ -1321,8 +1302,8 @@
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SHARED_BUILD = 92;
SHARED_VERSION = 2.7.2;
SHARED_BUILD = 94;
SHARED_VERSION = 2.8;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -1384,8 +1365,8 @@
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
SHARED_BUILD = 92;
SHARED_VERSION = 2.7.2;
SHARED_BUILD = 94;
SHARED_VERSION = 2.8;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -1437,7 +1418,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = "${SHARED_VERSION}";
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.leonboettger.neardrop;
@@ -1501,7 +1482,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MACOSX_DEPLOYMENT_TARGET = 12.0;
MARKETING_VERSION = "${SHARED_VERSION}";
OTHER_SWIFT_FLAGS = "";
PRODUCT_BUNDLE_IDENTIFIER = com.leonboettger.neardrop;
@@ -1860,6 +1841,16 @@
package = EBADEB8B2E8029420021B5E4 /* XCRemoteSwiftPackageReference "DeviceKit" */;
productName = DeviceKit;
};
EBE5FF3E2EDB6F550019B380 /* LUI */ = {
isa = XCSwiftPackageProductDependency;
package = EB886E562E4E66420052B7D9 /* XCRemoteSwiftPackageReference "LUI" */;
productName = LUI;
};
EBE5FF402EDB6F5D0019B380 /* LUI */ = {
isa = XCSwiftPackageProductDependency;
package = EB886E562E4E66420052B7D9 /* XCRemoteSwiftPackageReference "LUI" */;
productName = LUI;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 69DA9A0629E0BF5100A442DA /* Project object */;
@@ -52,7 +52,7 @@
"location" : "https://github.com/leonboe1/LUI.git",
"state" : {
"branch" : "main",
"revision" : "1490442fc11be90a3418355b81db783abeb9a6c2"
"revision" : "1c507e431e4b73cee127165a1fbd4a930abecd7d"
}
},
{
@@ -50,6 +50,9 @@
ReferencedContainer = "container:QuickDrop.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<StoreKitConfigurationFileReference
identifier = "../../Config.storekit">
</StoreKitConfigurationFileReference>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
+17 -14
View File
@@ -12,6 +12,7 @@ import Network
import StoreKit
import SwiftUI
import UserNotifications
import LUI
@main
class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDelegate, NSWindowDelegate {
@@ -34,7 +35,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
// MARK: NSApplicationDelegate functions
func applicationWillFinishLaunching(_: Notification) {
LUIInit(configuration: configuration)
}
func applicationDidFinishLaunching(_: Notification) {
let menu = NSMenu()
let visibleItem = NSMenuItem(
@@ -67,14 +74,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
statusItem?.behavior = .removalAllowed
iapManager = IAPManager.sharedInstance
iapManager?.startObserving()
if DistributionDetector.isDirectDistributionEnabled {
log("[AppDelegate] Downloaded from GitHub.")
}
else {
log("[AppDelegate] Downloaded from App Store.")
}
// app did not lauch before
if !Settings.shared.appLaunchedBefore {
@@ -92,6 +91,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
if !Settings.shared.isEligibleForIap {
log("[AppDelegate] Granting QuickDrop+ for old user")
Settings.shared.gotPlus = true
UserDefaults.standard.set(true, forKey: Settings.UserDefaultsKeys.plusVersionLegacy.rawValue)
} else {
if !fullVersion() {
log("[AppDelegate] New user - QuickDrop+ not available")
@@ -160,7 +160,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
func applicationWillTerminate(_: Notification) {
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
iapManager?.stopObserving()
}
@@ -258,11 +257,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
log("Opening Plus screen")
// Create the welcome screen SwiftUI view
let plusView = PlusView(closeView: {
log("Closing plus screen")
self.plusWindow?.close()
self.plusWindow = nil
})
let plusView = GetPlusViewV4(showSheet: Binding(get: { self.plusWindow != nil }, set: { newValue in
if !newValue {
self.plusWindow?.close()
self.plusWindow = nil
}
}), hasSheet: false)
let plusViewWidth: CGFloat = 550
let plusViewHeight: CGFloat = 650
// Create an NSWindow to host the SwiftUI view
let window = NSWindow(
Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 KiB

@@ -12,15 +12,6 @@
{
"idiom" : "universal",
"scale" : "3x"
},
{
"filename" : "1024-1024.png",
"idiom" : "mac",
"scale" : "1x"
},
{
"idiom" : "mac",
"scale" : "2x"
}
],
"info" : {
+1 -1
View File
@@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "FileTransfer Kopie-512.png",
"filename" : "iDroidIcon-iOS-Default-1024x1024@1x.png",
"idiom" : "universal",
"scale" : "1x"
},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

File diff suppressed because it is too large. Load diff
-66
View File
@@ -1,66 +0,0 @@
//
// StoreChecker.swift
// LUI
//
// Created by Leon Böttger on 19.08.24.
//
import StoreKit
class IAPConverter: NSObject, SKRequestDelegate {
let switchToIAPAfterAppVersion = "1.2"
static let shared = IAPConverter()
private override init() {
super.init()
}
func restoreIAPfromPreviousPurchase(success: ((Bool) -> ())? = nil) {
Task {
do {
// Get the appTransaction.
if #available(macOS 13.0, *) {
let shared = try await AppTransaction.shared
if case .verified(let appTransaction) = shared {
let originalVersion = appTransaction.originalAppVersion
let newModelVersion = switchToIAPAfterAppVersion
log("[LUI] Original Purchased Version: \(originalVersion) | New Model Version: \(newModelVersion)")
if originalVersion.compare(newModelVersion, options: .numeric) == .orderedAscending {
grant(success: success)
return
}
}
} else {
// Fallback on earlier versions
// Grant IAP, as we cannot check the original purchase date.
DispatchQueue.main.async {
log("[LUI] Cannot check version - Plus Version Granted!")
success?(true)
}
}
}
catch {
// Handle errors.
log("[LUI] Error: \(error.localizedDescription)")
}
DispatchQueue.main.async {
log("[LUI] Plus Version Denied!")
success?(false)
}
}
}
private func grant(success: ((Bool) -> ())? = nil) {
log("[LUI] Existing User - Plus Version Granted!")
DispatchQueue.main.async {
success?(true)
}
}
}
-76
View File
@@ -1,76 +0,0 @@
//
// DistributionDetector.swift
// QuickDrop
//
// Created by Leon Böttger on 11.08.25.
//
import Security
import Foundation
class DistributionDetector {
static var isDirectDistributionEnabled: Bool {
get {
#if os(macOS)
if let cached = _directDistributionCached {
return cached
}
let result = isDirectDistribution()
self._directDistributionCached = result
return result
#else
return false
#endif
}
}
static var _directDistributionCached: Bool? = nil
#if os(macOS)
private static func isDirectDistribution() -> Bool {
// 1. Get dynamic code object for our process
var code: SecCode?
var status = SecCodeCopySelf([], &code)
guard status == errSecSuccess, let codeRef = code else {
log("[DistributionDetector] Could not get code object)")
return false
}
// 2. Convert to static code object
var staticCode: SecStaticCode?
status = SecCodeCopyStaticCode(codeRef, [], &staticCode)
guard status == errSecSuccess, let staticCodeRef = staticCode else {
log("[DistributionDetector] Could not get static code object)")
return false
}
// 3. Get signing info
var infoCF: CFDictionary?
status = SecCodeCopySigningInformation(staticCodeRef,
SecCSFlags(rawValue: kSecCSSigningInformation),
&infoCF)
guard status == errSecSuccess, let info = infoCF as? [String: Any] else {
log("[DistributionDetector] Could not get signing info")
return false
}
// 4. Look at the first certificate's common name
if let certificates = info[kSecCodeInfoCertificates as String] as? [SecCertificate],
let firstCert = certificates.first {
var commonName: CFString?
status = SecCertificateCopyCommonName(firstCert, &commonName)
if status == errSecSuccess, let commonNameStr = commonName as String? {
if commonNameStr.contains("Developer ID Application") {
return true
}
}
}
return false
}
#endif
}
-191
View File
@@ -1,191 +0,0 @@
//
// PurchaseHelper.swift
// Loop
//
// Created by Leon Böttger on 26.10.20.
// Copyright © 2020 Leon Böttger. All rights reserved.
//
import StoreKit
let iapName = "plusversion"
class IAPManager: NSObject, ObservableObject {
enum IAPManagerError: Error {
case noProductIDsFound
case noProductsFound
case paymentWasCancelled
case productRequestFailed
}
static let plusVersionID = iapName
static let sharedInstance = IAPManager()
var onReceiveProductsHandler: ((Result<[SKProduct], IAPManagerError>) -> Void)?
var onBuyProductHandler: ((Result<Bool, Error>) -> Void)?
var totalRestoredPurchases = 0
var isActive = false
private override init() {
super.init()
getProducts { _ in }
}
fileprivate func getProductIDs() -> [String]? {
return [IAPManager.plusVersionID]
}
func startObserving() {
SKPaymentQueue.default().add(self)
}
func stopObserving() {
SKPaymentQueue.default().remove(self)
}
func canMakePayments() -> Bool {
return SKPaymentQueue.canMakePayments()
}
func getProducts(withHandler productsReceiveHandler: @escaping (_ result: Result<[SKProduct], IAPManagerError>) -> Void) {
// Keep the handler (closure) that will be called when requesting for
// products on the App Store is finished.
onReceiveProductsHandler = productsReceiveHandler
// Get the product identifiers.
guard let productIDs = getProductIDs() else {
productsReceiveHandler(.failure(.noProductIDsFound))
return
}
// Initialize a product request.
let request = SKProductsRequest(productIdentifiers: Set(productIDs))
// Set self as the its delegate.
request.delegate = self
// Make the request.
request.start()
}
func buy(product: SKProduct, withHandler handler: @escaping ((_ result: Result<Bool, Error>) -> Void)) {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
// Keep the completion handler.
onBuyProductHandler = handler
}
func restorePurchases(withHandler handler: @escaping ((_ result: Result<Bool, Error>) -> Void)) {
onBuyProductHandler = handler
totalRestoredPurchases = 0
SKPaymentQueue.default().restoreCompletedTransactions()
}
}
extension IAPManager: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
log("IAP updatedTransactions")
transactions.forEach { (transaction) in
switch transaction.transactionState {
case .purchased:
log("IAP purchased")
onBuyProductHandler?(.success(true))
SKPaymentQueue.default().finishTransaction(transaction)
case .restored:
log("IAP restored")
totalRestoredPurchases += 1
onBuyProductHandler?(.success(true))
SKPaymentQueue.default().finishTransaction(transaction)
case .failed:
log("[LUI] IAP failed")
if let error = transaction.error as? SKError {
if error.code != .paymentCancelled {
onBuyProductHandler?(.failure(error))
} else {
onBuyProductHandler?(.failure(IAPManagerError.paymentWasCancelled))
}
log("[LUI] IAP Error:" + error.localizedDescription)
}
onBuyProductHandler?(.failure(transaction.error ?? IAPManagerError.productRequestFailed))
SKPaymentQueue.default().finishTransaction(transaction)
case .deferred, .purchasing: log("[LUI] other cases")
@unknown default: log("[LUI] other default cases")
}
}
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
if self.totalRestoredPurchases == 0 {
log("[LUI] IAP: No purchases to restore!")
self.onBuyProductHandler?(.success(false))
}
}
}
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
if let error = error as? SKError {
if error.code != .paymentCancelled {
log("[LUI] IAP Restore Error:" + error.localizedDescription)
onBuyProductHandler?(.failure(error))
} else {
onBuyProductHandler?(.failure(IAPManagerError.paymentWasCancelled))
}
}
}
}
extension IAPManager: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
// Get the available products contained in the response.
let products = response.products
// Check if there are any products available.
if products.count > 0 {
// Call the following handler passing the received products.
onReceiveProductsHandler?(.success(products))
} else {
// No products were found.
onReceiveProductsHandler?(.failure(.noProductsFound))
}
}
func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
return true
}
func request(_ request: SKRequest, didFailWithError error: Error) {
onReceiveProductsHandler?(.failure(.productRequestFailed))
}
func requestDidFinish(_ request: SKRequest) {
// Implement this method OPTIONALLY and add any custom logic
// you want to apply when a product request is finished.
}
}
extension IAPManager.IAPManagerError: LocalizedError {
var errorDescription: String? {
switch self {
case .noProductIDsFound: return "No In-App Purchase product identifiers were found."
case .noProductsFound: return "No In-App Purchases were found."
case .productRequestFailed: return "plusview_productRequestFailed".localized()
case .paymentWasCancelled: return "In-App Purchase process was cancelled."
}
}
}
-248
View File
@@ -1,248 +0,0 @@
//
// PlusView.swift
// QuickDrop
//
// Created by Leon Böttger on 23.02.25.
//
import SwiftUI
import StoreKit
let plusViewWidth: CGFloat = 440
let plusViewHeight: CGFloat = 200
struct PlusView: View {
@ObservedObject private var settings = Settings.shared
@ObservedObject private var iaphelper = IAPManager.sharedInstance
let closeView: () -> Void
@State private var restoring = false
@State private var showRestoreError = false
@State private var restoreError = ""
@Environment(\.colorScheme) var colorScheme
@State var products = [SKProduct]()
@State var price: String = ""
@State var warning = false
@State var buying = false
@State var errorText = ""
@State var boughtSuccessAlert = false
var body: some View {
ZStack {
Color.defaultBackground
.edgesIgnoringSafeArea(.all)
let validPrice = price != ""
VStack(spacing: 12) {
Text("plusview_title")
.font(.title)
.bold()
Text("plusview_description")
.multilineTextAlignment(.center)
.padding()
.padding(.top, -10)
.alert(isPresented: $boughtSuccessAlert, content: {
Alert(title: Text("plusview_success_title"), message: Text("plusview_success_description"), dismissButton: .default(Text("plusview_success_button"), action: {
closeView()
}))
})
HStack(spacing: 10) {
Button("plusview_restorepurchase") {
restorePurchases()
}
.alert(isPresented: $showRestoreError, content: {
Alert(title: Text("plusview_restorefailed"), message: Text(restoreError), dismissButton: .default(Text("plusview_restorefailed_proceed")))
})
.opacity(restoring ? 0 : 1)
.overlay(
ProgressView()
.scaleEffect(0.5)
.opacity(restoring ? 1 : 0)
)
let priceString = validPrice ? " (\(price))" : ""
Button("plusview_unlock_label".localized() + priceString) {
buttonAction()
}
.keyboardShortcut(.defaultAction)
.alert(isPresented: $warning, content: {
Alert(title: Text("plusview_purchasefailed"), message: Text("plusview_purchasefaileddescription".localized() + (errorText != "" ? " Error: " : "") + errorText), dismissButton: .default(Text("plusview_restorefailed_proceed")))
})
}
.opacity(buying || restoring ? 0 : 1)
.overlay(
ProgressView()
.scaleEffect(0.5)
.opacity(buying || restoring ? 1 : 0)
)
}
.onAppear {
loadProducts(shouldBuy: false)
}
.animation(.default, value: validPrice)
.padding()
}
.frame(width: plusViewWidth, height: plusViewHeight)
}
func restorePurchases() {
withAnimation {
restoring = true
}
iaphelper.restorePurchases { result in
DispatchQueue.main.async {
switch result {
case .success(let success):
if success {
log("[LUI] Restored purchases successfully")
withAnimation {
settings.gotPlus = true
}
boughtSuccessAlert = true
} else {
log("[LUI] Nothing to restore")
//nothing to restore
restoreFailed(message: "plusview_nothingtorestore".localized())
}
case .failure(let error):
log("[LUI] Error restoring purchases: \(error.localizedDescription)")
log(error.localizedDescription)
restoreFailed(message: error.localizedDescription)
}
}
}
func restoreFailed(message: String) {
#if targetEnvironment(simulator)
isPlusVersion = true
dismissView()
#else
// now, try to convert from old version
IAPConverter.shared.restoreIAPfromPreviousPurchase { success in
DispatchQueue.main.async {
if success {
withAnimation {
settings.gotPlus = true
}
boughtSuccessAlert = true
}
else {
restoreError = message
showRestoreError = true
withAnimation {
restoring = false
}
}
}
}
#endif
}
}
func buttonAction() {
if let product = products.first {
purchase(product: product)
}
else {
loadProducts(shouldBuy: true)
}
}
func purchase(product: SKProduct) {
if !iaphelper.canMakePayments() {
warning = true
} else {
withAnimation{
buying = true
}
iaphelper.buy(product: product) { (result) in
DispatchQueue.main.async {
withAnimation{
buying = false
}
switch result {
case .success(_):
withAnimation {
settings.gotPlus = true
boughtSuccessAlert = true
warning = false
}
case .failure(let error):
warning = true
errorText = error.localizedDescription
}
}
}
}
}
func loadProducts(shouldBuy: Bool) {
iaphelper.getProducts { (result) in
DispatchQueue.main.async {
switch result {
case .success(let products):
self.products = products
price = products.first?.localizedPrice ?? ""
if let product = products.first, shouldBuy {
purchase(product: product)
}
case .failure(let error):
errorText = error.localizedDescription
if shouldBuy {
warning = true
}
withAnimation {
buying = false
}
}
}
}
}
}
extension SKProduct {
var localizedPrice: String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = priceLocale
if let price = formatter.string(from: price) {
return price
}
return ""
}
}
#Preview {
PlusView(closeView: {})
}
+8 -3
View File
@@ -55,9 +55,11 @@ class Settings: ObservableObject {
// MARK: - Debug
func deleteAllUserDefaults() {
for key in UserDefaultsKeys.allCases {
UserDefaults.standard.removeObject(forKey: key.rawValue)
let defaults = UserDefaults.standard
if let bundleID = Bundle.main.bundleIdentifier {
defaults.removePersistentDomain(forName: bundleID)
}
defaults.synchronize()
Settings.shared = Settings()
}
@@ -69,11 +71,14 @@ class Settings: ObservableObject {
public enum UserDefaultsKeys: String, CaseIterable {
case isEligibleForIap = "isEligibleForIap"
case appLaunchedBefore = "ShowedWelcomeScreen"
case plusVersion = "isPlusVersion"
case plusVersion = "plusVersion"
case transmissionCount = "reviewRequestCountKey"
case automaticallyAcceptFiles = "automaticallyAcceptFiles"
case saveFolderBookmark = "saveFolderBookmark"
case openFinderAfterReceiving = "openFinderAfterReceiving"
case endpointID = "endpointID"
// Plus version used before StoreKit 2 update. New key is fetched from App Store directly.
case plusVersionLegacy = "isPlusVersion"
}
}
+1
View File
@@ -6,6 +6,7 @@
//
import AVFoundation
import LUI
class AudioManager {
static private var incomingFileSoundPlayer: AVAudioPlayer?
+1 -2
View File
@@ -10,12 +10,11 @@ import Network
import StoreKit
import SwiftUI
import UserNotifications
import LUI
#if os(macOS)
import BezelNotification
import Cocoa
#else
import LUI
#endif
class ErrorAlertHandler {
+1
View File
@@ -19,6 +19,7 @@
import SwiftUI
import ServiceManagement
import os.log
import LUI
@available(macOS 13.0, *)
public enum LaunchAtLogin {
@@ -6,6 +6,7 @@
//
import Foundation
import LUI
/// Returns the name of the currently active network interface (e.g. "en0", "en1")
func getActiveNetworkInterface() -> String? {
@@ -7,6 +7,7 @@
import Foundation
import Network
import LUI
class IPv6DeviceScanner {
/// Scans for IPv6 devices using `ping6 ff02::1` multicast on the given interface.
@@ -7,6 +7,7 @@
import Foundation
import Network
import LUI
class DeviceToDeviceHeuristicScanner {
private let scanQueue = DispatchQueue(label: "DeviceToDeviceScanQueue")
@@ -9,6 +9,7 @@ import SwiftUI
import Foundation
import AppKit
import Network
import LUI
fileprivate let routerCheckQueue = DispatchQueue(label: "routerCheckQueue")
+9 -1
View File
@@ -90,7 +90,15 @@ struct QuickDropToastView: View {
}
HStack {
CapsuleProgress(value: receiveModel.progress ?? 1.0)
ZStack {
CapsuleProgress(value: 0.5)
.opacity(0)
if let progress = receiveModel.progress {
CapsuleProgress(value: progress)
}
}
.animation(.easeInOut, value: receiveModel.progress == nil)
Button(action: onCancel) {
Image(systemName: "xmark")
+1
View File
@@ -7,6 +7,7 @@
import SwiftUI
import AppKit
import LUI
struct SettingsView: View {
+3 -4
View File
@@ -6,6 +6,7 @@
//
import SwiftUI
import LUI
struct TutorialView: View {
@@ -81,10 +82,8 @@ struct LargeAppIconView<Content: View>: View {
ScrollView {
VStack {
Image("AppIconHighRes")
.resizable()
.frame(width: 150, height: 150)
.padding(.top, 40)
AppIconView(hasPlusIcon: false, size: 120)
.padding(.top, 35)
Text(title.localized())
.font(.largeTitle)
+2 -5
View File
@@ -8,11 +8,10 @@
import SwiftUI
import StoreKit
import UserNotifications
import LUI
#if os(macOS)
import BezelNotification
#else
import LUI
#endif
class ReceiveModel: ObservableObject, InboundAppDelegate {
@@ -290,9 +289,7 @@ class ReceiveModel: ObservableObject, InboundAppDelegate {
#if os(macOS)
func showReviewIfAppropriate(currentTransmissionCount: Int) {
// If distributed directly, do not request review here as it does not work
if pendingReview && !DistributionDetector.isDirectDistributionEnabled {
if pendingReview {
// Dont request review again
pendingReview = false
+3 -1
View File
@@ -11,7 +11,7 @@ import Foundation
let configuration = LUIConfiguration(
appName: "QuickDrop",
plusVersionInfo: GetPlusViewInformation(lifetimeID: "plusversion", description: "plusview_description_phone", plusVersionFeatures: [], settingsLabelColor: .orange, settingsFooter: "plusview_footer_phone"),
plusVersionInfo: GetPlusViewInformation(lifetimeID: "plusversion", yearlyID: "quickdrop.yearly", plusViewHeader: isMac() ? "plusview_title" : nil, description: isMac() ? "plusview_description" : "plusview_description_phone", onBoughtPlusVersionAlertMessage: "plusview_success_description", plusVersionFeatures: [], settingsLabelColor: .orange, settingsFooter: "plusview_footer_phone"),
setPlusVersion: { result in
DispatchQueue.main.async {
@@ -21,6 +21,8 @@ let configuration = LUIConfiguration(
hasRoundedFont: false,
switchedToIAPafterBundleVersion: AppVersionBundle(appVersion: "1.2", buildVersion: 0, legacyUserDefaultsKey: Settings.UserDefaultsKeys.plusVersionLegacy.rawValue),
copyrightInformation: licenseText,
loggingUrl: AppGroup.appGroupDirectory,
-2
View File
@@ -11,9 +11,7 @@ import LUI
@main
struct QuickDropiOSApp: App {
#if !os(macOS)
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
#endif
init() {
LUIInit(configuration: configuration)
+1
View File
@@ -11,6 +11,7 @@ import UIKit
import AppKit
#endif
import LUI
import Photos
import UniformTypeIdentifiers
+1
View File
@@ -7,6 +7,7 @@
import SwiftUI
import UIKit
import LUI
// MARK: - UIKit Alert Presenter
+1
View File
@@ -8,6 +8,7 @@
import Foundation
import UniformTypeIdentifiers
import SwiftUI
import LUI
#if os(macOS)
import Cocoa
#endif
+3
View File
@@ -8,6 +8,7 @@
import Cocoa
import Foundation
import SwiftUI
import LUI
class ShareViewController: NSViewController, OutboundAppDelegate {
@@ -49,6 +50,8 @@ class ShareViewController: NSViewController, OutboundAppDelegate {
override func loadView() {
super.loadView()
LUIInit(configuration: configuration)
loadAttachments(with: extensionContext, loadedItems: { result in
log("Loaded attachments: \(result)")
+10 -4
View File
@@ -10,6 +10,16 @@ import SwiftUI
import LUI
class ShareViewController: UIViewController {
override func loadView() {
super.loadView()
// skip introduction for share extension
LUIInit(configuration: configuration)
LUISettings.sharedInstance.appLaunchedBefore = true
}
override func viewDidLoad() {
super.viewDidLoad()
@@ -29,10 +39,6 @@ class ShareViewController: UIViewController {
NearbyConnectionManager.shared.attachments = attachmentDetails
}
})
// skip introduction for share extension
LUIInit(configuration: configuration)
LUISettings.sharedInstance.appLaunchedBefore = true
let hostingController = UIHostingController(rootView: ContentView())