Kaynağa Gözat

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 3 ay önce
ebeveyn
işleme
3b55b1a364

+ 53 - 29
meetings_app/LaunchSplashView.swift

@@ -18,10 +18,12 @@ final class LaunchSplashView: NSView {
     private let iconView = NSImageView()
     private let titleLabel = NSTextField(labelWithString: "")
     private let subtitleLabel = NSTextField(labelWithString: "Preparing your meetings workspace")
-    private let loadingBar = NSProgressIndicator()
+    private let loadingTrackView = NSView()
+    private let loadingFillView = NSView()
     private let statusLabel = NSTextField(labelWithString: "CONNECTING SERVICES")
     private let percentLabel = NSTextField(labelWithString: "0%")
     private let taglineLabel = NSTextField(labelWithString: "Plan smart, achieve more")
+    private var loadingFillWidthConstraint: NSLayoutConstraint?
     private var textAnimationTimer: Timer?
     private var progressAnimationTimer: Timer?
     private var textAnimationFrame = 0
@@ -44,6 +46,20 @@ final class LaunchSplashView: NSView {
         startAnimations()
     }
 
+    func completeLoading(duration: TimeInterval = 0.22, completion: (() -> Void)? = nil) {
+        stopAnimations()
+        progressValue = 100
+        percentLabel.stringValue = "100%"
+        statusLabel.stringValue = "READY"
+        updateLoadingFill(for: 100)
+        layoutSubtreeIfNeeded()
+
+        // Keep the fully filled bar visible briefly before dismiss.
+        DispatchQueue.main.asyncAfter(deadline: .now() + max(0, duration)) {
+            completion?()
+        }
+    }
+
     func apply(theme: Theme) {
         wantsLayer = true
         layer?.backgroundColor = theme.background.cgColor
@@ -54,17 +70,15 @@ final class LaunchSplashView: NSView {
         percentLabel.textColor = theme.subtitleText.withAlphaComponent(0.9)
         taglineLabel.textColor = theme.subtitleText.withAlphaComponent(0.85)
 
-        loadingBar.wantsLayer = true
-        loadingBar.layer?.cornerRadius = 2.5
-        loadingBar.layer?.masksToBounds = true
-        loadingBar.controlTint = .blueControlTint
-        if #available(macOS 10.14, *) {
-            loadingBar.contentFilters = []
-        }
+        loadingTrackView.wantsLayer = true
+        loadingTrackView.layer?.cornerRadius = 2.5
+        loadingTrackView.layer?.masksToBounds = true
+        loadingTrackView.layer?.backgroundColor = theme.cardBorder.withAlphaComponent(0.3).cgColor
 
-        let barTrackColor = theme.cardBorder.withAlphaComponent(0.3).cgColor
-        loadingBar.layer?.backgroundColor = barTrackColor
-        loadingBar.appearance = NSAppearance(named: NSApp.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua ? .darkAqua : .aqua)
+        loadingFillView.wantsLayer = true
+        loadingFillView.layer?.cornerRadius = 2.5
+        loadingFillView.layer?.masksToBounds = true
+        loadingFillView.layer?.backgroundColor = theme.accent.cgColor
     }
 
     private func setupView() {
@@ -84,13 +98,8 @@ final class LaunchSplashView: NSView {
         subtitleLabel.alignment = .center
         subtitleLabel.font = NSFont.systemFont(ofSize: 17, weight: .medium)
 
-        loadingBar.translatesAutoresizingMaskIntoConstraints = false
-        loadingBar.style = .bar
-        loadingBar.isIndeterminate = false
-        loadingBar.minValue = 0
-        loadingBar.maxValue = 100
-        loadingBar.doubleValue = 0
-        loadingBar.controlSize = .regular
+        loadingTrackView.translatesAutoresizingMaskIntoConstraints = false
+        loadingFillView.translatesAutoresizingMaskIntoConstraints = false
 
         statusLabel.translatesAutoresizingMaskIntoConstraints = false
         statusLabel.alignment = .left
@@ -107,11 +116,15 @@ final class LaunchSplashView: NSView {
         addSubview(iconView)
         addSubview(titleLabel)
         addSubview(subtitleLabel)
-        addSubview(loadingBar)
+        addSubview(loadingTrackView)
+        loadingTrackView.addSubview(loadingFillView)
         addSubview(statusLabel)
         addSubview(percentLabel)
         addSubview(taglineLabel)
 
+        let fillWidthConstraint = loadingFillView.widthAnchor.constraint(equalToConstant: 0)
+        loadingFillWidthConstraint = fillWidthConstraint
+
         NSLayoutConstraint.activate([
             iconView.centerXAnchor.constraint(equalTo: centerXAnchor),
             iconView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -138),
@@ -128,16 +141,21 @@ final class LaunchSplashView: NSView {
             subtitleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 24),
             trailingAnchor.constraint(greaterThanOrEqualTo: subtitleLabel.trailingAnchor, constant: 24),
 
-            loadingBar.topAnchor.constraint(equalTo: subtitleLabel.bottomAnchor, constant: 30),
-            loadingBar.centerXAnchor.constraint(equalTo: centerXAnchor),
-            loadingBar.widthAnchor.constraint(equalToConstant: 320),
-            loadingBar.heightAnchor.constraint(equalToConstant: 5),
+            loadingTrackView.topAnchor.constraint(equalTo: subtitleLabel.bottomAnchor, constant: 30),
+            loadingTrackView.centerXAnchor.constraint(equalTo: centerXAnchor),
+            loadingTrackView.widthAnchor.constraint(equalToConstant: 320),
+            loadingTrackView.heightAnchor.constraint(equalToConstant: 5),
+
+            loadingFillView.leadingAnchor.constraint(equalTo: loadingTrackView.leadingAnchor),
+            loadingFillView.topAnchor.constraint(equalTo: loadingTrackView.topAnchor),
+            loadingFillView.bottomAnchor.constraint(equalTo: loadingTrackView.bottomAnchor),
+            fillWidthConstraint,
 
-            statusLabel.topAnchor.constraint(equalTo: loadingBar.bottomAnchor, constant: 8),
-            statusLabel.leadingAnchor.constraint(equalTo: loadingBar.leadingAnchor),
+            statusLabel.topAnchor.constraint(equalTo: loadingTrackView.bottomAnchor, constant: 8),
+            statusLabel.leadingAnchor.constraint(equalTo: loadingTrackView.leadingAnchor),
 
             percentLabel.centerYAnchor.constraint(equalTo: statusLabel.centerYAnchor),
-            percentLabel.trailingAnchor.constraint(equalTo: loadingBar.trailingAnchor),
+            percentLabel.trailingAnchor.constraint(equalTo: loadingTrackView.trailingAnchor),
 
             taglineLabel.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 26),
             taglineLabel.centerXAnchor.constraint(equalTo: centerXAnchor)
@@ -149,7 +167,7 @@ final class LaunchSplashView: NSView {
 
         textAnimationFrame = 0
         progressValue = 0
-        loadingBar.doubleValue = progressValue
+        updateLoadingFill(for: progressValue)
         percentLabel.stringValue = "0%"
 
         textAnimationTimer = Timer.scheduledTimer(withTimeInterval: 0.42, repeats: true) { [weak self] _ in
@@ -179,12 +197,18 @@ final class LaunchSplashView: NSView {
     }
 
     private func advanceLoadingBar() {
-        let next = min(progressValue + 0.8, 94)
+        let next = min(progressValue + 0.8, 100)
         progressValue = next
-        loadingBar.doubleValue = next
+        updateLoadingFill(for: next)
         percentLabel.stringValue = "\(Int(next.rounded()))%"
     }
 
+    private func updateLoadingFill(for percent: Double) {
+        let clamped = max(0, min(percent, 100))
+        let totalWidth: CGFloat = 320
+        loadingFillWidthConstraint?.constant = totalWidth * CGFloat(clamped / 100)
+    }
+
     override func viewWillMove(toSuperview newSuperview: NSView?) {
         if newSuperview == nil {
             stopAnimations()

+ 14 - 6
meetings_app/ViewController.swift

@@ -582,16 +582,24 @@ private extension ViewController {
             self?.launchSplashShownAt = nil
         }
 
+        let fadeOutAndRemove: () -> Void = {
+            NSAnimationContext.runAnimationGroup({ context in
+                context.duration = 0.24
+                context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
+                splash.animator().alphaValue = 0
+            }, completionHandler: removeSplash)
+        }
+
         if NSWorkspace.shared.accessibilityDisplayShouldReduceMotion {
-            removeSplash()
+            splash.completeLoading(duration: 0) {
+                removeSplash()
+            }
             return
         }
 
-        NSAnimationContext.runAnimationGroup({ context in
-            context.duration = 0.24
-            context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
-            splash.animator().alphaValue = 0
-        }, completionHandler: removeSplash)
+        splash.completeLoading {
+            fadeOutAndRemove()
+        }
     }
 
     func systemPrefersDarkMode() -> Bool {