Quellcode durchsuchen

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 vor 1 Monat
Ursprung
Commit
0714123b2f
1 geänderte Dateien mit 126 neuen und 60 gelöschten Zeilen
  1. 126 60
      meetings_app/LaunchSplashView.swift

+ 126 - 60
meetings_app/LaunchSplashView.swift

@@ -15,12 +15,17 @@ final class LaunchSplashView: NSView {
15 15
         let accent: NSColor
16 16
     }
17 17
 
18
-    private let cardContainer = NSVisualEffectView()
19 18
     private let iconView = NSImageView()
20 19
     private let titleLabel = NSTextField(labelWithString: "")
21
-    private let subtitleLabel = NSTextField(labelWithString: "Preparing your meetings workspace...")
22
-    private let spinner = NSProgressIndicator()
23
-    private let accentBar = NSView()
20
+    private let subtitleLabel = NSTextField(labelWithString: "Preparing your meetings workspace")
21
+    private let loadingBar = NSProgressIndicator()
22
+    private let statusLabel = NSTextField(labelWithString: "CONNECTING SERVICES")
23
+    private let percentLabel = NSTextField(labelWithString: "0%")
24
+    private let taglineLabel = NSTextField(labelWithString: "Plan smart, achieve more")
25
+    private var textAnimationTimer: Timer?
26
+    private var progressAnimationTimer: Timer?
27
+    private var textAnimationFrame = 0
28
+    private var progressValue: Double = 0
24 29
 
25 30
     override init(frame frameRect: NSRect) {
26 31
         super.init(frame: frameRect)
@@ -36,93 +41,154 @@ final class LaunchSplashView: NSView {
36 41
         titleLabel.stringValue = appName
37 42
         iconView.image = appIcon
38 43
         apply(theme: theme)
39
-        spinner.startAnimation(nil)
44
+        startAnimations()
40 45
     }
41 46
 
42 47
     func apply(theme: Theme) {
43 48
         wantsLayer = true
44 49
         layer?.backgroundColor = theme.background.cgColor
45 50
 
46
-        cardContainer.wantsLayer = true
47
-        cardContainer.layer?.backgroundColor = theme.cardBackground.withAlphaComponent(0.85).cgColor
48
-        cardContainer.layer?.borderWidth = 1
49
-        cardContainer.layer?.borderColor = theme.cardBorder.withAlphaComponent(0.5).cgColor
50
-        cardContainer.layer?.cornerRadius = 20
51
-
52 51
         titleLabel.textColor = theme.titleText
53 52
         subtitleLabel.textColor = theme.subtitleText
54
-        accentBar.wantsLayer = true
55
-        accentBar.layer?.backgroundColor = theme.accent.cgColor
53
+        statusLabel.textColor = theme.subtitleText.withAlphaComponent(0.9)
54
+        percentLabel.textColor = theme.subtitleText.withAlphaComponent(0.9)
55
+        taglineLabel.textColor = theme.subtitleText.withAlphaComponent(0.85)
56
+
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
+        }
64
+
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)
56 68
     }
57 69
 
