fix(ui): resolve build errors in MainScreen
This commit is contained in:
parent
c22a20b2c8
commit
3bcbda4d27
app/src/main/java/com/voca/app
@ -381,45 +381,37 @@ fun MainScreen(
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// --- Conditionally Render Controls ---
|
||||
// --- Use Unified Controls ---
|
||||
UnifiedPlaybackControls(
|
||||
ttsState = ttsState,
|
||||
playbackState = playbackState,
|
||||
isContentAvailable = isDocumentLoaded || hasUserInput,
|
||||
isMp3Mode = isMp3FlowActive, // Determine if MP3 mode is active
|
||||
onTogglePlayback = { viewModel.onAction(MainAction.TogglePlayback) }, // Pass the new action
|
||||
onStopPlayback = { viewModel.onAction(MainAction.StopPlayback) } // Pass the new action
|
||||
)
|
||||
// --- End Unified Controls ---
|
||||
|
||||
// --- MP3 Generation/Save/Seek UI (Keep separate for now) ---
|
||||
if (isMp3FlowActive) {
|
||||
// MP3 Player Controls - Only show if MP3 flow is active
|
||||
Timber.d("Rendering MP3 Controls because isMp3FlowActive = true")
|
||||
Mp3PlayerControls(
|
||||
playbackState = playbackState,
|
||||
mp3GenerationState = mp3GenerationState,
|
||||
mp3SaveState = mp3SaveState,
|
||||
currentMp3UriFromViewModel = currentMp3Uri,
|
||||
// Enable generation button only if there's content and MP3 isn't already active/generating
|
||||
generationEnabled = (isDocumentLoaded || hasUserInput) && !(playbackState.currentUri != null || mp3GenerationState is Mp3GenerationProgress.InProgress),
|
||||
onPlay = { (viewModel::onAction)(MainAction.PlayMp3) },
|
||||
onPause = { (viewModel::onAction)(MainAction.PauseMp3) },
|
||||
onResume = { (viewModel::onAction)(MainAction.ResumeMp3) },
|
||||
onStop = { (viewModel::onAction)(MainAction.StopMp3) },
|
||||
onCancel = { (viewModel::onAction)(MainAction.CancelMp3Generation) },
|
||||
onSeek = { positionMs -> (viewModel::onAction)(MainAction.SeekMp3(positionMs)) },
|
||||
onGenerateMp3 = { (viewModel::onAction)(MainAction.GenerateMp3) }
|
||||
)
|
||||
} else {
|
||||
// TTS Playback Controls - Only show if MP3 flow is NOT active
|
||||
Timber.d("Rendering TTS Controls because isMp3FlowActive = false")
|
||||
TtsControls(
|
||||
ttsState = ttsState,
|
||||
// Enable TTS controls only if there is content available
|
||||
enabled = isDocumentLoaded || hasUserInput,
|
||||
onPlayPauseToggle = {
|
||||
if (ttsState.isSpeaking && !ttsState.isPaused) {
|
||||
(viewModel::onAction)(MainAction.PauseSpeech)
|
||||
} else if (ttsState.isPaused) {
|
||||
(viewModel::onAction)(MainAction.ResumeSpeech)
|
||||
} else {
|
||||
(viewModel::onAction)(MainAction.PlayText) // ViewModel will decide source
|
||||
}
|
||||
},
|
||||
onStop = { (viewModel::onAction)(MainAction.StopSpeaking) }
|
||||
)
|
||||
Mp3GenerationAndSeekControls(
|
||||
playbackState = playbackState,
|
||||
mp3GenerationState = mp3GenerationState,
|
||||
mp3SaveState = mp3SaveState,
|
||||
currentMp3UriFromViewModel = currentMp3Uri,
|
||||
onCancel = { (viewModel::onAction)(MainAction.CancelMp3Generation) },
|
||||
onSeek = { positionMs -> (viewModel::onAction)(MainAction.SeekMp3(positionMs)) },
|
||||
onGenerateMp3 = { (viewModel::onAction)(MainAction.GenerateMp3) }
|
||||
)
|
||||
} else if (isDocumentLoaded || hasUserInput) { // Use the same condition used for `generationEnabled` previously
|
||||
Button(
|
||||
onClick = { viewModel.onAction(MainAction.GenerateMp3) },
|
||||
modifier = Modifier.padding(top = 8.dp) // Add some padding
|
||||
) {
|
||||
Text("Create MP3")
|
||||
}
|
||||
}
|
||||
// ---
|
||||
// --- End MP3 Generation/Save/Seek UI ---
|
||||
|
||||
// Optional: Show subtle indicator if background audio is playing (Consider removing if controls are clear)
|
||||
/*
|
||||
@ -438,38 +430,58 @@ fun MainScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// Simple composable for TTS control buttons
|
||||
// --- NEW Unified Playback Controls Composable ---
|
||||
@Composable
|
||||
fun TtsControls(
|
||||
fun UnifiedPlaybackControls(
|
||||
ttsState: TTSState,
|
||||
enabled: Boolean,
|
||||
onPlayPauseToggle: () -> Unit,
|
||||
onStop: () -> Unit
|
||||
playbackState: PlaybackState,
|
||||
isContentAvailable: Boolean,
|
||||
isMp3Mode: Boolean, // True if MP3 is loaded/active, false for TTS
|
||||
onTogglePlayback: () -> Unit,
|
||||
onStopPlayback: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// Determine Play/Pause state based on mode
|
||||
val isPlaying = if (isMp3Mode) playbackState.isPlaying else ttsState.isSpeaking && !ttsState.isPaused
|
||||
val isPaused = if (isMp3Mode) playbackState.isPaused else ttsState.isPaused
|
||||
val canPlayback = isContentAvailable || (isMp3Mode && playbackState.currentUri != null) // Enable if content or MP3 URI exists
|
||||
|
||||
// Play/Pause Button
|
||||
IconButton(onClick = onPlayPauseToggle, enabled = enabled) {
|
||||
IconButton(onClick = onTogglePlayback, enabled = canPlayback) {
|
||||
Icon(
|
||||
imageVector = if (ttsState.isSpeaking && !ttsState.isPaused) Icons.Filled.Pause else Icons.Filled.PlayArrow,
|
||||
contentDescription = if (ttsState.isSpeaking && !ttsState.isPaused) "Pause Speech" else "Play Speech",
|
||||
imageVector = if (isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
|
||||
contentDescription = when {
|
||||
isPlaying -> "Pause"
|
||||
isPaused -> "Resume"
|
||||
else -> "Play"
|
||||
},
|
||||
modifier = Modifier.size(48.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// Stop Button (Enabled only if speaking or paused)
|
||||
IconButton(onClick = onStop, enabled = ttsState.isSpeaking || ttsState.isPaused) {
|
||||
// Stop Button (Enabled if either TTS or MP3 is playing or paused)
|
||||
val canStop = (isMp3Mode && (playbackState.isPlaying || playbackState.isPaused)) ||
|
||||
(!isMp3Mode && (ttsState.isSpeaking || ttsState.isPaused))
|
||||
IconButton(onClick = onStopPlayback, enabled = canStop) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Stop,
|
||||
contentDescription = "Stop Speech",
|
||||
contentDescription = "Stop",
|
||||
modifier = Modifier.size(48.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
// --- END Unified Playback Controls Composable ---
|
||||
|
||||
// Simple composable for TTS control buttons - REMOVED
|
||||
/*
|
||||
@Composable
|
||||
fun TtsControls(...) { ... }
|
||||
*/
|
||||
|
||||
// --- Preview Data Structure ---
|
||||
// Simple data class to hold state for preview purposes, avoiding complex ViewModel mocking
|
||||
@ -546,17 +558,14 @@ fun MainScreenPreviewDark() {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
TtsControls(
|
||||
enabled = true,
|
||||
ttsState = ttsState,
|
||||
onPlayPauseToggle = {
|
||||
if (ttsState.isSpeaking && !ttsState.isPaused) {
|
||||
state.onAction(MainAction.PauseSpeech)
|
||||
} else {
|
||||
state.onAction(MainAction.PlayText)
|
||||
}
|
||||
},
|
||||
onStop = { state.onAction(MainAction.StopSpeaking) }
|
||||
// Use UnifiedPlaybackControls in Preview
|
||||
UnifiedPlaybackControls(
|
||||
ttsState = ttsState, // Pass dummy state
|
||||
playbackState = PlaybackState(), // Pass dummy state
|
||||
isContentAvailable = text.isNotEmpty(),
|
||||
isMp3Mode = false, // Assume TTS mode for preview
|
||||
onTogglePlayback = { state.onAction(MainAction.TogglePlayback) }, // Use new action
|
||||
onStopPlayback = { state.onAction(MainAction.StopPlayback) } // Use new action
|
||||
)
|
||||
|
||||
when(genState) {
|
||||
@ -776,15 +785,6 @@ fun Mp3PlayerControls(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats milliseconds duration into MM:SS format.
|
||||
*/
|
||||
private fun formatDuration(millis: Long): String {
|
||||
val minutes = TimeUnit.MILLISECONDS.toMinutes(millis)
|
||||
val seconds = TimeUnit.MILLISECONDS.toSeconds(millis) % 60
|
||||
return String.format("%02d:%02d", minutes, seconds)
|
||||
}
|
||||
|
||||
/**
|
||||
* New Composable for displaying the full text with sentence highlighting.
|
||||
*/
|
||||
@ -936,4 +936,142 @@ fun FullTextDisplayArea(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- NEW Composable for MP3 Generation and Seek Controls ---
|
||||
@Composable
|
||||
fun Mp3GenerationAndSeekControls(
|
||||
playbackState: PlaybackState,
|
||||
mp3GenerationState: Mp3GenerationProgress?,
|
||||
mp3SaveState: FileSaveProgress?,
|
||||
currentMp3UriFromViewModel: Uri?, // Keep for context, might be redundant
|
||||
onCancel: () -> Unit,
|
||||
onSeek: (Long) -> Unit,
|
||||
onGenerateMp3: () -> Unit // Can likely remove this if generate button is outside
|
||||
) {
|
||||
val isGenerating = mp3GenerationState is Mp3GenerationProgress.InProgress
|
||||
val isSaving = mp3SaveState is FileSaveProgress.InProgress
|
||||
val isProcessing = isGenerating || isSaving // Combine generation and saving flags
|
||||
|
||||
// Ready ONLY if NOT processing AND (saving succeeded OR playback service has a URI loaded)
|
||||
val isMp3Ready = !isProcessing &&
|
||||
(mp3SaveState is FileSaveProgress.Success || playbackState.currentUri != null)
|
||||
|
||||
val hasError = !isProcessing && ( // Only show errors *after* processing finishes
|
||||
playbackState.error != null ||
|
||||
mp3GenerationState is Mp3GenerationProgress.Error ||
|
||||
mp3SaveState is FileSaveProgress.Error
|
||||
)
|
||||
val isLoading = !isProcessing && !isMp3Ready && !hasError && playbackState.isLoading
|
||||
|
||||
// Determine overall visibility: Show if processing, ready, loading, or has error
|
||||
val shouldShowSection = isProcessing || isMp3Ready || isLoading || hasError
|
||||
|
||||
if (shouldShowSection) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp, horizontal = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// --- Show Progress Indicator Section (Highest Priority) ---
|
||||
if (isProcessing) {
|
||||
val progress: Float = when {
|
||||
isGenerating -> (mp3GenerationState as Mp3GenerationProgress.InProgress).progress
|
||||
isSaving -> 0.5f // Indicate saving is happening, maybe use indeterminate later
|
||||
else -> 0f
|
||||
}
|
||||
val progressText: String = when {
|
||||
isGenerating -> "Creating MP3..."
|
||||
isSaving -> "Saving MP3..."
|
||||
else -> "Processing..."
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(progressText, style = MaterialTheme.typography.bodyMedium)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
LinearProgressIndicator(
|
||||
progress = { progress }, // Use calculated progress
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(8.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
trackColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
)
|
||||
// Add an indeterminate bar if progress is low or saving
|
||||
if (progress <= 0.01f || isSaving) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
LinearProgressIndicator(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
color = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
// Show cancel button only during generation, not saving
|
||||
if (isGenerating) {
|
||||
Button(
|
||||
onClick = onCancel,
|
||||
enabled = mp3GenerationState is Mp3GenerationProgress.InProgress,
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error)
|
||||
) {
|
||||
Text("X")
|
||||
}
|
||||
}
|
||||
}
|
||||
// --- Show Seek Bar Section (Only if Ready and has duration) ---
|
||||
} else if (isMp3Ready && playbackState.durationMs > 0) {
|
||||
// Seek Bar and Time Display
|
||||
Slider(
|
||||
value = playbackState.currentPositionMs.toFloat(),
|
||||
onValueChange = { onSeek(it.roundToLong()) },
|
||||
valueRange = 0f..playbackState.durationMs.toFloat(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(formatDuration(playbackState.currentPositionMs), style = MaterialTheme.typography.labelSmall)
|
||||
Text(formatDuration(playbackState.durationMs), style = MaterialTheme.typography.labelSmall)
|
||||
}
|
||||
|
||||
// --- Show Loading Indicator Section (Only if Loading) ---
|
||||
} else if (isLoading) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
CircularProgressIndicator(modifier = Modifier.size(24.dp))
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
Text("Loading MP3...", style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
// --- Show Error Section (Only if not processing, not ready, not loading, but has error) ---
|
||||
} else if (hasError) {
|
||||
val errorMsg = when {
|
||||
mp3GenerationState is Mp3GenerationProgress.Error -> "Error generating MP3: ${mp3GenerationState.message}"
|
||||
mp3SaveState is FileSaveProgress.Error -> "Error saving MP3: ${mp3SaveState.message}"
|
||||
playbackState.error != null -> "Error loading MP3: ${playbackState.error}"
|
||||
else -> "An unknown error occurred."
|
||||
}
|
||||
Text(
|
||||
errorMsg,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// --- END MP3 Generation and Seek Controls Composable ---
|
||||
|
||||
/**
|
||||
* Formats milliseconds duration into MM:SS format.
|
||||
*/
|
||||
private fun formatDuration(millis: Long): String {
|
||||
val minutes = TimeUnit.MILLISECONDS.toMinutes(millis)
|
||||
val seconds = TimeUnit.MILLISECONDS.toSeconds(millis) % 60
|
||||
return String.format("%02d:%02d", minutes, seconds)
|
||||
}
|
||||
|
||||
// ... existing code ...
|
@ -63,20 +63,31 @@ sealed interface MainAction {
|
||||
data class ProcessSelectedDocument(val uri: Uri) : MainAction
|
||||
data class ProcessUri(val uri: Uri) : MainAction
|
||||
data class ProcessSharedText(val text: String) : MainAction
|
||||
data object PlayText : MainAction // Plays current page via TTS
|
||||
data object PauseSpeech : MainAction
|
||||
data object ResumeSpeech : MainAction
|
||||
data object StopSpeaking : MainAction
|
||||
data class SetTextAndPlay(val text: String) : MainAction // Set text and immediately play TTS
|
||||
data class SetTextAndPlay(val text: String) : MainAction // Set text and immediately play TTS - Keep for specific use cases
|
||||
|
||||
// --- NEW Unified Playback Actions ---
|
||||
data object TogglePlayback : MainAction // Unified Play/Pause/Resume
|
||||
data object StopPlayback : MainAction // Unified Stop
|
||||
// --- END Unified Actions ---
|
||||
|
||||
// --- REMOVED Specific Playback Actions ---
|
||||
// data object PlayText : MainAction
|
||||
// data object PauseSpeech : MainAction
|
||||
// data object ResumeSpeech : MainAction
|
||||
// data object StopSpeaking : MainAction
|
||||
// data object PlayMp3 : MainAction
|
||||
// data object PauseMp3 : MainAction
|
||||
// data object ResumeMp3 : MainAction
|
||||
// data object StopMp3 : MainAction
|
||||
// --- END REMOVED ---
|
||||
|
||||
data object GenerateMp3 : MainAction
|
||||
data object CancelMp3Generation : MainAction // New action to cancel MP3 generation
|
||||
data class SaveMp3(val desiredFileName: String? = null) : MainAction // Optional desired filename
|
||||
data object PlayMp3 : MainAction // Plays the last generated/loaded MP3
|
||||
data object PauseMp3 : MainAction
|
||||
data object ResumeMp3 : MainAction
|
||||
data object StopMp3 : MainAction
|
||||
// Change parameter type to Long
|
||||
|
||||
// Keep Seek action for MP3
|
||||
data class SeekMp3(val positionMs: Long) : MainAction
|
||||
|
||||
data object NextPage : MainAction
|
||||
data object PreviousPage : MainAction
|
||||
data object TriggerDocumentPicker : MainAction
|
||||
@ -332,18 +343,16 @@ class MainViewModel(
|
||||
is MainAction.ProcessUri -> handleLoadDocumentAndInitiateAudioProcessing(action.uri)
|
||||
is MainAction.ProcessSharedText -> handleUpdateText(action.text)
|
||||
is MainAction.UpdateText -> handleUpdateText(action.text)
|
||||
is MainAction.SetTextAndPlay -> handleSetTextAndPlay(action.text) // Processes text then speaks
|
||||
is MainAction.SetTextAndPlay -> handleSetTextAndPlay(action.text)
|
||||
is MainAction.UpdateInputText -> {
|
||||
_userInputText.value = action.text
|
||||
}
|
||||
is MainAction.TriggerDocumentPicker -> handleTriggerDocumentPicker()
|
||||
|
||||
// TTS Actions
|
||||
MainAction.PlayText -> handlePlayText() // Plays current page
|
||||
MainAction.PauseSpeech -> handlePauseSpeech()
|
||||
MainAction.ResumeSpeech -> handleResumeSpeech()
|
||||
MainAction.StopSpeaking -> handleStopSpeaking()
|
||||
// MainAction.ToggleTTS -> handleToggleTTS() // Removed as separate Pause/Resume/Play are preferred
|
||||
// --- UPDATED Playback Actions ---
|
||||
MainAction.TogglePlayback -> handleTogglePlayback()
|
||||
MainAction.StopPlayback -> handleStopPlayback()
|
||||
// --- END UPDATED ---
|
||||
|
||||
// MP3 Generation & Saving (Now combined or manual)
|
||||
MainAction.GenerateMp3 -> handleGenerateAndSaveMp3() // Manual trigger for combined process
|
||||
@ -351,10 +360,6 @@ class MainViewModel(
|
||||
is MainAction.SaveMp3 -> Unit // Explicitly ignore if saving is purely automatic now
|
||||
|
||||
// MP3 Playback
|
||||
MainAction.PlayMp3 -> handlePlayMp3()
|
||||
MainAction.PauseMp3 -> handlePauseMp3()
|
||||
MainAction.ResumeMp3 -> handleResumeMp3()
|
||||
MainAction.StopMp3 -> handleStopMp3()
|
||||
is MainAction.SeekMp3 -> handleSeekMp3(action.positionMs)
|
||||
|
||||
// Pagination
|
||||
@ -519,41 +524,54 @@ class MainViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePlayText() {
|
||||
Timber.d("handlePlayText called.")
|
||||
// --- NEW Handler for Unified Playback Toggle ---
|
||||
private fun handleTogglePlayback() {
|
||||
Timber.d("handleTogglePlayback called.")
|
||||
viewModelScope.launch {
|
||||
// --- ADD CHECK: Do not start TTS if an MP3 is loaded ---
|
||||
if (_currentMp3Uri.value != null) {
|
||||
Timber.w("handlePlayText: MP3 URI is loaded ($_currentMp3Uri.value). Skipping TTS playback.")
|
||||
return@launch
|
||||
}
|
||||
// --- END CHECK ---
|
||||
val mp3Uri = _currentMp3Uri.value
|
||||
val currentPlaybackState = playbackState.value
|
||||
val currentTtsState = ttsStateFlow.value
|
||||
|
||||
val textToSpeak = getCurrentDocumentStateUseCase.currentPageText.value
|
||||
if (!textToSpeak.isNullOrBlank()) {
|
||||
Timber.d("Speaking current page text: '${textToSpeak.take(50)}...'")
|
||||
speakTextUseCase(textToSpeak)
|
||||
if (mp3Uri != null) {
|
||||
// --- MP3 Mode ---
|
||||
Timber.d("Toggle playback in MP3 mode. State: $currentPlaybackState")
|
||||
when {
|
||||
currentPlaybackState.isPlaying -> pauseMp3UseCase()
|
||||
currentPlaybackState.isPaused -> resumeMp3UseCase()
|
||||
else -> playMp3UseCase(mp3Uri) // Start playback if idle
|
||||
}
|
||||
} else {
|
||||
Timber.w("PlayText action called but no text available (document or input).")
|
||||
// --- TTS Mode ---
|
||||
Timber.d("Toggle playback in TTS mode. State: $currentTtsState")
|
||||
val textToSpeak = getCurrentDocumentStateUseCase.currentPageText.value
|
||||
if (!textToSpeak.isNullOrBlank()) {
|
||||
when {
|
||||
currentTtsState.isSpeaking && !currentTtsState.isPaused -> pauseTTSUseCase()
|
||||
currentTtsState.isPaused -> resumeTTSUseCase()
|
||||
else -> speakTextUseCase(textToSpeak) // Start TTS if idle
|
||||
}
|
||||
} else {
|
||||
Timber.w("TogglePlayback (TTS): No text available.")
|
||||
// Optionally emit an error or show a message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// --- END Handler ---
|
||||
|
||||
private fun handlePauseSpeech() {
|
||||
Timber.d("handlePauseSpeech called.")
|
||||
pauseTTSUseCase()
|
||||
}
|
||||
|
||||
private fun handleResumeSpeech() {
|
||||
Timber.d("handleResumeSpeech called.")
|
||||
resumeTTSUseCase()
|
||||
}
|
||||
|
||||
private fun handleStopSpeaking() {
|
||||
Timber.d("handleStopSpeaking called.")
|
||||
stopTTSUseCase()
|
||||
// --- NEW Handler for Unified Stop ---
|
||||
private fun handleStopPlayback() {
|
||||
Timber.d("handleStopPlayback called.")
|
||||
viewModelScope.launch {
|
||||
// Stop both TTS and MP3 playback regardless of the current mode
|
||||
Timber.d("Stopping both TTS and MP3.")
|
||||
stopTTSUseCase()
|
||||
stopMp3UseCase()
|
||||
}
|
||||
}
|
||||
// --- END Handler ---
|
||||
|
||||
// --- ADDED BACK: Handler for SetTextAndPlay ---
|
||||
private fun handleSetTextAndPlay(text: String) {
|
||||
Timber.d("handleSetTextAndPlay called.")
|
||||
if (text.isBlank()) {
|
||||
@ -566,17 +584,20 @@ class MainViewModel(
|
||||
|
||||
resetStateForNewContent() // Reset state FIRST
|
||||
delay(50) // Small delay to allow state reset propagation if needed
|
||||
_mp3SaveProgress.value = null // Reset save state
|
||||
|
||||
_isLoading.value = true
|
||||
_processingStatus.value = PdfProcessingStatus.PROCESSING_TEXT
|
||||
try {
|
||||
processDocumentUseCase.processText(text)
|
||||
delay(50) // Keep delay for now, but ideally improve state propagation
|
||||
val updatedText = getCurrentDocumentStateUseCase.currentPageText.value
|
||||
if (!updatedText.isNullOrBlank()) {
|
||||
speakTextUseCase(updatedText)
|
||||
_processingStatus.value = PdfProcessingStatus.COMPLETED
|
||||
// Initiate TTS playback after processing
|
||||
speakTextUseCase(updatedText)
|
||||
_processingStatus.value = PdfProcessingStatus.COMPLETED // Mark as completed after starting TTS
|
||||
} else {
|
||||
Timber.w("handleSetTextAndPlay: Text set, but state not updated in time for TTS.")
|
||||
_processingStatus.value = PdfProcessingStatus.COMPLETED
|
||||
_processingStatus.value = PdfProcessingStatus.COMPLETED // Mark as completed even if TTS didn't start
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error in setTextAndPlay")
|
||||
@ -586,6 +607,7 @@ class MainViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
// --- END ADDED BACK ---
|
||||
|
||||
private fun handleGenerateAndSaveMp3() {
|
||||
Timber.d("handleGenerateAndSaveMp3 called. Adding debug checkpoints.")
|
||||
@ -818,60 +840,6 @@ class MainViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePlayMp3(mp3Uri: Uri? = null) {
|
||||
Timber.d("handlePlayMp3 called.")
|
||||
viewModelScope.launch {
|
||||
// Use the _currentMp3Uri state if no explicit URI is passed
|
||||
val uriToPlay = mp3Uri ?: _currentMp3Uri.value
|
||||
|
||||
if (uriToPlay != null) {
|
||||
try {
|
||||
Timber.d("Attempting to play MP3 from URI: $uriToPlay")
|
||||
Timber.i("--> [VM] handlePlayMp3: Calling playMp3UseCase for URI: $uriToPlay")
|
||||
// Call use case with the correct URI
|
||||
playMp3UseCase(uriToPlay)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error starting MP3 playback for URI: $uriToPlay")
|
||||
}
|
||||
} else {
|
||||
Timber.w("Play MP3 requested, but no URI available.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePauseMp3() {
|
||||
Timber.d("handlePauseMp3 called.")
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
pauseMp3UseCase()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error pausing MP3 playback")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleResumeMp3() {
|
||||
Timber.d("handleResumeMp3 called.")
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
resumeMp3UseCase()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error resuming MP3 playback")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleStopMp3() {
|
||||
Timber.d("handleStopMp3 called.")
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
stopMp3UseCase()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error stopping MP3 playback")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSeekMp3(positionMs: Long) {
|
||||
Timber.d("handleSeekMp3 called with position: $positionMs ms")
|
||||
// No need for viewModelScope for simple seek
|
||||
|
Loading…
x
Reference in New Issue
Block a user