@@ -5,7 +5,7 @@ struct SummaryView<ViewModel: SummaryViewModelType>: View {
55 let onClose : ( ) -> Void
66 @ObservedObject var viewModel : ViewModel
77 let recordingID : String ?
8-
8+
99 init (
1010 onClose: @escaping ( ) -> Void ,
1111 viewModel: ViewModel ,
@@ -15,16 +15,16 @@ struct SummaryView<ViewModel: SummaryViewModelType>: View {
1515 self . viewModel = viewModel
1616 self . recordingID = recordingID
1717 }
18-
18+
1919 var body : some View {
2020 GeometryReader { geometry in
2121 ZStack {
2222 UIConstants . Gradients. backgroundGradient
2323 . ignoresSafeArea ( )
24-
24+
2525 VStack ( spacing: UIConstants . Spacing. sectionSpacing) {
2626 headerView
27-
27+
2828 if viewModel. isLoadingRecording {
2929 loadingView
3030 } else if let errorMessage = viewModel. errorMessage {
@@ -35,10 +35,12 @@ struct SummaryView<ViewModel: SummaryViewModelType>: View {
3535 processingView ( geometry: geometry)
3636 } else if viewModel. isRecordingReady {
3737 summaryView
38+ } else if let recording = viewModel. currentRecording {
39+ stuckRecordingView ( recording)
3840 } else {
39- errorView ( viewModel . currentRecording ? . errorMessage ?? " Recording is in an unexpected state " )
41+ errorView ( " Recording is in an unexpected state " )
4042 }
41-
43+
4244 Spacer ( )
4345 }
4446 }
@@ -65,48 +67,48 @@ struct SummaryView<ViewModel: SummaryViewModelType>: View {
6567 )
6668 }
6769 }
68-
70+
6971 private var headerView : some View {
7072 HStack {
7173 Text ( " Summary " )
7274 . foregroundColor ( UIConstants . Colors. textPrimary)
7375 . font ( UIConstants . Typography. appTitle)
7476 . padding ( . leading, UIConstants . Spacing. contentPadding)
7577 . padding ( . top, UIConstants . Spacing. sectionSpacing)
76-
78+
7779 Spacer ( )
78-
80+
7981 closeButton
8082 . padding ( . trailing, UIConstants . Spacing. contentPadding)
8183 . padding ( . top, UIConstants . Spacing. sectionSpacing)
8284 }
8385 }
84-
86+
8587 private var closeButton : some View {
8688 PillButton ( text: " Close " , icon: " xmark " ) {
8789 onClose ( )
8890 }
8991 }
90-
92+
9193 private var loadingView : some View {
9294 VStack ( spacing: 16 ) {
9395 ProgressView ( )
9496 . progressViewStyle ( CircularProgressViewStyle ( ) )
9597 . scaleEffect ( 1.5 )
96-
98+
9799 Text ( " Loading recording... " )
98100 . font ( UIConstants . Typography. bodyText)
99101 . foregroundColor ( UIConstants . Colors. textSecondary)
100102 }
101103 . frame ( maxHeight: . infinity)
102104 }
103-
105+
104106 private func errorView( _ message: String ) -> some View {
105107 VStack ( spacing: 16 ) {
106108 Image ( systemName: " exclamationmark.triangle " )
107109 . font ( . system( size: 48 ) )
108110 . foregroundColor ( . red. opacity ( 0.8 ) )
109-
111+
110112 Text ( message)
111113 . font ( . system( size: 14 ) )
112114 . foregroundColor ( UIConstants . Colors. textSecondary)
@@ -115,20 +117,43 @@ struct SummaryView<ViewModel: SummaryViewModelType>: View {
115117 }
116118 . frame ( maxHeight: . infinity)
117119 }
118-
120+
121+ private func stuckRecordingView( _ recording: RecordingInfo ) -> some View {
122+ VStack ( spacing: 20 ) {
123+ recordingStateInfo ( recording)
124+ . padding ( . horizontal, UIConstants . Spacing. contentPadding)
125+
126+ if let errorMessage = recording. errorMessage {
127+ VStack ( spacing: 12 ) {
128+ Image ( systemName: " exclamationmark.triangle " )
129+ . font ( . system( size: 48 ) )
130+ . foregroundColor ( . red. opacity ( 0.8 ) )
131+
132+ Text ( errorMessage)
133+ . font ( . system( size: 14 ) )
134+ . foregroundColor ( UIConstants . Colors. textSecondary)
135+ . multilineTextAlignment ( . center)
136+ . padding ( . horizontal, UIConstants . Spacing. contentPadding)
137+ }
138+ }
139+ }
140+ . frame ( maxHeight: . infinity, alignment: . top)
141+ . padding ( . top, 20 )
142+ }
143+
119144 private var noRecordingView : some View {
120145 VStack ( spacing: 16 ) {
121146 Image ( systemName: " mic.slash " )
122147 . font ( . system( size: 48 ) )
123148 . foregroundColor ( UIConstants . Colors. textTertiary)
124-
149+
125150 Text ( " No recordings found " )
126151 . font ( . system( size: 14 ) )
127152 . foregroundColor ( UIConstants . Colors. textSecondary)
128153 }
129154 . frame ( maxHeight: . infinity)
130155 }
131-
156+
132157 private func processingView( geometry: GeometryProxy ) -> some View {
133158 VStack ( spacing: UIConstants . Spacing. sectionSpacing) {
134159 if let stage = viewModel. processingStage {
@@ -138,18 +163,20 @@ struct SummaryView<ViewModel: SummaryViewModelType>: View {
138163 )
139164 . padding ( . horizontal, UIConstants . Spacing. contentPadding)
140165 }
141-
166+
142167 Spacer ( )
143168 }
144169 }
145-
170+
146171 private var summaryView : some View {
147172 VStack ( spacing: 0 ) {
148173 ScrollView {
149174 VStack ( alignment: . leading, spacing: UIConstants . Spacing. cardSpacing) {
150175 if let recording = viewModel. currentRecording {
151176
152177 VStack ( alignment: . leading, spacing: UIConstants . Spacing. cardInternalSpacing) {
178+ recordingStateInfo ( recording)
179+
153180 if let transcriptionText = recording. transcriptionText, !transcriptionText. isEmpty {
154181 TranscriptDropdownButton (
155182 transcriptText: transcriptionText
@@ -216,11 +243,11 @@ struct SummaryView<ViewModel: SummaryViewModelType>: View {
216243 }
217244 }
218245 }
219-
246+
220247 summaryActionButtons
221248 }
222249 }
223-
250+
224251 private var summaryActionButtons : some View {
225252 VStack ( spacing: 0 ) {
226253 HStack ( spacing: 12 ) {
@@ -230,14 +257,14 @@ struct SummaryView<ViewModel: SummaryViewModelType>: View {
230257 ) {
231258 viewModel. copySummary ( )
232259 }
233-
260+
234261 SummaryActionButton (
235262 text: " Copy Transcription " ,
236263 icon: " doc.text "
237264 ) {
238265 viewModel. copyTranscription ( )
239266 }
240-
267+
241268 SummaryActionButton (
242269 text: retryButtonText,
243270 icon: " arrow.clockwise "
@@ -254,15 +281,99 @@ struct SummaryView<ViewModel: SummaryViewModelType>: View {
254281 . background ( UIConstants . Gradients. summaryButtonBackground)
255282 . cornerRadius ( UIConstants . Sizing. cornerRadius)
256283 }
257-
284+
258285 private var retryButtonText : String {
259286 guard let recording = viewModel. currentRecording else { return " Retry Summarization " }
260-
287+
261288 switch recording. state {
262289 case . transcriptionFailed:
263290 return " Retry "
264291 default :
265292 return " Retry Summarization "
266293 }
267294 }
295+
296+ private func recordingStateInfo( _ recording: RecordingInfo ) -> some View {
297+ VStack ( alignment: . leading, spacing: 12 ) {
298+ HStack {
299+ Text ( " Recording State: " )
300+ . font ( UIConstants . Typography. bodyText)
301+ . foregroundColor ( UIConstants . Colors. textSecondary)
302+
303+ Text ( recording. state. displayName)
304+ . font ( UIConstants . Typography. bodyText. weight ( . semibold) )
305+ . foregroundColor ( stateColor ( for: recording. state) )
306+ }
307+
308+ if recording. state == . recording || recording. state == . recorded || recording. state. isFailed {
309+ VStack ( alignment: . leading, spacing: 8 ) {
310+ if recording. state == . recording {
311+ Text ( " This recording is stuck in 'Recording' state. " )
312+ . font ( . caption)
313+ . foregroundColor ( . orange)
314+ } else if recording. state. isFailed {
315+ Text ( " This recording has failed processing. " )
316+ . font ( . caption)
317+ . foregroundColor ( . red)
318+ }
319+
320+ HStack ( spacing: 8 ) {
321+ Button ( action: {
322+ Task {
323+ await viewModel. fixStuckRecording ( )
324+ }
325+ } ) {
326+ HStack ( spacing: 6 ) {
327+ Image ( systemName: " wrench.and.screwdriver " )
328+ Text ( " Fix & Process " )
329+ }
330+ . font ( . caption. weight ( . medium) )
331+ . foregroundColor ( . white)
332+ . padding ( . horizontal, 12 )
333+ . padding ( . vertical, 6 )
334+ . background ( Color . orange)
335+ . cornerRadius ( 6 )
336+ }
337+ . buttonStyle ( . plain)
338+
339+ Button ( action: {
340+ Task {
341+ await viewModel. markAsCompleted ( )
342+ }
343+ } ) {
344+ HStack ( spacing: 6 ) {
345+ Image ( systemName: " checkmark.circle " )
346+ Text ( " Mark Completed " )
347+ }
348+ . font ( . caption. weight ( . medium) )
349+ . foregroundColor ( . white)
350+ . padding ( . horizontal, 12 )
351+ . padding ( . vertical, 6 )
352+ . background ( Color . green. opacity ( 0.8 ) )
353+ . cornerRadius ( 6 )
354+ }
355+ . buttonStyle ( . plain)
356+ }
357+ }
358+ }
359+ }
360+ . padding ( 12 )
361+ . background ( Color ( hex: " 242323 " ) . opacity ( 0.3 ) )
362+ . cornerRadius ( 8 )
363+ }
364+
365+ private func stateColor( for state: RecordingProcessingState ) -> Color {
366+ switch state {
367+ case . completed:
368+ return UIConstants . Colors. audioGreen
369+ case . transcriptionFailed, . summarizationFailed:
370+ return . red
371+ case . transcribing, . summarizing:
372+ return . orange
373+ case . recording:
374+ return . yellow
375+ default :
376+ return UIConstants . Colors. textTertiary
377+ }
378+ }
268379}
0 commit comments