From 9d6f138e40a5322ee731f8096eaa3332c94a6532 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Mon, 18 May 2026 17:54:35 +0800
Subject: [PATCH] Speed up repeated app list opens

---
 Apptag/ApptagApp.swift       |   19 ++++++---
 Apptag/ContentView.swift     |   26 +++++++++---
 Apptag/DataLayer.swift       |   41 ++++++++++++++++++++
 Apptag/PreferencesView.swift |    2 
 Apptag/Info.plist            |    4 +-
 CHANGELOG.md                 |    7 +++
 6 files changed, 82 insertions(+), 17 deletions(-)

diff --git a/Apptag/ApptagApp.swift b/Apptag/ApptagApp.swift
index daf5d45..f108c83 100644
--- a/Apptag/ApptagApp.swift
+++ b/Apptag/ApptagApp.swift
@@ -499,15 +499,22 @@
             NSMouseInRect(mousePoint, $0.frame, false)
         }) ?? NSScreen.main ?? NSScreen.screens.first else { return }
 
-        overlayWindow?.orderOut(nil)
-        overlayWindow = makeOverlayWindow(on: screen)
-        overlayWindow?.setFrame(screen.frame, display: true)
-        overlayWindow?.level = isEditingAppNote ? Self.overlayTextInputLevel : Self.overlayDefaultLevel
+        let window: NSWindow
+        if let existingWindow = overlayWindow {
+            window = existingWindow
+        } else {
+            window = makeOverlayWindow(on: screen)
+            overlayWindow = window
+        }
+
+        window.setFrame(screen.frame, display: true)
+        window.level = isEditingAppNote ? Self.overlayTextInputLevel : Self.overlayDefaultLevel
 
         installOverlayKeyMonitor()
 
-        overlayWindow?.makeKeyAndOrderFront(nil)
-        overlayWindow?.orderFrontRegardless()
+        window.makeKeyAndOrderFront(nil)
+        window.orderFrontRegardless()
+        NotificationCenter.default.post(name: .tagLauncherOverlayDidShow, object: nil)
     }
 
     private func installOverlayKeyMonitor() {
diff --git a/Apptag/ContentView.swift b/Apptag/ContentView.swift
index 8eae143..16f587e 100644
--- a/Apptag/ContentView.swift
+++ b/Apptag/ContentView.swift
@@ -9,6 +9,7 @@
     static let tagLauncherAppNoteEditingChanged = Notification.Name("TagLauncherAppNoteEditingChanged")
     static let tagLauncherDataDidChange = Notification.Name("TagLauncherDataDidChange")
     static let tagLauncherOpenPreferencesRequested = Notification.Name("TagLauncherOpenPreferencesRequested")
+    static let tagLauncherOverlayDidShow = Notification.Name("TagLauncherOverlayDidShow")
 }
 
 // MARK: - Edit Phase
@@ -483,14 +484,15 @@
             }
         }
         .onAppear {
-            let mousePoint = NSEvent.mouseLocation
-            let activeScreen = NSScreen.screens.first(where: {
-                NSMouseInRect(mousePoint, $0.frame, false)
-            }) ?? NSScreen.main
-            notchHeight = activeScreen?.safeAreaInsets.top ?? 0
+            refreshNotchHeight()
+            refreshApps()
+        }
+        .onReceive(NotificationCenter.default.publisher(for: .tagLauncherOverlayDidShow)) { _ in
+            refreshNotchHeight()
             refreshApps()
         }
         .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in
