瀏覽代碼

Redesign launch splash into a full-screen experience with centered branded content.

Replace the card-and-spinner layout with an animated loading bar and status text treatment so startup feels cleaner and more professional.

Made-with: Cursor
huzaifahayat12 3 月之前
父節點
當前提交
0714123b2f
共有 1 個文件被更改,包括 126 次插入60 次删除
  1. 126 60
      meetings_app/LaunchSplashView.swift

+ 126 - 60
meetings_app/LaunchSplashView.swift

@@ -15,12 +15,17 @@ final class LaunchSplashView: NSView {
         let accent: NSColor
     }
 
-    private let cardContainer = NSVisualEffectView()
     private let iconView = NSImageView()
     private let titleLabel = NSTextField(labelWithString: "")
-    private let subtitleLabel = NSTextField(labelWithString: "Preparing your meetings workspace...")
-    private let spinner = NSProgressIndicator()
-    private let accentBar = NSView()
+    private let subtitleLabel = NSTextField(labelWithString: "Preparing your meetings workspace")
+    private let loadingBar = NSProgressIndicator()
+    private let statusLabel = NSTextField(labelWithString: "CONNECTING SERVICES")
+    private let percentLabel = NSTextField(labelWithString: "0%")
+    private let taglineLabel = NSTextField(labelWithString: "Plan smart, achieve more")
+    private var textAnimationTimer: Timer?
+    private var progressAnimationTimer: Timer?
+    private var textAnimationFrame = 0
+    private var progressValue: Double = 0
 
     override init(frame frameRect: NSRect) {
         super.init(frame: frameRect)
@@ -36,93 +41,154 @@ final class LaunchSplashView: NSView {
         titleLabel.stringValue = appName
         iconView.image = appIcon
         apply(theme: theme)
-        spinner.startAnimation(nil)
+        startAnimations()
     }
 
     func apply(theme: Theme) {
         wantsLayer = true
         layer?.backgroundColor = theme.background.cgColor
 
-        cardContainer.wantsLayer = true
-        cardContainer.layer?.backgroundColor = theme.cardBackground.withAlphaComponent(0.85).cgColor
-        cardContainer.layer?.borderWidth = 1
-        cardContainer.layer?.borderColor = theme.cardBorder.withAlphaComponent(0.5).cgColor
-        cardContainer.layer?.cornerRadius = 20
-
         titleLabel.textColor = theme.titleText
         subtitleLabel.textColor = theme.subtitleText
-        accentBar.wantsLayer = true
-        accentBar.layer?.backgroundColor = theme.accent.cgColor
+        statusLabel.textColor = theme.subtitleText.withAlphaComponent(0.9)
+        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 = []
+        }
+
+        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)
     }
 
     private func setupView() {
         translatesAutoresizingMaskIntoConstraints = false
 
-        cardContainer.translatesAutoresizingMaskIntoConstraints = false
-        cardContainer.material = .hudWindow
-        cardContainer.blendingMode = .withinWindow
-        cardContainer.state = .active
-
         iconView.translatesAutoresizingMaskIntoConstraints = false
         iconView.imageScaling = .scaleProportionallyUpOrDown
         iconView.wantsLayer = true
-        iconView.layer?.cornerRadius = 22
+        iconView.layer?.cornerRadius = 20
         iconView.layer?.masksToBounds = true
 
         titleLabel.translatesAutoresizingMaskIntoConstraints = false
         titleLabel.alignment = .center
-        titleLabel.font = NSFont.systemFont(ofSize: 30, weight: .bold)
-        titleLabel.maximumNumberOfLines = 1
-        titleLabel.lineBreakMode = .byTruncatingTail
+        titleLabel.font = NSFont.systemFont(ofSize: 42, weight: .bold)
 
         subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
         subtitleLabel.alignment = .center
-        subtitleLabel.font = NSFont.systemFont(ofSize: 14, weight: .medium)
-
-        spinner.translatesAutoresizingMaskIntoConstraints = false
-        spinner.style = .spinning
-        spinner.controlSize = .large
-        spinner.isDisplayedWhenStopped = true
-        
-        accentBar.translatesAutoresizingMaskIntoConstraints = false
-        accentBar.wantsLayer = true
-        accentBar.layer?.cornerRadius = 2
-
-        addSubview(cardContainer)
-        cardContainer.addSubview(iconView)
-        cardContainer.addSubview(titleLabel)
-        cardContainer.addSubview(subtitleLabel)
-        cardContainer.addSubview(accentBar)
-        cardContainer.addSubview(spinner)
+        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
+
+        statusLabel.translatesAutoresizingMaskIntoConstraints = false
+        statusLabel.alignment = .left
+        statusLabel.font = NSFont.monospacedSystemFont(ofSize: 10, weight: .semibold)
+
+        percentLabel.translatesAutoresizingMaskIntoConstraints = false
+        percentLabel.alignment = .right
+        percentLabel.font = NSFont.monospacedDigitSystemFont(ofSize: 10, weight: .semibold)
+
+        taglineLabel.translatesAutoresizingMaskIntoConstraints = false
+        taglineLabel.alignment = .center
+        taglineLabel.font = NSFont.systemFont(ofSize: 14, weight: .semibold)
+
+        addSubview(iconView)
+        addSubview(titleLabel)
+        addSubview(subtitleLabel)
+        addSubview(loadingBar)
+        addSubview(statusLabel)
+        addSubview(percentLabel)
+        addSubview(taglineLabel)
 
         NSLayoutConstraint.activate([
-            cardContainer.centerXAnchor.constraint(equalTo: centerXAnchor),
-            cardContainer.centerYAnchor.constraint(equalTo: centerYAnchor),
-            cardContainer.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 24),
-            trailingAnchor.constraint(greaterThanOrEqualTo: cardContainer.trailingAnchor, constant: 24),
-            cardContainer.widthAnchor.constraint(lessThanOrEqualToConstant: 620),
-
-            iconView.topAnchor.constraint(equalTo: cardContainer.topAnchor, constant: 34),
-            iconView.centerXAnchor.constraint(equalTo: cardContainer.centerXAnchor),
-            iconView.widthAnchor.constraint(equalToConstant: 120),
+            iconView.centerXAnchor.constraint(equalTo: centerXAnchor),
+            iconView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -138),
+            iconView.widthAnchor.constraint(equalToConstant: 104),
             iconView.heightAnchor.constraint(equalTo: iconView.widthAnchor),
 
             titleLabel.topAnchor.constraint(equalTo: iconView.bottomAnchor, constant: 22),
-            titleLabel.leadingAnchor.constraint(equalTo: cardContainer.leadingAnchor, constant: 24),
-            titleLabel.trailingAnchor.constraint(equalTo: cardContainer.trailingAnchor, constant: -24),
+            titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
+            titleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 24),
+            trailingAnchor.constraint(greaterThanOrEqualTo: titleLabel.trailingAnchor, constant: 24),
+
+            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 14),
+            subtitleLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
+            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),
 
