Просмотр исходного кода

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 месяц назад
Родитель
Сommit
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
     private let iconView = NSImageView()
18
     private let iconView = NSImageView()
19
     private let titleLabel = NSTextField(labelWithString: "")
19
     private let titleLabel = NSTextField(labelWithString: "")
20
     private let subtitleLabel = NSTextField(labelWithString: "Preparing your meetings workspace")
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
     private let statusLabel = NSTextField(labelWithString: "CONNECTING SERVICES")
23
     private let statusLabel = NSTextField(labelWithString: "CONNECTING SERVICES")
23
     private let percentLabel = NSTextField(labelWithString: "0%")
24
     private let percentLabel = NSTextField(labelWithString: "0%")
24
     private let taglineLabel = NSTextField(labelWithString: "Plan smart, achieve more")
25
     private let taglineLabel = NSTextField(labelWithString: "Plan smart, achieve more")
26
+    private var loadingFillWidthConstraint: NSLayoutConstraint?
25
     private var textAnimationTimer: Timer?
27
     private var textAnimationTimer: Timer?
26
     private var progressAnimationTimer: Timer?
28
     private var progressAnimationTimer: Timer?
27
     private var textAnimationFrame = 0
29
     private var textAnimationFrame = 0
@@ -44,6 +46,20 @@ final class LaunchSplashView: NSView {
44
         startAnimations()
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
     func apply(theme: Theme) {
63
     func apply(theme: Theme) {
48
         wantsLayer = true
64
         wantsLayer = true
49
         layer?.backgroundColor = theme.background.cgColor
65
         layer?.backgroundColor = theme.background.cgColor
@@ -54,17 +70,15 @@ final class LaunchSplashView: NSView {
54
         percentLabel.textColor = theme.subtitleText.withAlphaComponent(0.9)
70
         percentLabel.textColor = theme.subtitleText.withAlphaComponent(0.9)
55
         taglineLabel.textColor = theme.subtitleText.withAlphaComponent(0.85)
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
     private func setupView() {
84
     private func setupView() {
@@ -84,13 +98,8 @@ final class LaunchSplashView: NSView {
84
         subtitleLabel.alignment = .center
98
         subtitleLabel.alignment = .center
85
         subtitleLabel.font = NSFont.systemFont(ofSize: 17, weight: .medium)
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
         statusLabel.translatesAutoresizingMaskIntoConstraints = false
104
         statusLabel.translatesAutoresizingMaskIntoConstraints = false
96
         statusLabel.alignment = .left
105
         statusLabel.alignment = .left
@@ -107,11 +116,15 @@ final class LaunchSplashView: NSView {
107
         addSubview(iconView)
116
         addSubview(iconView)
108
         addSubview(titleLabel)
117
         addSubview(titleLabel)
109
         addSubview(subtitleLabel)
118
         addSubview(subtitleLabel)
110
-        addSubview(loadingBar)
119
+        addSubview(loadingTrackView)
120
+        loadingTrackView.addSubview(loadingFillView)
111
         addSubview(statusLabel)
121
         addSubview(statusLabel)
112
         addSubview(percentLabel)
122
         addSubview(percentLabel)
113
         addSubview(taglineLabel)
123
         addSubview(taglineLabel)
114
 
124
 
125
+        let fillWidthConstraint = loadingFillView.widthAnchor.constraint(equalToConstant: 0)
126
+        loadingFillWidthConstraint = fillWidthConstraint
127
+
115
         NSLayoutConstraint.activate([
128
         NSLayoutConstraint.activate([
116
             iconView.centerXAnchor.constraint(equalTo: centerXAnchor),
129
             iconView.centerXAnchor.constraint(equalTo: centerXAnchor),
117
             iconView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -138),
130
             iconView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -138),
@@ -128,16 +141,21 @@ final class LaunchSplashView: NSView {
128
             subtitleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 24),
141
             subtitleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 24),
129
             trailingAnchor.constraint(greaterThanOrEqualTo: subtitleLabel.trailingAnchor, constant: 24),
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
             percentLabel.centerYAnchor.constraint(equalTo: statusLabel.centerYAnchor),
157
             percentLabel.centerYAnchor.constraint(equalTo: statusLabel.centerYAnchor),
140
-            percentLabel.trailingAnchor.constraint(equalTo: loadingBar.trailingAnchor),
158
+            percentLabel.trailingAnchor.constraint(equalTo: loadingTrackView.trailingAnchor),
141
 
159
 
142
             taglineLabel.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 26),
160
             taglineLabel.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 26),
143
             taglineLabel.centerXAnchor.constraint(equalTo: centerXAnchor)
161
             taglineLabel.centerXAnchor.constraint(equalTo: centerXAnchor)
@@ -149,7 +167,7 @@ final class LaunchSplashView: NSView {
149
 
167
 
150
         textAnimationFrame = 0
168
         textAnimationFrame = 0
151
         progressValue = 0
169
         progressValue = 0
152
-        loadingBar.doubleValue = progressValue
170
+        updateLoadingFill(for: progressValue)
153
         percentLabel.stringValue = "0%"
171
         percentLabel.stringValue = "0%"
154
 
172
 
155
         textAnimationTimer = Timer.scheduledTimer(withTimeInterval: 0.42, repeats: true) { [weak self] _ in
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
     private func advanceLoadingBar() {
199
     private func advanceLoadingBar() {
182
-        let next = min(progressValue + 0.8, 94)
200
+        let next = min(progressValue + 0.8, 100)
183
         progressValue = next
201
         progressValue = next
184
-        loadingBar.doubleValue = next
202
+        updateLoadingFill(for: next)
185
         percentLabel.stringValue = "\(Int(next.rounded()))%"
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
     override func viewWillMove(toSuperview newSuperview: NSView?) {
212
     override func viewWillMove(toSuperview newSuperview: NSView?) {
189
         if newSuperview == nil {
213
         if newSuperview == nil {
190
             stopAnimations()
214
             stopAnimations()

+ 14 - 6
meetings_app/ViewController.swift

@@ -582,16 +582,24 @@ private extension ViewController {
582
             self?.launchSplashShownAt = nil
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
         if NSWorkspace.shared.accessibilityDisplayShouldReduceMotion {
593
         if NSWorkspace.shared.accessibilityDisplayShouldReduceMotion {
586
-            removeSplash()
594
+            splash.completeLoading(duration: 0) {
595
+                removeSplash()
596
+            }
587
             return
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
     func systemPrefersDarkMode() -> Bool {
605
     func systemPrefersDarkMode() -> Bool {