From 044753efb19f46aeb91ad724f771e319c46c6ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Sun, 1 Feb 2026 15:12:29 +0100 Subject: [PATCH] Present introduction as sheet window --- QuickDrop.xcodeproj/project.pbxproj | 54 ++++++++++++--- QuickDrop/AppDelegate.swift | 69 +++---------------- .../IntroductionSheetManager.swift | 65 +++++++++++++++++ .../{ => Introduction}/IntroductionView.swift | 0 .../ChangeDeviceNameModifier.swift | 0 .../View/{ => Settings}/SettingsView.swift | 0 .../{ => Settings}/TrustedDevicesView.swift | 0 .../View/{ => Toast}/QuickDropToastView.swift | 0 .../View/{ => UserManual}/LicenseWindow.swift | 0 .../View/{ => UserManual}/TutorialView.swift | 0 .../View/{ => UserManual}/WelcomeScreen.swift | 0 11 files changed, 122 insertions(+), 66 deletions(-) create mode 100644 QuickDrop/View/Introduction/IntroductionSheetManager.swift rename QuickDrop/View/{ => Introduction}/IntroductionView.swift (100%) rename QuickDrop/View/{ => Settings}/ChangeDeviceNameModifier.swift (100%) rename QuickDrop/View/{ => Settings}/SettingsView.swift (100%) rename QuickDrop/View/{ => Settings}/TrustedDevicesView.swift (100%) rename QuickDrop/View/{ => Toast}/QuickDropToastView.swift (100%) rename QuickDrop/View/{ => UserManual}/LicenseWindow.swift (100%) rename QuickDrop/View/{ => UserManual}/TutorialView.swift (100%) rename QuickDrop/View/{ => UserManual}/WelcomeScreen.swift (100%) diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index ea7e080..fd7c727 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -222,6 +222,8 @@ EB5C383F2E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */; }; EB5C38402E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */; }; EB5C38412E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */; }; + EB6E51A52F2F9370006D9812 /* IntroductionSheetManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6E51A42F2F9370006D9812 /* IntroductionSheetManager.swift */; }; + EB6E51A62F2F9370006D9812 /* IntroductionSheetManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6E51A42F2F9370006D9812 /* IntroductionSheetManager.swift */; }; EB6E6AB42ECBA46D00E71D57 /* PhotoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBBEE1452EC9D92F00AA7831 /* PhotoManager.swift */; }; EB6E6B212ECBA95000E71D57 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB6E6B202ECBA95000E71D57 /* InfoPlist.xcstrings */; }; EB6E6B222ECBA95000E71D57 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB6E6B202ECBA95000E71D57 /* InfoPlist.xcstrings */; }; @@ -514,6 +516,7 @@ EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuickDropiOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = justMaybe.mp3; sourceTree = ""; }; EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLogin.swift; sourceTree = ""; }; + EB6E51A42F2F9370006D9812 /* IntroductionSheetManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntroductionSheetManager.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 = ""; }; EB6FE7CC2EFEAFF000FF2BCF /* NoLocalNetworkAccessAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoLocalNetworkAccessAlertView.swift; sourceTree = ""; }; @@ -819,6 +822,43 @@ path = Helpers; sourceTree = ""; }; + EB6E51A72F2F95D0006D9812 /* Introduction */ = { + isa = PBXGroup; + children = ( + EB6E51A42F2F9370006D9812 /* IntroductionSheetManager.swift */, + EB7A95F22EF6E1BE0021111B /* IntroductionView.swift */, + ); + path = Introduction; + sourceTree = ""; + }; + EB6E51A82F2F95E0006D9812 /* Settings */ = { + isa = PBXGroup; + children = ( + EBA1BB1C2EF1F5650068E46E /* ChangeDeviceNameModifier.swift */, + EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */, + EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */, + ); + path = Settings; + sourceTree = ""; + }; + EB6E51A92F2F95EE006D9812 /* UserManual */ = { + isa = PBXGroup; + children = ( + EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */, + EB7F54112D78E95C00989564 /* TutorialView.swift */, + EB7F54132D78E9E400989564 /* LicenseWindow.swift */, + ); + path = UserManual; + sourceTree = ""; + }; + EB6E51AA2F2F9606006D9812 /* Toast */ = { + isa = PBXGroup; + children = ( + EB869DB42E92AA33009AAAD4 /* QuickDropToastView.swift */, + ); + path = Toast; + sourceTree = ""; + }; EB950B6C2E5206E2002D68FA /* iOSShareExtension */ = { isa = PBXGroup; children = ( @@ -845,14 +885,10 @@ isa = PBXGroup; children = ( EB7654772E525ACC006B4AA3 /* MainMenu.xib */, - EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */, - EB7F54112D78E95C00989564 /* TutorialView.swift */, - EB7F54132D78E9E400989564 /* LicenseWindow.swift */, - EB869DB42E92AA33009AAAD4 /* QuickDropToastView.swift */, - EBA1BB1C2EF1F5650068E46E /* ChangeDeviceNameModifier.swift */, - EB7A95F22EF6E1BE0021111B /* IntroductionView.swift */, - EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */, - EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */, + EB6E51A92F2F95EE006D9812 /* UserManual */, + EB6E51AA2F2F9606006D9812 /* Toast */, + EB6E51A82F2F95E0006D9812 /* Settings */, + EB6E51A72F2F95D0006D9812 /* Introduction */, EBC05C812E33B58300EECC18 /* IssueViews */, ); path = View; @@ -1294,6 +1330,7 @@ EB886E682E4F2F800052B7D9 /* License.swift in Sources */, EB7F54142D78E9E700989564 /* LicenseWindow.swift in Sources */, 69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */, + EB6E51A62F2F9370006D9812 /* IntroductionSheetManager.swift in Sources */, EB6F62D72D91F4AF00534EC2 /* SettingsView.swift in Sources */, EB7F54122D78E96000989564 /* TutorialView.swift in Sources */, EB9925812E45D53E00357055 /* sharing_enums.pb.swift in Sources */, @@ -1348,6 +1385,7 @@ EB3B9F1D2EF7150400D6704E /* License.swift in Sources */, EB3B9F1E2EF7150400D6704E /* LicenseWindow.swift in Sources */, EB3B9F202EF7150400D6704E /* AppDelegate.swift in Sources */, + EB6E51A52F2F9370006D9812 /* IntroductionSheetManager.swift in Sources */, EB3B9F212EF7150400D6704E /* SettingsView.swift in Sources */, EB3B9F222EF7150400D6704E /* TutorialView.swift in Sources */, EB3B9F232EF7150400D6704E /* sharing_enums.pb.swift in Sources */, diff --git a/QuickDrop/AppDelegate.swift b/QuickDrop/AppDelegate.swift index 94a2cfd..29ee850 100644 --- a/QuickDrop/AppDelegate.swift +++ b/QuickDrop/AppDelegate.swift @@ -20,7 +20,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele private var statusItem: NSStatusItem? var welcomeWindow: NSWindow? - var introductionWindow: NSWindow? var plusWindow: NSWindow? private var sheetView: NSPanel? = nil @@ -61,7 +60,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele menu.addItem(NSMenuItem.separator()) - let userManualItem = NSMenuItem(title: "UserManual".localized(), action: #selector(openApplicableScreen), keyEquivalent: "") + let userManualItem = NSMenuItem(title: "UserManual".localized(), action: #selector(openMainWindow), keyEquivalent: "") userManualItem.image = NSImage(systemSymbolName: "gear", accessibilityDescription: nil) menu.addItem(userManualItem) @@ -80,7 +79,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele if !Settings.sharedInstance.appLaunchedBefore { log("[AppDelegate] Opening Welcome Screen") // open welcome screen - openIntroductionWindow() + openMainWindow() // user installed the app after the IAP was implemented, set the user as eligible for IAP Settings.sharedInstance.isEligibleForIap = true @@ -150,7 +149,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele func windowWillClose(_ notification: Notification) { if let window = notification.object as? NSWindow, window == welcomeWindow { - log("Welcome window closed") + log("[AppDelegate] Welcome window closed") welcomeWindow = nil NSApp.setActivationPolicy(.accessory) } @@ -158,21 +157,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele func applicationShouldHandleReopen(_: NSApplication, hasVisibleWindows _: Bool) -> Bool { - self.openApplicableScreen() + self.openMainWindow() statusItem?.isVisible = true return true } - - @objc func openApplicableScreen() { - if Settings.sharedInstance.appLaunchedBefore { - openMainWindow() - } - else { - openIntroductionWindow() - } - } - public func userNotificationCenter(_: UNUserNotificationCenter, willPresent _: UNNotification, @@ -233,6 +222,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele @objc func openMainWindow() { + defer { + if let welcomeWindow, !Settings.sharedInstance.appLaunchedBefore { + IntroductionSheetManager.sharedInstance.openIntroductionWindow(on: welcomeWindow, startReceiving: startReceiving) { + Settings.sharedInstance.appLaunchedBefore = true + } + } + } NSApp.setActivationPolicy(.regular) @@ -273,49 +269,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele welcomeWindow?.makeKeyAndOrderFront(nil) welcomeWindow?.level = .normal } - - - @objc func openIntroductionWindow() { - - NSApp.setActivationPolicy(.regular) - - if let window = introductionWindow { - if window.isMiniaturized { - window.deminiaturize(nil) - } - NSApp.activate(ignoringOtherApps: true) - window.makeKeyAndOrderFront(nil) - return - } - - let introductionView = IntroductionView(startReceiving: startReceiving) { - Settings.sharedInstance.appLaunchedBefore = true - self.introductionWindow?.close() - self.introductionWindow = nil - self.openMainWindow() - } - - introductionWindow = NSWindow( - contentRect: NSRect(x: 0, y: 0, width: IntroductionView.width, height: IntroductionView.height), - styleMask: [.titled, .closable], - backing: .buffered, - defer: false - ) - - introductionWindow?.styleMask.insert(.fullSizeContentView) - introductionWindow?.titleVisibility = .hidden - introductionWindow?.titlebarAppearsTransparent = true - - introductionWindow?.center() - introductionWindow?.isReleasedWhenClosed = false - introductionWindow?.setFrameAutosaveName("IntroductionScreen") - introductionWindow?.contentView = NSHostingView(rootView: introductionView) - introductionWindow?.delegate = self - - NSApp.activate(ignoringOtherApps: true) - introductionWindow?.makeKeyAndOrderFront(nil) - introductionWindow?.level = .normal - } @objc func openPlusScreen() { diff --git a/QuickDrop/View/Introduction/IntroductionSheetManager.swift b/QuickDrop/View/Introduction/IntroductionSheetManager.swift new file mode 100644 index 0000000..f0c7451 --- /dev/null +++ b/QuickDrop/View/Introduction/IntroductionSheetManager.swift @@ -0,0 +1,65 @@ +// +// AlertManager.swift +// QuickDrop +// +// Created by Leon Böttger on 01.02.26. +// + + +import LUI +import SwiftUI + +public final class IntroductionSheetManager: ObservableObject { + + public static let sharedInstance = IntroductionSheetManager() + + private var sheetView: NSPanel? = nil + private var sheetAttachedWindow: NSWindow? = nil + + + func openIntroductionWindow(on window: NSWindow, startReceiving: @escaping () -> Void, onFinish: @escaping () -> Void) { + openSheetWindow(on: window, contentView: IntroductionView(startReceiving: startReceiving, onFinish: { + self.closeSheetWindow() + onFinish() + })) + } + + + private func openSheetWindow(on window: NSWindow, contentView: Content) { + if sheetView == nil { + // Create the panel + let panel = NSPanel( + contentRect: NSRect(x: 0, y: 0, width: IntroductionView.width, height: IntroductionView.height), + styleMask: [.titled, .closable, .utilityWindow], + backing: .buffered, + defer: false + ) + + // Set content (can be a NSHostingView for SwiftUI views) + let hostingView = NSHostingView(rootView: contentView) + panel.contentView = hostingView + + // Present as sheet + window.beginSheet(panel, completionHandler: { response in + log("[IntroductionSheetManager] Sheet dismissed with response: \(response)") + }) + + self.sheetAttachedWindow = window + self.sheetView = panel + } + } + + + func closeSheetWindow() { + + if let mainWindow = sheetAttachedWindow, let sheetView = sheetView { + + log("[IntroductionSheetManager] Closing sheetView window") + + mainWindow.endSheet(sheetView) + + self.sheetView = nil + self.sheetAttachedWindow = nil + } + } +} diff --git a/QuickDrop/View/IntroductionView.swift b/QuickDrop/View/Introduction/IntroductionView.swift similarity index 100% rename from QuickDrop/View/IntroductionView.swift rename to QuickDrop/View/Introduction/IntroductionView.swift diff --git a/QuickDrop/View/ChangeDeviceNameModifier.swift b/QuickDrop/View/Settings/ChangeDeviceNameModifier.swift similarity index 100% rename from QuickDrop/View/ChangeDeviceNameModifier.swift rename to QuickDrop/View/Settings/ChangeDeviceNameModifier.swift diff --git a/QuickDrop/View/SettingsView.swift b/QuickDrop/View/Settings/SettingsView.swift similarity index 100% rename from QuickDrop/View/SettingsView.swift rename to QuickDrop/View/Settings/SettingsView.swift diff --git a/QuickDrop/View/TrustedDevicesView.swift b/QuickDrop/View/Settings/TrustedDevicesView.swift similarity index 100% rename from QuickDrop/View/TrustedDevicesView.swift rename to QuickDrop/View/Settings/TrustedDevicesView.swift diff --git a/QuickDrop/View/QuickDropToastView.swift b/QuickDrop/View/Toast/QuickDropToastView.swift similarity index 100% rename from QuickDrop/View/QuickDropToastView.swift rename to QuickDrop/View/Toast/QuickDropToastView.swift diff --git a/QuickDrop/View/LicenseWindow.swift b/QuickDrop/View/UserManual/LicenseWindow.swift similarity index 100% rename from QuickDrop/View/LicenseWindow.swift rename to QuickDrop/View/UserManual/LicenseWindow.swift diff --git a/QuickDrop/View/TutorialView.swift b/QuickDrop/View/UserManual/TutorialView.swift similarity index 100% rename from QuickDrop/View/TutorialView.swift rename to QuickDrop/View/UserManual/TutorialView.swift diff --git a/QuickDrop/View/WelcomeScreen.swift b/QuickDrop/View/UserManual/WelcomeScreen.swift similarity index 100% rename from QuickDrop/View/WelcomeScreen.swift rename to QuickDrop/View/UserManual/WelcomeScreen.swift