Skip to content

Commit f13958e

Browse files
committed
Fix up loading progress view
1 parent d4d69ad commit f13958e

File tree

6 files changed

+205
-76
lines changed

6 files changed

+205
-76
lines changed

Modules/Sources/Support/UI/Bot Conversations/ConversationListView.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ public struct ConversationListView: View {
5858

5959
return .loaded(conversations, nil)
6060
}
61+
62+
var isPartiallyLoaded: Bool {
63+
guard case .partiallyLoaded = self else {
64+
return false
65+
}
66+
67+
return true
68+
}
6169
}
6270

6371
enum ViewSubstate {
@@ -109,11 +117,9 @@ public struct ConversationListView: View {
109117
.disabled(!currentUser.permissions.contains(.createChatConversation))
110118
}
111119
}
112-
.overlay(content: {
113-
if case .partiallyLoaded = state {
114-
LoadingLatestContentView()
115-
}
116-
})
120+
.overlay {
121+
OverlayProgressView(shouldBeVisible: state.isPartiallyLoaded)
122+
}
117123
.task(self.loadConversations)
118124
.refreshable(action: self.reloadConversations)
119125
}

Modules/Sources/Support/UI/Bot Conversations/ConversationView.swift

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,14 @@ public struct ConversationView: View {
183183
)
184184
}
185185
}
186+
187+
var isPartiallyLoaded: Bool {
188+
guard case .partiallyLoaded = self else {
189+
return false
190+
}
191+
192+
return true
193+
}
186194
}
187195

188196
enum ViewSubstate: Equatable {
@@ -284,11 +292,9 @@ public struct ConversationView: View {
284292
)
285293
}
286294
}
287-
.overlay(content: {
288-
if case .partiallyLoaded = state {
289-
LoadingLatestContentView()
290-
}
291-
})
295+
.overlay {
296+
OverlayProgressView(shouldBeVisible: state.isPartiallyLoaded)
297+
}
292298
.task(self.loadExistingConversation)
293299
.refreshable(action: self.reloadConversation)
294300
}

Modules/Sources/Support/UI/LoadingLatestContentView.swift

Lines changed: 0 additions & 53 deletions
This file was deleted.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import SwiftUI
2+
3+
struct OverlayProgressView: View {
4+
5+
enum ViewState {
6+
case mustBeHidden
7+
case mustBeVisible
8+
case inherit
9+
}
10+
11+
enum Style {
12+
case toast
13+
case horizontalBar
14+
}
15+
16+
let shouldBeVisible: Bool
17+
private let minimumDisplayTime: Duration
18+
private let style: Style
19+
20+
@State
21+
private var state: ViewState = .mustBeHidden // Start off hidden so the view animates in
22+
23+
private var isVisible: Bool {
24+
switch self.state {
25+
case .mustBeHidden: false
26+
case .mustBeVisible: true
27+
case .inherit: shouldBeVisible
28+
}
29+
}
30+
31+
init(shouldBeVisible: Bool, minimumDisplayTime: Duration = .seconds(3.8), style: Style = .horizontalBar) {
32+
self.shouldBeVisible = shouldBeVisible
33+
self.minimumDisplayTime = minimumDisplayTime
34+
self.style = style
35+
}
36+
37+
var body: some View {
38+
ZStack {
39+
switch style {
40+
case .toast:
41+
toastView
42+
case .horizontalBar:
43+
horizontalBarView
44+
}
45+
}
46+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: style == .toast ? .top : .bottom)
47+
.padding(.top, style == .toast ? 24 : 0)
48+
.padding(.bottom, style == .horizontalBar ? 0 : 0)
49+
.onAppear {
50+
withAnimation(.easeOut) {
51+
self.state = .mustBeVisible
52+
}
53+
}
54+
.task {
55+
try? await Task.sleep(for: self.minimumDisplayTime)
56+
await MainActor.run {
57+
withAnimation(.easeOut) {
58+
self.state = .inherit
59+
}
60+
}
61+
}
62+
}
63+
64+
@ViewBuilder
65+
private var toastView: some View {
66+
// The toast container
67+
HStack(spacing: 12) {
68+
ProgressView()
69+
.progressViewStyle(.circular)
70+
71+
Text("Loading latest content")
72+
.font(.callout)
73+
.foregroundStyle(.primary)
74+
}
75+
.padding(.horizontal, 16)
76+
.padding(.vertical, 12)
77+
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 14, style: .continuous))
78+
.overlay(
79+
RoundedRectangle(cornerRadius: 14, style: .continuous)
80+
.strokeBorder(.secondary.opacity(0.15))
81+
)
82+
.shadow(color: .black.opacity(0.15), radius: 10, x: 0, y: 4)
83+
.opacity(isVisible ? 1 : 0)
84+
.offset(y: isVisible ? 0 : -12)
85+
.accessibilityElement(children: .combine)
86+
.accessibilityLabel("Loading latest content")
87+
.accessibilityAddTraits(.isStaticText)
88+
}
89+
90+
@ViewBuilder
91+
private var horizontalBarView: some View {
92+
VStack(spacing: 0) {
93+
Rectangle()
94+
.fill(Color.accentColor)
95+
.frame(height: 4)
96+
.frame(maxWidth: .infinity)
97+
.opacity(isVisible ? 1 : 0)
98+
.scaleEffect(x: isVisible ? 1 : 0, y: 1, anchor: .leading)
99+
.overlay(
100+
Rectangle()
101+
.fill(Color.accentColor.opacity(0.7))
102+
.scaleEffect(x: 0.3, y: 1)
103+
.offset(x: isVisible ? UIScreen.main.bounds.width : -100)
104+
.animation(
105+
isVisible ? .easeInOut(duration: 1.2).repeatForever(autoreverses: false) : .default,
106+
value: isVisible
107+
)
108+
)
109+
.accessibilityLabel("Loading")
110+
.accessibilityAddTraits(.updatesFrequently)
111+
}
112+
}
113+
}
114+
115+
#Preview("Toast Style") {
116+
NavigationStack {
117+
List {
118+
ForEach(0..<12) { i in
119+
Text("Row \(i)")
120+
}
121+
}
122+
.navigationTitle("Demo")
123+
}
124+
.overlay(alignment: .top) {
125+
OverlayProgressView(shouldBeVisible: true, style: .toast)
126+
}
127+
}
128+
129+
#Preview("Horizontal Bar Style") {
130+
NavigationStack {
131+
List {
132+
ForEach(0..<12) { i in
133+
Text("Row \(i)")
134+
}
135+
}
136+
.navigationTitle("Demo")
137+
}
138+
.overlay(alignment: .bottom) {
139+
OverlayProgressView(shouldBeVisible: true, style: .horizontalBar)
140+
}
141+
}