58 70
     private func setupView() {
59 71
         translatesAutoresizingMaskIntoConstraints = false
60 72
 
61
-        cardContainer.translatesAutoresizingMaskIntoConstraints = false
62
-        cardContainer.material = .hudWindow
63
-        cardContainer.blendingMode = .withinWindow
64
-        cardContainer.state = .active
65
-
66 73
         iconView.translatesAutoresizingMaskIntoConstraints = false
67 74
         iconView.imageScaling = .scaleProportionallyUpOrDown
68 75
         iconView.wantsLayer = true
69
-        iconView.layer?.cornerRadius = 22
76
+        iconView.layer?.cornerRadius = 20
70 77
         iconView.layer?.masksToBounds = true
71 78
 
72 79
         titleLabel.translatesAutoresizingMaskIntoConstraints = false
73 80
         titleLabel.alignment = .center
74
-        titleLabel.font = NSFont.systemFont(ofSize: 30, weight: .bold)
75
-        titleLabel.maximumNumberOfLines = 1
76
-        titleLabel.lineBreakMode = .byTruncatingTail
81
+        titleLabel.font = NSFont.systemFont(ofSize: 42, weight: .bold)
77 82
 
78 83
         subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
79 84
         subtitleLabel.alignment = .center
80
-        subtitleLabel.font = NSFont.systemFont(ofSize: 14, weight: .medium)
81
-
82
-        spinner.translatesAutoresizingMaskIntoConstraints = false
83
-        spinner.style = .spinning
84
-        spinner.controlSize = .large
85
-        spinner.isDisplayedWhenStopped = true
86
-        
87
-        accentBar.translatesAutoresizingMaskIntoConstraints = false
88
-        accentBar.wantsLayer = true
89
-        accentBar.layer?.cornerRadius = 2
90
-
91
-        addSubview(cardContainer)
92
-        cardContainer.addSubview(iconView)
93
-        cardContainer.addSubview(titleLabel)
94
-        cardContainer.addSubview(subtitleLabel)
95
-        cardContainer.addSubview(accentBar)
96
-        cardContainer.addSubview(spinner)
85
+        subtitleLabel.font = NSFont.systemFont(ofSize: 17, weight: .medium)
86
+
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
94
+
95
+        statusLabel.translatesAutoresizingMaskIntoConstraints = false
96
+        statusLabel.alignment = .left
97
+        statusLabel.font = NSFont.monospacedSystemFont(ofSize: 10, weight: .semibold)
98
+
99
+        percentLabel.translatesAutoresizingMaskIntoConstraints = false
100
+        percentLabel.alignment = .right
101
+        percentLabel.font = NSFont.monospacedDigitSystemFont(ofSize: 10, weight: .semibold)
102
+
103
+        taglineLabel.translatesAutoresizingMaskIntoConstraints = false
104
+        taglineLabel.alignment = .center
105
+        taglineLabel.font = NSFont.systemFont(ofSize: 14, weight: .semibold)
106
+
107
+        addSubview(iconView)
108
+        addSubview(titleLabel)
109
+        addSubview(subtitleLabel)
110
+        addSubview(loadingBar)
111
+        addSubview(statusLabel)
112
+        addSubview(percentLabel)
113
+        addSubview(taglineLabel)
97 114
 
98 115
         NSLayoutConstraint.activate([
99
-            cardContainer.centerXAnchor.constraint(equalTo: centerXAnchor),
100
-            cardContainer.centerYAnchor.constraint(equalTo: centerYAnchor),
101
-            cardContainer.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 24),
102
-            trailingAnchor.constraint(greaterThanOrEqualTo: cardContainer.trailingAnchor, constant: 24),
103
-            cardContainer.widthAnchor.constraint(lessThanOrEqualToConstant: 620),
104
-
105
-            iconView.topAnchor.constraint(equalTo: cardContainer.topAnchor, constant: 34),
106
-            iconView.centerXAnchor.constraint(equalTo: cardContainer.centerXAnchor),
107
-            iconView.widthAnchor.constraint(equalToConstant: 120),
116
+            iconView.centerXAnchor.constraint(equalTo: centerXAnchor),
117
+            iconView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -138),
118
+            iconView.widthAnchor.constraint(equalToConstant: 104),
108 119
             iconView.heightAnchor.constraint(equalTo: iconView.widthAnchor),
109 120
 
110 121
             titleLabel.topAnchor.constraint(equalTo: iconView.bottomAnchor, constant: 22),
111
-            titleLabel.leadingAnchor.constraint(equalTo: cardContainer.leadingAnchor, constant: 24),
112
-            titleLabel.trailingAnchor.constraint(equalTo: cardContainer.trailingAnchor, constant: -24),
122
+            titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
123
+            titleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 24),
124
+            trailingAnchor.constraint(greaterThanOrEqualTo: titleLabel.trailingAnchor, constant: 24),
125
+
126
+            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 14),
127
+            subtitleLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
128
+            subtitleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 24),
129
+            trailingAnchor.constraint(greaterThanOrEqualTo: subtitleLabel.trailingAnchor, constant: 24),
130
+
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),
113 135
 