-            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),
-            subtitleLabel.leadingAnchor.constraint(equalTo: cardContainer.leadingAnchor, constant: 24),
-            subtitleLabel.trailingAnchor.constraint(equalTo: cardContainer.trailingAnchor, constant: -24),
+            statusLabel.topAnchor.constraint(equalTo: loadingBar.bottomAnchor, constant: 8),
+            statusLabel.leadingAnchor.constraint(equalTo: loadingBar.leadingAnchor),
 
-            accentBar.topAnchor.constraint(equalTo: subtitleLabel.bottomAnchor, constant: 18),
-            accentBar.centerXAnchor.constraint(equalTo: cardContainer.centerXAnchor),
-            accentBar.widthAnchor.constraint(equalToConstant: 120),
-            accentBar.heightAnchor.constraint(equalToConstant: 4),
+            percentLabel.centerYAnchor.constraint(equalTo: statusLabel.centerYAnchor),
+            percentLabel.trailingAnchor.constraint(equalTo: loadingBar.trailingAnchor),
 
-            spinner.topAnchor.constraint(equalTo: accentBar.bottomAnchor, constant: 14),
-            spinner.centerXAnchor.constraint(equalTo: cardContainer.centerXAnchor),
-            spinner.bottomAnchor.constraint(equalTo: cardContainer.bottomAnchor, constant: -32)
+            taglineLabel.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 26),
+            taglineLabel.centerXAnchor.constraint(equalTo: centerXAnchor)
         ])
     }
+
+    private func startAnimations() {
+        stopAnimations()
+
+        textAnimationFrame = 0
+        progressValue = 0
+        loadingBar.doubleValue = progressValue
+        percentLabel.stringValue = "0%"
+
+        textAnimationTimer = Timer.scheduledTimer(withTimeInterval: 0.42, repeats: true) { [weak self] _ in
+            self?.updateAnimatedText()
+        }
+        progressAnimationTimer = Timer.scheduledTimer(withTimeInterval: 0.045, repeats: true) { [weak self] _ in
+            self?.advanceLoadingBar()
+        }
+        if let textAnimationTimer {
+            RunLoop.main.add(textAnimationTimer, forMode: .common)
+        }
+        if let progressAnimationTimer {
+            RunLoop.main.add(progressAnimationTimer, forMode: .common)
+        }
+    }
+
+    private func stopAnimations() {
+        textAnimationTimer?.invalidate()
+        textAnimationTimer = nil
+        progressAnimationTimer?.invalidate()
+        progressAnimationTimer = nil
+    }
+
+    private func updateAnimatedText() {
+        textAnimationFrame = (textAnimationFrame + 1) % 4
+        subtitleLabel.stringValue = "Preparing your meetings workspace" + String(repeating: ".", count: textAnimationFrame)
+    }
+
+    private func advanceLoadingBar() {
+        let next = min(progressValue + 0.8, 94)
+        progressValue = next
+        loadingBar.doubleValue = next
+        percentLabel.stringValue = "\(Int(next.rounded()))%"
+    }
+
+    override func viewWillMove(toSuperview newSuperview: NSView?) {
+        if newSuperview == nil {
+            stopAnimations()
+        }
+        super.viewWillMove(toSuperview: newSuperview)
+    }
 }