Modules/Sources/Support/UI/Support Conversations/SupportConversationListView.swift

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,34 @@ import SwiftUI
22

33
public struct SupportConversationListView: View {
44

5-
enum ViewState {
5+
enum ViewState: Equatable {
66
case loading
77
case partiallyLoaded([ConversationSummary])
88
case loaded([ConversationSummary])
99
case error(Error)
10+
11+
static func == (lhs: ViewState, rhs: ViewState) -> Bool {
12+
switch (lhs, rhs) {
13+
case (.loading, .loading):
14+
return true
15+
case (.partiallyLoaded(let lhsConversations), .partiallyLoaded(let rhsConversations)):
16+
return lhsConversations == rhsConversations
17+
case (.loaded(let lhsConversations), .loaded(let rhsConversations)):
18+
return lhsConversations == rhsConversations
19+
case (.error, .error):
20+
return true
21+
default:
22+
return false
23+
}
24+
}
25+
26+
var isPartiallyLoaded: Bool {
27+
guard case .partiallyLoaded = self else {
28+
return false
29+
}
30+
31+
return true
32+
}
1033
}
1134

1235
@EnvironmentObject
@@ -55,11 +78,9 @@ public struct SupportConversationListView: View {
5578
SupportForm(supportIdentity: self.currentUser)
5679
}.environmentObject(self.dataProvider) // Required until SwiftUI owns the nav controller
5780
})
58-
.overlay(content: {
59-
if case .partiallyLoaded = state {
60-
LoadingLatestContentView()
61-
}
62-
})
81+
.overlay {
82+
OverlayProgressView(shouldBeVisible: self.state.isPartiallyLoaded)
83+
}
6384
.task(self.loadConversations)
6485
.refreshable(action: self.reloadConversations)
6586
}

Modules/Sources/Support/UI/Support Conversations/SupportConversationView.swift

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ public struct SupportConversationView: View {
77
case partiallyLoaded(Conversation)
88
case loaded(Conversation)
99
case error(Error)
10+
11+
var isPartiallyLoaded: Bool {
12+
guard case .partiallyLoaded = self else {
13+
return false
14+
}
15+
16+
return true
17+
}
1018
}
1119

1220
@EnvironmentObject
@@ -49,8 +57,10 @@ public struct SupportConversationView: View {
4957
switch self.state {
5058
case .loading:
5159
ProgressView(Localization.loadingMessages)
52-
case .partiallyLoaded(let conversation): self.conversationView(conversation)
53-
case .loaded(let conversation): self.conversationView(conversation)
60+
case .partiallyLoaded(let conversation):
61+
self.conversationView(conversation)
62+
case .loaded(let conversation):
63+
self.conversationView(conversation)
5464
case .error(let error):
5565
ErrorView(
5666
title: Localization.unableToDisplayConversation,
@@ -70,11 +80,9 @@ public struct SupportConversationView: View {
7080
.disabled(!canReply)
7181
}
7282
}
73-
.overlay(content: {
74-
if case .partiallyLoaded = state {
75-
LoadingLatestContentView()
76-
}
77-
})
83+
.overlay {
84+
OverlayProgressView(shouldBeVisible: self.state.isPartiallyLoaded)
85+
}
7886
.sheet(isPresented: $isReplying) {
7987
if case .loaded(let conversation) = state {
8088
NavigationStack {

0 commit comments

Comments
 (0)