diff --git a/Textream/Textream/BrowserServer.swift b/Textream/Textream/BrowserServer.swift index c58f481..9b211fe 100644 --- a/Textream/Textream/BrowserServer.swift +++ b/Textream/Textream/BrowserServer.swift @@ -187,21 +187,28 @@ class BrowserServer { let charCount: Int let mode = NotchSettings.shared.listeningMode + // Check if scroll already reached the end, to stop advancing the timer + let scrollDone = totalCharCount > 0 && charOffsetForWordProgress(timerWordProgress) >= totalCharCount switch mode { case .wordTracking: charCount = speechRecognizer?.recognizedCharCount ?? 0 case .classic: - timerWordProgress += NotchSettings.shared.scrollSpeed * 0.1 + if !scrollDone { + timerWordProgress += NotchSettings.shared.scrollSpeed * 0.1 + } charCount = charOffsetForWordProgress(timerWordProgress) case .silencePaused: - if speechRecognizer?.isListening == true && (speechRecognizer?.isSpeaking ?? false) { + if !scrollDone && speechRecognizer?.isListening == true && (speechRecognizer?.isSpeaking ?? false) { timerWordProgress += NotchSettings.shared.scrollSpeed * 0.1 } charCount = charOffsetForWordProgress(timerWordProgress) } let effective = min(charCount, totalCharCount) - let isDone = totalCharCount > 0 && effective >= totalCharCount + let rawDone = totalCharCount > 0 && effective >= totalCharCount + // In classic/silence-paused modes on the last page, suppress Done so the + // browser keeps showing the prompter text (speaker may still be talking). + let isDone = rawDone && (mode == .wordTracking || hasNextPage) let highlightWords = mode == .wordTracking diff --git a/Textream/Textream/ExternalDisplayController.swift b/Textream/Textream/ExternalDisplayController.swift index ac2b272..f239e63 100644 --- a/Textream/Textream/ExternalDisplayController.swift +++ b/Textream/Textream/ExternalDisplayController.swift @@ -176,7 +176,7 @@ struct ExternalDisplayView: View { ZStack { Color.black.ignoresSafeArea() - if isDone { + if isDone && (listeningMode == .wordTracking || hasNextPage) { doneView } else { prompterView @@ -192,7 +192,7 @@ struct ExternalDisplayView: View { .scaleEffect(x: mirrorAxis?.scaleX ?? 1, y: mirrorAxis?.scaleY ?? 1) .animation(.easeInOut(duration: 0.5), value: isDone) .onChange(of: isDone) { _, done in - if done { + if done && listeningMode == .wordTracking { speechRecognizer.stop() } } diff --git a/Textream/Textream/NotchOverlayController.swift b/Textream/Textream/NotchOverlayController.swift index 330d202..c4f5605 100644 --- a/Textream/Textream/NotchOverlayController.swift +++ b/Textream/Textream/NotchOverlayController.swift @@ -721,7 +721,7 @@ struct NotchOverlayView: View { if content.showPagePicker { pagePickerView - } else if isDone { + } else if isDone && (listeningMode == .wordTracking || hasNextPage) { doneView } else { prompterView @@ -765,12 +765,19 @@ struct NotchOverlayView: View { .animation(.easeInOut(duration: 0.5), value: isDone) .onChange(of: isDone) { _, done in if done { - // Stop listening when page is done - speechRecognizer.stop() + // In word tracking mode, stop listening when page is done + if listeningMode == .wordTracking { + speechRecognizer.stop() + } if !hasNextPage { - // Show "Done" briefly, then auto-dismiss - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - speechRecognizer.shouldDismiss = true + // Only auto-dismiss in word tracking mode. + // In classic/silence-paused modes the speaker may still be + // talking after the auto-scroll finishes, so keep the text + // visible and let them dismiss manually (X button or Esc). + if listeningMode == .wordTracking { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + speechRecognizer.shouldDismiss = true + } } } else if NotchSettings.shared.autoNextPage { startCountdown() @@ -1213,7 +1220,7 @@ struct FloatingOverlayView: View { VStack(spacing: 0) { if content.showPagePicker { floatingPagePickerView - } else if isDone { + } else if isDone && (listeningMode == .wordTracking || hasNextPage) { floatingDoneView } else { floatingPrompterView @@ -1260,11 +1267,14 @@ struct FloatingOverlayView: View { .animation(.easeInOut(duration: 0.5), value: isDone) .onChange(of: isDone) { _, done in if done { - // Stop listening when page is done - speechRecognizer.stop() + if listeningMode == .wordTracking { + speechRecognizer.stop() + } if !hasNextPage { - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - speechRecognizer.shouldDismiss = true + if listeningMode == .wordTracking { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + speechRecognizer.shouldDismiss = true + } } } else if followingCursor || NotchSettings.shared.autoNextPage { startCountdown()