Skip to content

Commit aa6ffc9

Browse files
author
Ivo Bellin Salarin
committed
fix: UI to fix stuck recordings
1 parent 60a2a48 commit aa6ffc9

File tree

3 files changed

+181
-27
lines changed

3 files changed

+181
-27
lines changed

Recap/UseCases/Summary/SummaryView.swift

Lines changed: 136 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

Recap/UseCases/Summary/ViewModel/SummaryViewModel.swift

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,13 @@ final class SummaryViewModel: SummaryViewModelType {
108108

109109
func retryProcessing() async {
110110
guard let recording = currentRecording else { return }
111-
111+
112112
if recording.state == .transcriptionFailed {
113113
await processingCoordinator.retryProcessing(recordingID: recording.id)
114114
} else {
115115
do {
116116
try await recordingRepository.updateRecordingState(
117-
id: recording.id,
117+
id: recording.id,
118118
state: .summarizing,
119119
errorMessage: nil
120120
)
@@ -126,6 +126,47 @@ final class SummaryViewModel: SummaryViewModelType {
126126

127127
loadRecording(withID: recording.id)
128128
}
129+
130+
func fixStuckRecording() async {
131+
guard let recording = currentRecording else { return }
132+
133+
do {
134+
// Fix stuck recording by transitioning to .recorded state
135+
try await recordingRepository.updateRecordingState(
136+
id: recording.id,
137+
state: .recorded,
138+
errorMessage: nil
139+
)
140+
141+
// Reload the recording to reflect the change
142+
loadRecording(withID: recording.id)
143+
144+
// Trigger processing
145+
if let updatedRecording = try await recordingRepository.fetchRecording(id: recording.id) {
146+
await processingCoordinator.startProcessing(recordingInfo: updatedRecording)
147+
}
148+
} catch {
149+
errorMessage = "Failed to fix recording state: \(error.localizedDescription)"
150+
}
151+
}
152+
153+
func markAsCompleted() async {
154+
guard let recording = currentRecording else { return }
155+
156+
do {
157+
// Mark recording as completed without processing
158+
try await recordingRepository.updateRecordingState(
159+
id: recording.id,
160+
state: .completed,
161+
errorMessage: nil
162+
)
163+
164+
// Reload the recording to reflect the change
165+
loadRecording(withID: recording.id)
166+
} catch {
167+
errorMessage = "Failed to mark recording as completed: \(error.localizedDescription)"
168+
}
169+
}
129170

130171
func startAutoRefresh() {
131172
stopAutoRefresh()

Recap/UseCases/Summary/ViewModel/SummaryViewModelType.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ protocol SummaryViewModelType: ObservableObject {
1414
func loadRecording(withID recordingID: String)
1515
func loadLatestRecording()
1616
func retryProcessing() async
17+
func fixStuckRecording() async
18+
func markAsCompleted() async
1719
func startAutoRefresh()
1820
func stopAutoRefresh()
1921
func copySummary()

0 commit comments

Comments
 (0)