瀏覽代碼

Ensure launch splash loading bar visibly completes to 100% before dismissal.

Replace the system progress indicator with a custom track/fill bar and complete the fill before fade-out so the startup state is clear and consistent.

Made-with: Cursor
huzaifahayat12 1 月之前
父節點
當前提交
3b55b1a364
共有 2 個文件被更改,包括 67 次插入35 次删除
  1. 53 29
      meetings_app/LaunchSplashView.swift
  2. 14 6
      meetings_app/ViewController.swift

+ 53 - 29
meetings_app/LaunchSplashView.swift

@@ -18,10 +18,12 @@ final class LaunchSplashView: NSView {
18 18
     private let iconView = NSImageView()
19 19
     private let titleLabel = NSTextField(labelWithString: "")
20 20
     private let subtitleLabel = NSTextField(labelWithString: "Preparing your meetings workspace")
21
-    private let loadingBar = NSProgressIndicator()
21
+    private let loadingTrackView = NSView()
22
+    private let loadingFillView = NSView()
22 23
     private let statusLabel = NSTextField(labelWithString: "CONNECTING SERVICES")
23 24
     private let percentLabel = NSTextField(labelWithString: "0%")
24 25
     private let taglineLabel = NSTextField(labelWithString: "Plan smart, achieve more")
26
+    private var loadingFillWidthConstraint: NSLayoutConstraint?
25 27
     private var textAnimationTimer: Timer?
26 28
     private var progressAnimationTimer: Timer?
27 29
     private var textAnimationFrame = 0
@@ -44,6 +46,20 @@ final class LaunchSplashView: NSView {
44 46
         startAnimations()
45 47
     }
46 48
 
49
+    func completeLoading(duration: TimeInterval = 0.22, completion: (() -> Void)? = nil) {
50
+        stopAnimations()
51
+        progressValue = 100
52
+        percentLabel.stringValue = "100%"
53
+        statusLabel.stringValue = "READY"
54
+        updateLoadingFill(for: 100)
55
+        layoutSubtreeIfNeeded()
56
+
57
+        // Keep the fully filled bar visible briefly before dismiss.
58
+        DispatchQueue.main.asyncAfter(deadline: .now() + max(0, duration)) {
59
+            completion?()
60
+        }
61
+    }
62
+
47 63
     func apply(theme: Theme) {
48 64
         wantsLayer = true
49 65
         layer?.backgroundColor = theme.background.cgColor
@@ -54,17 +70,15 @@ final class LaunchSplashView: NSView {
54 70
         percentLabel.textColor = theme.subtitleText.withAlphaComponent(0.9)
55 71
         taglineLabel.textColor = theme.subtitleText.withAlphaComponent(0.85)
56 72
 
57
-        loadingBar.wantsLayer = true
58
-        loadingBar.layer?.cornerRadius = 2.5
59
-        loadingBar.layer?.masksToBounds = true
60
-        loadingBar.controlTint = .blueControlTint
61
-        if #available(macOS 10.14, *) {
62
-            loadingBar.contentFilters = []
63
-        }
73
+        loadingTrackView.wantsLayer = true
74
+        loadingTrackView.layer?.cornerRadius = 2.5
75
+        loadingTrackView.layer?.masksToBounds = true
76
+        loadingTrackView.layer?.backgroundColor = theme.cardBorder.withAlphaComponent(0.3).cgColor
64 77
 
65
-        let barTrackColor = theme.cardBorder.withAlphaComponent(0.3).cgColor
66
-        loadingBar.layer?.backgroundColor = barTrackColor
67
-        loadingBar.appearance = NSAppearance(named: NSApp.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua ? .darkAqua : .aqua)
78
+        loadingFillView.wantsLayer = true
79
+        loadingFillView.layer?.cornerRadius = 2.5
80
+        loadingFillView.layer?.masksToBounds = true
81
+        loadingFillView.layer?.backgroundColor = theme.accent.cgColor
68 82
     }
69 83
 
70 84
     private func setupView() {
@@ -84,13 +98,8 @@ final class LaunchSplashView: NSView {
84 98
         subtitleLabel.alignment = .center
85 99
         subtitleLabel.font = NSFont.systemFont(ofSize: 17, weight: .medium)
86 100
 
87
-        loadingBar.translatesAutoresizingMaskIntoConstraints = false
88
-        loadingBar.style = .bar
89
-        loadingBar.isIndeterminate = false
90
-        loadingBar.minValue = 0
91
-        loadingBar.maxValue = 100
92
-        loadingBar.doubleValue = 0
93
-        loadingBar.controlSize = .regular
101
+        loadingTrackView.translatesAutoresizingMaskIntoConstraints = false
102
+        loadingFillView.translatesAutoresizingMaskIntoConstraints = false
94 103
 
95 104
         statusLabel.translatesAutoresizingMaskIntoConstraints = false
96 105
         statusLabel.alignment = .left
@@ -107,11 +116,15 @@ final class LaunchSplashView: NSView {
107 116
         addSubview(iconView)
108 117
         addSubview(titleLabel)
109 118
         addSubview(subtitleLabel)
110
-        addSubview(loadingBar)
119
+        addSubview(loadingTrackView)
120
+        loadingTrackView.addSubview(loadingFillView)
111 121
         addSubview(statusLabel)
112 122
         addSubview(percentLabel)
113 123
         addSubview(taglineLabel)
114 124
 
125
+        let fillWidthConstraint = loadingFillView.widthAnchor.constraint(equalToConstant: 0)
126
+        loadingFillWidthConstraint = fillWidthConstraint
127
+
115 128
         NSLayoutConstraint.activate([
116 129
             iconView.centerXAnchor.constraint(equalTo: centerXAnchor),
117 130
             iconView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -138),
@@ -128,16 +141,21 @@ final class LaunchSplashView: NSView {
128 141
             subtitleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 24),
129 142
             trailingAnchor.constraint(greaterThanOrEqualTo: subtitleLabel.trailingAnchor, constant: 24),
130 143
 
131
-            loadingBar.topAnchor.constraint(equalTo: subtitleLabel.bottomAnchor, constant: 30),
132
-            loadingBar.centerXAnchor.constraint(equalTo: centerXAnchor),
133
-            loadingBar.widthAnchor.constraint(equalToConstant: 320),
134
-            loadingBar.heightAnchor.constraint(equalToConstant: 5),
144
+            loadingTrackView.topAnchor.constraint(equalTo: subtitleLabel.bottomAnchor, constant: 30),
145
+            loadingTrackView.centerXAnchor.constraint(equalTo: centerXAnchor),
146
+            loadingTrackView.widthAnchor.constraint(equalToConstant: 320),
147
+            loadingTrackView.heightAnchor.constraint(equalToConstant: 5),
148
+
149
+            loadingFillView.leadingAnchor.constraint(equalTo: loadingTrackView.leadingAnchor),
150
+            loadingFillView.topAnchor.constraint(equalTo: loadingTrackView.topAnchor),
151
+            loadingFillView.bottomAnchor.constraint(equalTo: loadingTrackView.bottomAnchor),
152
+            fillWidthConstraint,
135 153
 
136
-            statusLabel.topAnchor.constraint(equalTo: loadingBar.bottomAnchor, constant: 8),
137
-            statusLabel.leadingAnchor.constraint(equalTo: loadingBar.leadingAnchor),
154
+            statusLabel.topAnchor.constraint(equalTo: loadingTrackView.bottomAnchor, constant: 8),
155
+            statusLabel.leadingAnchor.constraint(equalTo: loadingTrackView.leadingAnchor),
138 156
 
139 157
             percentLabel.centerYAnchor.constraint(equalTo: statusLabel.centerYAnchor),
140
-            percentLabel.trailingAnchor.constraint(equalTo: loadingBar.trailingAnchor),
158
+            percentLabel.trailingAnchor.constraint(equalTo: loadingTrackView.trailingAnchor),
141 159
 
142 160
             taglineLabel.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 26),
143 161
             taglineLabel.centerXAnchor.constraint(equalTo: centerXAnchor)
@@ -149,7 +167,7 @@ final class LaunchSplashView: NSView {
149 167
 
150 168
         textAnimationFrame = 0
151 169
         progressValue = 0
152
-        loadingBar.doubleValue = progressValue
170
+        updateLoadingFill(for: progressValue)
153 171
         percentLabel.stringValue = "0%"
154 172
 
155 173
         textAnimationTimer = Timer.scheduledTimer(withTimeInterval: 0.42, repeats: true) { [weak self] _ in
@@ -179,12 +197,18 @@ final class LaunchSplashView: NSView {
179 197
     }
180 198
 
181 199
     private func advanceLoadingBar() {
182
-        let next = min(progressValue + 0.8, 94)
200
+        let next = min(progressValue + 0.8, 100)
183 201
         progressValue = next
184
-        loadingBar.doubleValue = next
202
+        updateLoadingFill(for: next)
185 203
         percentLabel.stringValue = "\(Int(next.rounded()))%"
186 204
     }
187 205
 
206
+    private func updateLoadingFill(for percent: Double) {
207
+        let clamped = max(0, min(percent, 100))
208
+        let totalWidth: CGFloat = 320
209
+        loadingFillWidthConstraint?.constant = totalWidth * CGFloat(clamped / 100)
210
+    }
211
+
188 212
     override func viewWillMove(toSuperview newSuperview: NSView?) {
189 213
         if newSuperview == nil {
190 214
             stopAnimations()

+ 14 - 6
meetings_app/ViewController.swift

@@ -582,16 +582,24 @@ private extension ViewController {
582 582
             self?.launchSplashShownAt = nil
583 583
         }
584 584
 
585
+        let fadeOutAndRemove: () -> Void = {
586
+            NSAnimationContext.runAnimationGroup({ context in
587
+                context.duration = 0.24
588
+                context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
589
+                splash.animator().alphaValue = 0
590
+            }, completionHandler: removeSplash)
591
+        }
592
+
585 593
         if NSWorkspace.shared.accessibilityDisplayShouldReduceMotion {
586
-            removeSplash()
594
+            splash.completeLoading(duration: 0) {
595
+                removeSplash()
596
+            }
587 597
             return
588 598
         }
589 599
 
590
-        NSAnimationContext.runAnimationGroup({ context in
591
-            context.duration = 0.24
592
-            context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
593
-            splash.animator().alphaValue = 0
594
-        }, completionHandler: removeSplash)
600
+        splash.completeLoading {
601
+            fadeOutAndRemove()
602
+        }
595 603
     }
596 604
 
597 605
     func systemPrefersDarkMode() -> Bool {