diff --git a/Config.storekit b/Config.storekit new file mode 100644 index 0000000..e962b49 --- /dev/null +++ b/Config.storekit @@ -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 + } +} diff --git a/NearbyShare/Connection/InboundNearbyConnection.swift b/NearbyShare/Connection/InboundNearbyConnection.swift index e74092f..7acecdd 100644 --- a/NearbyShare/Connection/InboundNearbyConnection.swift +++ b/NearbyShare/Connection/InboundNearbyConnection.swift @@ -12,6 +12,7 @@ import Network import System import BigInt import SwiftECC +import LUI #if os(macOS) import AppKit diff --git a/NearbyShare/Connection/NearbyConnection.swift b/NearbyShare/Connection/NearbyConnection.swift index ecb9bcb..a68e28d 100644 --- a/NearbyShare/Connection/NearbyConnection.swift +++ b/NearbyShare/Connection/NearbyConnection.swift @@ -12,6 +12,7 @@ import Network import System import BigInt import SwiftECC +import LUI class NearbyConnection { diff --git a/NearbyShare/Connection/NearbyConnectionManager.swift b/NearbyShare/Connection/NearbyConnectionManager.swift index 5bd75a1..6fc891d 100644 --- a/NearbyShare/Connection/NearbyConnectionManager.swift +++ b/NearbyShare/Connection/NearbyConnectionManager.swift @@ -17,6 +17,7 @@ import SwiftECC import CryptoKit import SwiftUI import QRCode +import LUI public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate, ObservableObject { diff --git a/NearbyShare/Connection/OutboundNearbyConnection.swift b/NearbyShare/Connection/OutboundNearbyConnection.swift index 7a47786..ddf66d4 100644 --- a/NearbyShare/Connection/OutboundNearbyConnection.swift +++ b/NearbyShare/Connection/OutboundNearbyConnection.swift @@ -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 { diff --git a/NearbyShare/Util/EXIFUtils.swift b/NearbyShare/Util/EXIFUtils.swift index 901f366..bde8810 100644 --- a/NearbyShare/Util/EXIFUtils.swift +++ b/NearbyShare/Util/EXIFUtils.swift @@ -7,6 +7,7 @@ import Foundation import ImageIO +import LUI struct EXIFUtils { diff --git a/NearbyShare/Util/Helpers.swift b/NearbyShare/Util/Helpers.swift index cba44c2..f84701a 100644 --- a/NearbyShare/Util/Helpers.swift +++ b/NearbyShare/Util/Helpers.swift @@ -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 } diff --git a/NearbyShare/Util/IdentityManager.swift b/NearbyShare/Util/IdentityManager.swift index 3af4e99..a912378 100644 --- a/NearbyShare/Util/IdentityManager.swift +++ b/NearbyShare/Util/IdentityManager.swift @@ -10,6 +10,7 @@ import Foundation import SwiftECC import Security import CryptoKit +import LUI class IdentityManager { diff --git a/NearbyShare/Util/Logger.swift b/NearbyShare/Util/Logger.swift deleted file mode 100644 index b4bd483..0000000 --- a/NearbyShare/Util/Logger.swift +++ /dev/null @@ -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 - } - } -} diff --git a/NearbyShare/Util/SleepManager.swift b/NearbyShare/Util/SleepManager.swift index ac026a2..fd7fc33 100644 --- a/NearbyShare/Util/SleepManager.swift +++ b/NearbyShare/Util/SleepManager.swift @@ -13,6 +13,7 @@ import IOKit.pwr_mgt #elseif os(iOS) import UIKit #endif +import LUI class SleepManager { public static let shared = SleepManager() diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index 5d8e29e..d352267 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -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 = ""; }; EB6E6B202ECBA95000E71D57 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = ""; }; EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; - EB704A812E4A7BAE008E608F /* DistributionDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DistributionDetector.swift; sourceTree = ""; }; EB721F232E75AFA900F2099F /* QuickDropIcon.icon */ = {isa = PBXFileReference; lastKnownFileType = folder.iconcomposer.icon; path = QuickDropIcon.icon; sourceTree = ""; }; EB7654772E525ACC006B4AA3 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; - EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; EB7F54112D78E95C00989564 /* TutorialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialView.swift; sourceTree = ""; }; EB7F54132D78E9E400989564 /* LicenseWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseWindow.swift; sourceTree = ""; }; EB869DB42E92AA33009AAAD4 /* QuickDropToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickDropToastView.swift; sourceTree = ""; }; @@ -378,9 +374,6 @@ EBBC93B12EC8B75F00F541A1 /* LocalNetworkPermissionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNetworkPermissionView.swift; sourceTree = ""; }; EBBEE1452EC9D92F00AA7831 /* PhotoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoManager.swift; sourceTree = ""; }; EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlertHandler.swift; sourceTree = ""; }; - EBC05C6D2E33B57D00EECC18 /* Converter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Converter.swift; sourceTree = ""; }; - EBC05C6E2E33B57D00EECC18 /* Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = ""; }; - EBC05C6F2E33B57D00EECC18 /* PlusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlusView.swift; sourceTree = ""; }; EBC05C7D2E33B58300EECC18 /* ApIsolationIssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApIsolationIssueView.swift; sourceTree = ""; }; EBC05C7E2E33B58300EECC18 /* FirewallIssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirewallIssueView.swift; sourceTree = ""; }; EBC05C7F2E33B58300EECC18 /* IssueView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IssueView.swift; sourceTree = ""; }; @@ -388,6 +381,7 @@ EBC05C8A2E33B59E00EECC18 /* timeIsNow.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = timeIsNow.mp3; sourceTree = ""; }; EBCC27F82D80C9F70079C478 /* Zip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Zip.swift; sourceTree = ""; }; EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = ""; }; + EBE5FF4A2EDB8D4B0019B380 /* Config.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Config.storekit; sourceTree = ""; }; /* 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 = ""; }; - EBC05C702E33B57D00EECC18 /* Plus */ = { - isa = PBXGroup; - children = ( - EBC05C6D2E33B57D00EECC18 /* Converter.swift */, - EB704A812E4A7BAE008E608F /* DistributionDetector.swift */, - EBC05C6E2E33B57D00EECC18 /* Helper.swift */, - EBC05C6F2E33B57D00EECC18 /* PlusView.swift */, - ); - path = Plus; - sourceTree = ""; - }; 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 */; diff --git a/QuickDrop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/QuickDrop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c5ac072..0dfa186 100644 --- a/QuickDrop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/QuickDrop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -52,7 +52,7 @@ "location" : "https://github.com/leonboe1/LUI.git", "state" : { "branch" : "main", - "revision" : "1490442fc11be90a3418355b81db783abeb9a6c2" + "revision" : "1c507e431e4b73cee127165a1fbd4a930abecd7d" } }, { diff --git a/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDrop.xcscheme b/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDrop.xcscheme index d2ce80f..9213fdc 100644 --- a/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDrop.xcscheme +++ b/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDrop.xcscheme @@ -50,6 +50,9 @@ ReferencedContainer = "container:QuickDrop.xcodeproj"> + + ())? = 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) - } - } -} diff --git a/QuickDrop/Plus/DistributionDetector.swift b/QuickDrop/Plus/DistributionDetector.swift deleted file mode 100644 index 11916b7..0000000 --- a/QuickDrop/Plus/DistributionDetector.swift +++ /dev/null @@ -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 -} diff --git a/QuickDrop/Plus/Helper.swift b/QuickDrop/Plus/Helper.swift deleted file mode 100644 index 207bd73..0000000 --- a/QuickDrop/Plus/Helper.swift +++ /dev/null @@ -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) -> 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) -> Void)) { - let payment = SKPayment(product: product) - SKPaymentQueue.default().add(payment) - - // Keep the completion handler. - onBuyProductHandler = handler - } - - - func restorePurchases(withHandler handler: @escaping ((_ result: Result) -> 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." - } - } -} diff --git a/QuickDrop/Plus/PlusView.swift b/QuickDrop/Plus/PlusView.swift deleted file mode 100644 index 9e4fd52..0000000 --- a/QuickDrop/Plus/PlusView.swift +++ /dev/null @@ -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: {}) -} diff --git a/QuickDrop/Settings.swift b/QuickDrop/Settings.swift index 8d23d68..80cb6b9 100644 --- a/QuickDrop/Settings.swift +++ b/QuickDrop/Settings.swift @@ -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" } } diff --git a/QuickDrop/Util/AudioManager.swift b/QuickDrop/Util/AudioManager.swift index 61bd87a..4cf1d3c 100644 --- a/QuickDrop/Util/AudioManager.swift +++ b/QuickDrop/Util/AudioManager.swift @@ -6,6 +6,7 @@ // import AVFoundation +import LUI class AudioManager { static private var incomingFileSoundPlayer: AVAudioPlayer? diff --git a/QuickDrop/Util/ErrorAlertHandler.swift b/QuickDrop/Util/ErrorAlertHandler.swift index 98385bd..7124c34 100644 --- a/QuickDrop/Util/ErrorAlertHandler.swift +++ b/QuickDrop/Util/ErrorAlertHandler.swift @@ -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 { diff --git a/QuickDrop/Util/LaunchAtLogin.swift b/QuickDrop/Util/LaunchAtLogin.swift index 165eca2..7c47594 100644 --- a/QuickDrop/Util/LaunchAtLogin.swift +++ b/QuickDrop/Util/LaunchAtLogin.swift @@ -19,6 +19,7 @@ import SwiftUI import ServiceManagement import os.log +import LUI @available(macOS 13.0, *) public enum LaunchAtLogin { diff --git a/QuickDrop/Util/SelfTest/ActiveNetworkInterface.swift b/QuickDrop/Util/SelfTest/ActiveNetworkInterface.swift index 8ee44f7..df5f827 100644 --- a/QuickDrop/Util/SelfTest/ActiveNetworkInterface.swift +++ b/QuickDrop/Util/SelfTest/ActiveNetworkInterface.swift @@ -6,6 +6,7 @@ // import Foundation +import LUI /// Returns the name of the currently active network interface (e.g. "en0", "en1") func getActiveNetworkInterface() -> String? { diff --git a/QuickDrop/Util/SelfTest/ApIsolation6Tester.swift b/QuickDrop/Util/SelfTest/ApIsolation6Tester.swift index 84ddc51..dc90fe5 100644 --- a/QuickDrop/Util/SelfTest/ApIsolation6Tester.swift +++ b/QuickDrop/Util/SelfTest/ApIsolation6Tester.swift @@ -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. diff --git a/QuickDrop/Util/SelfTest/ApIsolationTester.swift b/QuickDrop/Util/SelfTest/ApIsolationTester.swift index d896f36..898aeb9 100644 --- a/QuickDrop/Util/SelfTest/ApIsolationTester.swift +++ b/QuickDrop/Util/SelfTest/ApIsolationTester.swift @@ -7,6 +7,7 @@ import Foundation import Network +import LUI class DeviceToDeviceHeuristicScanner { private let scanQueue = DispatchQueue(label: "DeviceToDeviceScanQueue") diff --git a/QuickDrop/View/IssueViews/ApIsolationIssueView.swift b/QuickDrop/View/IssueViews/ApIsolationIssueView.swift index 619e15b..d7ff0a3 100644 --- a/QuickDrop/View/IssueViews/ApIsolationIssueView.swift +++ b/QuickDrop/View/IssueViews/ApIsolationIssueView.swift @@ -9,6 +9,7 @@ import SwiftUI import Foundation import AppKit import Network +import LUI fileprivate let routerCheckQueue = DispatchQueue(label: "routerCheckQueue") diff --git a/QuickDrop/View/QuickDropToastView.swift b/QuickDrop/View/QuickDropToastView.swift index f85207e..9a24124 100644 --- a/QuickDrop/View/QuickDropToastView.swift +++ b/QuickDrop/View/QuickDropToastView.swift @@ -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") diff --git a/QuickDrop/View/SettingsView.swift b/QuickDrop/View/SettingsView.swift index a11a15d..3bc1edc 100644 --- a/QuickDrop/View/SettingsView.swift +++ b/QuickDrop/View/SettingsView.swift @@ -7,6 +7,7 @@ import SwiftUI import AppKit +import LUI struct SettingsView: View { diff --git a/QuickDrop/View/TutorialView.swift b/QuickDrop/View/TutorialView.swift index aeeb359..06eca2b 100644 --- a/QuickDrop/View/TutorialView.swift +++ b/QuickDrop/View/TutorialView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import LUI struct TutorialView: View { @@ -81,10 +82,8 @@ struct LargeAppIconView: 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) diff --git a/QuickDrop/ViewModel/ReceiveModel.swift b/QuickDrop/ViewModel/ReceiveModel.swift index 1237240..32165c0 100644 --- a/QuickDrop/ViewModel/ReceiveModel.swift +++ b/QuickDrop/ViewModel/ReceiveModel.swift @@ -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 diff --git a/QuickDropiOS/Configuration.swift b/QuickDropiOS/Configuration.swift index 4279917..2fe4b6e 100644 --- a/QuickDropiOS/Configuration.swift +++ b/QuickDropiOS/Configuration.swift @@ -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, diff --git a/QuickDropiOS/QuickDropiOSApp.swift b/QuickDropiOS/QuickDropiOSApp.swift index b46fe4e..274d965 100644 --- a/QuickDropiOS/QuickDropiOSApp.swift +++ b/QuickDropiOS/QuickDropiOSApp.swift @@ -11,9 +11,7 @@ import LUI @main struct QuickDropiOSApp: App { - #if !os(macOS) @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate - #endif init() { LUIInit(configuration: configuration) diff --git a/QuickDropiOS/View/PhotoManager.swift b/QuickDropiOS/View/PhotoManager.swift index fd1760b..278d7a4 100644 --- a/QuickDropiOS/View/PhotoManager.swift +++ b/QuickDropiOS/View/PhotoManager.swift @@ -11,6 +11,7 @@ import UIKit import AppKit #endif +import LUI import Photos import UniformTypeIdentifiers diff --git a/QuickDropiOS/View/UIAlert.swift b/QuickDropiOS/View/UIAlert.swift index faea41a..a2f243a 100644 --- a/QuickDropiOS/View/UIAlert.swift +++ b/QuickDropiOS/View/UIAlert.swift @@ -7,6 +7,7 @@ import SwiftUI import UIKit +import LUI // MARK: - UIKit Alert Presenter diff --git a/ShareExtension/Helpers/Attachments.swift b/ShareExtension/Helpers/Attachments.swift index d6a3258..6b3f296 100644 --- a/ShareExtension/Helpers/Attachments.swift +++ b/ShareExtension/Helpers/Attachments.swift @@ -8,6 +8,7 @@ import Foundation import UniformTypeIdentifiers import SwiftUI +import LUI #if os(macOS) import Cocoa #endif diff --git a/ShareExtension/ShareViewController.swift b/ShareExtension/ShareViewController.swift index 7383ed6..9dcbf62 100644 --- a/ShareExtension/ShareViewController.swift +++ b/ShareExtension/ShareViewController.swift @@ -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)") diff --git a/iOSShareExtension/ShareViewController.swift b/iOSShareExtension/ShareViewController.swift index c13e8ef..6862222 100644 --- a/iOSShareExtension/ShareViewController.swift +++ b/iOSShareExtension/ShareViewController.swift @@ -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())