Skip to content

Commit acf0aeb

Browse files
committed
fix: start coder connect progress indicator immediately
1 parent 8fb9382 commit acf0aeb

File tree

2 files changed

+54
-22
lines changed

2 files changed

+54
-22
lines changed

Coder-Desktop/Coder-Desktop/VPN/VPNProgress.swift

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@ struct VPNProgressView: View {
1313
var body: some View {
1414
VStack {
1515
CircularProgressView(value: value)
16-
// We estimate that the last half takes 8 seconds
16+
// We estimate the duration of the last 40%
1717
// so it doesn't appear stuck
18-
.autoComplete(threshold: 0.5, duration: 8)
18+
.autoComplete(threshold: 0.7, duration: 2.8)
19+
// We estimate the duration of the first 25% (spawning Helper)
20+
// so it doesn't appear stuck
21+
.autoStart(until: 0.25, duration: 2)
1922
Text(progressMessage)
2023
.multilineTextAlignment(.center)
2124
}
@@ -46,18 +49,18 @@ struct VPNProgressView: View {
4649
guard let downloadProgress = progress.downloadProgress else {
4750
// We can't make this illegal state unrepresentable because XPC
4851
// doesn't support enums with associated values.
49-
return 0.05
52+
return 0.15
5053
}
5154
// 35MB if the server doesn't give us the expected size
5255
let totalBytes = downloadProgress.totalBytesToWrite ?? 35_000_000
5356
let downloadPercent = min(1.0, Float(downloadProgress.totalBytesWritten) / Float(totalBytes))
54-
return 0.4 * downloadPercent
57+
return 0.25 + (0.35 * downloadPercent)
5558
case .validating:
56-
return 0.43
59+
return 0.63
5760
case .removingQuarantine:
58-
return 0.46
61+
return 0.66
5962
case .startingTunnel:
60-
return 0.50
63+
return 0.70
6164
}
6265
}
6366
}

Coder-Desktop/Coder-Desktop/Views/CircularProgressView.swift

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,24 @@ import SwiftUI
33
struct CircularProgressView: View {
44
let value: Float?
55

6-
var strokeWidth: CGFloat = 4
7-
var diameter: CGFloat = 22
6+
var strokeWidth: CGFloat
7+
var diameter: CGFloat
88
var primaryColor: Color = .secondary
99
var backgroundColor: Color = .secondary.opacity(0.3)
1010

11-
var autoCompleteThreshold: Float?
12-
var autoCompleteDuration: TimeInterval?
11+
private var autoComplete: (threshold: Float, duration: TimeInterval)?
12+
private var autoStart: (until: Float, duration: TimeInterval)?
13+
14+
@State private var currentProgress: Float = 0
15+
16+
init(value: Float? = nil,
17+
strokeWidth: CGFloat = 4,
18+
diameter: CGFloat = 22)
19+
{
20+
self.value = value
21+
self.strokeWidth = strokeWidth
22+
self.diameter = diameter
23+
}
1324

1425
var body: some View {
1526
ZStack {
@@ -19,13 +30,23 @@ struct CircularProgressView: View {
1930
.stroke(backgroundColor, style: StrokeStyle(lineWidth: strokeWidth, lineCap: .round))
2031

2132
Circle()
22-
.trim(from: 0, to: CGFloat(displayValue(for: value)))
33+
.trim(from: 0, to: CGFloat(displayValue(for: currentProgress)))
2334
.stroke(primaryColor, style: StrokeStyle(lineWidth: strokeWidth, lineCap: .round))
2435
.rotationEffect(.degrees(-90))
25-
.animation(autoCompleteAnimation(for: value), value: value)
2636
}
2737
.frame(width: diameter, height: diameter)
28-
38+
.onAppear {
39+
if let autoStart, value == 0 {
40+
withAnimation(.easeOut(duration: autoStart.duration)) {
41+
currentProgress = autoStart.until
42+
}
43+
}
44+
}
45+
.onChange(of: value) {
46+
withAnimation(currentAnimation(for: value)) {
47+
currentProgress = value
48+
}
49+
}
2950
} else {
3051
IndeterminateSpinnerView(
3152
diameter: diameter,
@@ -40,31 +61,39 @@ struct CircularProgressView: View {
4061
}
4162

4263
private func displayValue(for value: Float) -> Float {
43-
if let threshold = autoCompleteThreshold,
64+
if let threshold = autoComplete?.threshold,
4465
value >= threshold, value < 1.0
4566
{
4667
return 1.0
4768
}
4869
return value
4970
}
5071

51-
private func autoCompleteAnimation(for value: Float) -> Animation? {
52-
guard let threshold = autoCompleteThreshold,
53-
let duration = autoCompleteDuration,
54-
value >= threshold, value < 1.0
72+
private func currentAnimation(for value: Float) -> Animation {
73+
guard let autoComplete,
74+
value >= autoComplete.threshold, value < 1.0
5575
else {
76+
// Use the auto-start animation if it's running, otherwise default.
77+
if let autoStart {
78+
return .easeOut(duration: autoStart.duration)
79+
}
5680
return .default
5781
}
5882

59-
return .easeOut(duration: duration)
83+
return .easeOut(duration: autoComplete.duration)
6084
}
6185
}
6286

6387
extension CircularProgressView {
6488
func autoComplete(threshold: Float, duration: TimeInterval) -> CircularProgressView {
6589
var view = self
66-
view.autoCompleteThreshold = threshold
67-
view.autoCompleteDuration = duration
90+
view.autoComplete = (threshold: threshold, duration: duration)
91+
return view
92+
}
93+
94+
func autoStart(until value: Float, duration: TimeInterval) -> CircularProgressView {
95+
var view = self
96+
view.autoStart = (until: value, duration: duration)
6897
return view
6998
}
7099
}

0 commit comments

Comments
 (0)