@@ -7,6 +7,184 @@ import Pulse
7
7
import Combine
8
8
import CoreData
9
9
10
+ #if os(iOS)
11
+
12
+ @available ( iOS 15 , visionOS 1 . 0 , * )
13
+ struct ConsoleTaskCell : View {
14
+ @ObservedObject var task : NetworkTaskEntity
15
+ var isDisclosureNeeded = false
16
+
17
+ @ScaledMetric ( relativeTo: . body) private var fontMultiplier = 1.0
18
+ @ObservedObject private var settings : UserSettings = . shared
19
+ @Environment ( \. store) private var store : LoggerStore
20
+
21
+ var body : some View {
22
+ VStack ( alignment: . leading, spacing: 6 ) {
23
+ header
24
+ content
25
+ footer
26
+ }
27
+ }
28
+
29
+ // MARK: – Header
30
+
31
+ // TODO: add missing "Mock" badge
32
+ private var header : some View {
33
+ HStack {
34
+ StatusIndicatorView ( state: task. state ( in: store) )
35
+ info
36
+ Spacer ( )
37
+ ConsoleTimestampView ( date: task. createdAt)
38
+ . overlay ( alignment: . trailing) {
39
+ if isDisclosureNeeded {
40
+ ListDisclosureIndicator ( )
41
+ . offset ( x: 11 )
42
+ }
43
+ }
44
+ }
45
+ }
46
+
47
+ private var info : some View {
48
+ var text : Text {
49
+ let status = Text ( ConsoleFormatter . status ( for: task, store: store) )
50
+ . font ( . footnote. weight ( . medium) )
51
+
52
+ guard settings. displayOptions. isShowingDetails else {
53
+ return status
54
+ }
55
+ let details = settings. displayOptions. detailsFields
56
+ . compactMap ( makeInfoText)
57
+ . joined ( separator: " · " )
58
+ guard !details. isEmpty else {
59
+ return status
60
+ }
61
+ return status + Text( " · \( details) " ) . font ( . footnote)
62
+ }
63
+ return text
64
+ . tracking ( - 0.3 )
65
+ . lineLimit ( 1 )
66
+ . foregroundStyle ( . secondary)
67
+ }
68
+
69
+
70
+ private func makeInfoText( for detail: DisplayOptions . Field ) -> String ? {
71
+ switch detail {
72
+ case . method:
73
+ task. httpMethod
74
+ case . requestSize:
75
+ byteCount ( for: task. requestBodySize)
76
+ case . responseSize:
77
+ byteCount ( for: task. responseBodySize)
78
+ case . responseContentType:
79
+ task. responseContentType. map ( NetworkLogger . ContentType. init) ? . lastComponent. uppercased ( )
80
+ case . duration:
81
+ ConsoleFormatter . duration ( for: task)
82
+ case . host:
83
+ task. host
84
+ case . statusCode:
85
+ task. statusCode != 0 ? task. statusCode. description : nil
86
+ case . taskType:
87
+ NetworkLogger . TaskType ( rawValue: task. taskType) ? . urlSessionTaskClassName
88
+ case . taskDescription:
89
+ task. taskDescription
90
+ }
91
+ }
92
+
93
+ // MARK: – Content
94
+
95
+ private var content : some View {
96
+ var method : Text ? {
97
+ guard let method = task. httpMethod else {
98
+ return nil
99
+ }
100
+ return Text ( method. appending ( " " ) )
101
+ . font ( contentFont. weight ( . medium) . smallCaps ( ) )
102
+ . tracking ( - 0.3 )
103
+ }
104
+
105
+ var main : Text {
106
+ Text ( task. getFormattedContent ( options: settings. displayOptions) ?? " – " )
107
+ . font ( contentFont)
108
+ }
109
+
110
+ var text : Text {
111
+ if let method {
112
+ method + main
113
+ } else {
114
+ main
115
+ }
116
+ }
117
+
118
+ return text
119
+ . lineLimit ( settings. displayOptions. contentLineLimit)
120
+ }
121
+
122
+ // MARK: – Footer
123
+
124
+ @ViewBuilder
125
+ private var footer : some View {
126
+ if let host = task. host, !host. isEmpty {
127
+ Text ( host)
128
+ . lineLimit ( 1 )
129
+ . font ( . footnote)
130
+ . foregroundStyle ( . secondary)
131
+ }
132
+ }
133
+
134
+ // MARK: - Helpers
135
+
136
+ private var contentFont : Font {
137
+ let baseSize = CGFloat ( settings. displayOptions. contentFontSize)
138
+ return Font . system ( size: baseSize * fontMultiplier)
139
+ }
140
+
141
+ private var detailsFont : Font {
142
+ let baseSize = CGFloat ( settings. displayOptions. detailsFontSize)
143
+ return Font . system ( size: baseSize * fontMultiplier) . monospacedDigit ( )
144
+ }
145
+
146
+ private func byteCount( for size: Int64 ) -> String {
147
+ guard size > 0 else { return " 0 KB " }
148
+ return ByteCountFormatter . string ( fromByteCount: size)
149
+ }
150
+ }
151
+
152
+ private struct StatusIndicatorView : View {
153
+ let state : NetworkTaskEntity . State ?
154
+
155
+ var body : some View {
156
+ Image ( systemName: " circle.fill " )
157
+ . foregroundStyle ( color)
158
+ . font ( . system( size: 9 ) )
159
+ . clipShape ( RoundedRectangle ( cornerRadius: 3 ) )
160
+ }
161
+
162
+ private var color : Color {
163
+ guard let state else {
164
+ return . secondary
165
+ }
166
+ switch state {
167
+ case . pending: return . orange
168
+ case . success: return . green
169
+ case . failure: return . red
170
+ }
171
+ }
172
+ }
173
+
174
+ struct ConsoleTimestampView : View {
175
+ let date : Date
176
+
177
+ var body : some View {
178
+ Text ( ConsoleMessageCell . timeFormatter. string ( from: date) )
179
+ . font ( . caption)
180
+ . monospacedDigit ( )
181
+ . tracking ( - 0.5 )
182
+ . foregroundStyle ( . secondary)
183
+ }
184
+ }
185
+
186
+ #else
187
+
10
188
@available ( iOS 15 , visionOS 1 . 0 , * )
11
189
struct ConsoleTaskCell : View {
12
190
@ObservedObject var task : NetworkTaskEntity
@@ -135,7 +313,7 @@ struct ConsoleTaskCell: View {
135
313
Text ( task. httpMethod ?? " GET " )
136
314
case . requestSize:
137
315
makeInfoText ( " arrow.up " , byteCount ( for: task. requestBodySize) )
138
- case . responseSize:
316
+ case . responseSize:
139
317
makeInfoText ( " arrow.down " , byteCount ( for: task. responseBodySize) )
140
318
case . responseContentType:
141
319
task. responseContentType. map ( NetworkLogger . ContentType. init) . map {
@@ -211,6 +389,8 @@ private let titleSpacing: CGFloat = 20
211
389
private let titleSpacing : CGFloat ? = nil
212
390
#endif
213
391
392
+ #endif
393
+
214
394
@available ( iOS 15 , visionOS 1 . 0 , * )
215
395
struct MockBadgeView : View {
216
396
var body : some View {
@@ -248,3 +428,4 @@ struct ConsoleTaskCell_Previews: PreviewProvider {
248
428
}
249
429
}
250
430
#endif
431
+
0 commit comments