114
-            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 10),
115
-            subtitleLabel.leadingAnchor.constraint(equalTo: cardContainer.leadingAnchor, constant: 24),
116
-            subtitleLabel.trailingAnchor.constraint(equalTo: cardContainer.trailingAnchor, constant: -24),
136
+            statusLabel.topAnchor.constraint(equalTo: loadingBar.bottomAnchor, constant: 8),
137
+            statusLabel.leadingAnchor.constraint(equalTo: loadingBar.leadingAnchor),
117 138
 
118
-            accentBar.topAnchor.constraint(equalTo: subtitleLabel.bottomAnchor, constant: 18),
119
-            accentBar.centerXAnchor.constraint(equalTo: cardContainer.centerXAnchor),
120
-            accentBar.widthAnchor.constraint(equalToConstant: 120),
121
-            accentBar.heightAnchor.constraint(equalToConstant: 4),
139
+            percentLabel.centerYAnchor.constraint(equalTo: statusLabel.centerYAnchor),
140
+            percentLabel.trailingAnchor.constraint(equalTo: loadingBar.trailingAnchor),
122 141
 
123
-            spinner.topAnchor.constraint(equalTo: accentBar.bottomAnchor, constant: 14),
124
-            spinner.centerXAnchor.constraint(equalTo: cardContainer.centerXAnchor),
125
-            spinner.bottomAnchor.constraint(equalTo: cardContainer.bottomAnchor, constant: -32)
142
+            taglineLabel.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 26),
143
+            taglineLabel.centerXAnchor.constraint(equalTo: centerXAnchor)
126 144
         ])
127 145
     }
146
+
147
+    private func startAnimations() {
148
+        stopAnimations()
149
+
150
+        textAnimationFrame = 0
151
+        progressValue = 0
152
+        loadingBar.doubleValue = progressValue
153
+        percentLabel.stringValue = "0%"
154
+
155
+        textAnimationTimer = Timer.scheduledTimer(withTimeInterval: 0.42, repeats: true) { [weak self] _ in
156
+            self?.updateAnimatedText()
157
+        }
158
+        progressAnimationTimer = Timer.scheduledTimer(withTimeInterval: 0.045, repeats: true) { [weak self] _ in
159
+            self?.advanceLoadingBar()
160
+        }
161
+        if let textAnimationTimer {
162
+            RunLoop.main.add(textAnimationTimer, forMode: .common)
163
+        }
164
+        if let progressAnimationTimer {
165
+            RunLoop.main.add(progressAnimationTimer, forMode: .common)
166
+        }
167
+    }
168
+
169
+    private func stopAnimations() {
170
+        textAnimationTimer?.invalidate()
171
+        textAnimationTimer = nil
172
+        progressAnimationTimer?.invalidate()
173
+        progressAnimationTimer = nil
174
+    }
175
+
176
+    private func updateAnimatedText() {
177
+        textAnimationFrame = (textAnimationFrame + 1) % 4
178
+        subtitleLabel.stringValue = "Preparing your meetings workspace" + String(repeating: ".", count: textAnimationFrame)
179
+    }
180
+
181
+    private func advanceLoadingBar() {
182
+        let next = min(progressValue + 0.8, 94)
183
+        progressValue = next
184
+        loadingBar.doubleValue = next
185
+        percentLabel.stringValue = "\(Int(next.rounded()))%"
186
+    }
187
+
188
+    override func viewWillMove(toSuperview newSuperview: NSView?) {
189
+        if newSuperview == nil {
190
+            stopAnimations()
191
+        }
192
+        super.viewWillMove(toSuperview: newSuperview)
193
+    }
128 194
 }