+            guard allApps.isEmpty, !refreshInProgress else { return }
             refreshApps()
         }
         .onReceive(NotificationCenter.default.publisher(for: .tagLauncherDataDidChange)) { _ in
@@ -1853,6 +1855,14 @@
 
     // MARK: - Actions
 
+    private func refreshNotchHeight() {
+        let mousePoint = NSEvent.mouseLocation
+        let activeScreen = NSScreen.screens.first(where: {
+            NSMouseInRect(mousePoint, $0.frame, false)
+        }) ?? NSScreen.main
+        notchHeight = activeScreen?.safeAreaInsets.top ?? 0
+    }
+
     private func handleBubbleHover(app: AppInfo, frame: CGRect, hovering: Bool) {
         guard !appBubbleDisabled else {
             clearAppBubbleState()
@@ -2222,8 +2232,10 @@
 
     func refreshApps(forceLayoutRefresh: Bool = false) {
         guard !refreshInProgress else {
-            refreshAgainAfterCurrent = true
-            refreshAgainForceLayout = refreshAgainForceLayout || forceLayoutRefresh
+            if forceLayoutRefresh {
+                refreshAgainAfterCurrent = true
+                refreshAgainForceLayout = true
+            }
             return
         }
 
diff --git a/Apptag/DataLayer.swift b/Apptag/DataLayer.swift
index 6b0f69a..779beac 100644
--- a/Apptag/DataLayer.swift
+++ b/Apptag/DataLayer.swift
@@ -51,6 +51,10 @@
 // MARK: - App Scanner
 
 enum AppIndexer {
+    private static let scanCacheLock = NSLock()
+    private static var cachedScanApps: [AppInfo]? = nil
+    private static var cachedScanAt: Date? = nil
+    private static let scanCacheTTL: TimeInterval = 1800
 
     static let searchPaths: [URL] = [
         URL(fileURLWithPath: "/Applications"),
@@ -72,7 +76,42 @@
     }
 
     /// Scan all standard locations. Tags are annotated from TagDatabase by the caller.
-    static func scan() -> [AppInfo] {
+    static func scan(useCache: Bool = true) -> [AppInfo] {
+        if useCache, let cached = cachedScanIfFresh() {
+            return cached
+        }
+
+        let apps = performScan()
+        updateScanCache(apps)
+        return apps
+    }
+
+    static func invalidateScanCache() {
+        scanCacheLock.lock()
+        cachedScanApps = nil
+        cachedScanAt = nil
+        scanCacheLock.unlock()
+    }
+
+    private static func cachedScanIfFresh() -> [AppInfo]? {
+        scanCacheLock.lock()
+        defer { scanCacheLock.unlock() }
+
+        guard let cachedScanApps,
+              let cachedScanAt,
+              Date().timeIntervalSince(cachedScanAt) < scanCacheTTL
+        else { return nil }
+        return cachedScanApps
+    }
+
+    private static func updateScanCache(_ apps: [AppInfo]) {
+        scanCacheLock.lock()
+        cachedScanApps = apps
+        cachedScanAt = Date()
+        scanCacheLock.unlock()
+    }
+
+    private static func performScan() -> [AppInfo] {
         var seenResolvedPaths = Set<String>()
         var apps: [AppInfo] = []
 
diff --git a/Apptag/Info.plist b/Apptag/Info.plist
index 0f31def..0e5117b 100644
--- a/Apptag/Info.plist
+++ b/Apptag/Info.plist
@@ -19,9 +19,9 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>7.3.3</string>
+	<string>7.3.4</string>
 	<key>CFBundleVersion</key>
-	<string>742</string>
+	<string>743</string>
 	<key>LSMinimumSystemVersion</key>
 	<string>15.0</string>
 	<key>NSHighResolutionCapable</key>
diff --git a/Apptag/PreferencesView.swift b/Apptag/PreferencesView.swift
index 1a89d05..88c00a4 100644
--- a/Apptag/PreferencesView.swift
+++ b/Apptag/PreferencesView.swift
@@ -140,7 +140,7 @@
         isApplyingSystemScheme = true
 
         DispatchQueue.global(qos: .userInitiated).async {
-            let scannedApps = AppIndexer.scan()
+            let scannedApps = AppIndexer.scan(useCache: false)
             let result = SmartStartService.applySystemInitialScheme(apps: scannedApps)
             let store = result.store
             let apps = TagEditor.annotate(apps: scannedApps, store: store)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f47fdd..6415b88 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Apptag Changelog
 
+## [7.3.4] — 2026-05-18
+
+- 优化应用列表打开速度:覆盖层窗口不再每次显示都销毁重建,减少重复初始化
+- App 扫描增加会话缓存,避免短时间内反复加载全部应用图标导致每次打开都长时间等待
+- 避免应用激活通知和首次显示同时触发重复全量刷新;刷新进行中时只保留必要的强制刷新请求
+- 版本号更新为 `7.3.4`,Build 更新为 `743`
+
 ## [7.3.3] — 2026-05-18
 
 - 拖拽 App 时会立即关闭并禁用 App 用途气泡,避免拖向目标容器时旧气泡继续遮挡视线

--
Gitblit v1.9.3