fix(ui): resolve build errors in MainScreen

This commit is contained in:
dave 2025-04-08 17:40:57 +03:00
parent c22a20b2c8
commit 3bcbda4d27
2 changed files with 280 additions and 174 deletions
app/src/main/java/com/voca/app

@ -381,45 +381,37 @@ fun MainScreen(
Spacer(modifier = Modifier.height(16.dp)) 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) { if (isMp3FlowActive) {
// MP3 Player Controls - Only show if MP3 flow is active Mp3GenerationAndSeekControls(
Timber.d("Rendering MP3 Controls because isMp3FlowActive = true") playbackState = playbackState,
Mp3PlayerControls( mp3GenerationState = mp3GenerationState,
playbackState = playbackState, mp3SaveState = mp3SaveState,
mp3GenerationState = mp3GenerationState, currentMp3UriFromViewModel = currentMp3Uri,
mp3SaveState = mp3SaveState, onCancel = { (viewModel::onAction)(MainAction.CancelMp3Generation) },
currentMp3UriFromViewModel = currentMp3Uri, onSeek = { positionMs -> (viewModel::onAction)(MainAction.SeekMp3(positionMs)) },
// Enable generation button only if there's content and MP3 isn't already active/generating onGenerateMp3 = { (viewModel::onAction)(MainAction.GenerateMp3) }
generationEnabled = (isDocumentLoaded || hasUserInput) && !(playbackState.currentUri != null || mp3GenerationState is Mp3GenerationProgress.InProgress), )
onPlay = { (viewModel::onAction)(MainAction.PlayMp3) }, } else if (isDocumentLoaded || hasUserInput) { // Use the same condition used for `generationEnabled` previously
onPause = { (viewModel::onAction)(MainAction.PauseMp3) }, Button(
onResume = { (viewModel::onAction)(MainAction.ResumeMp3) }, onClick = { viewModel.onAction(MainAction.GenerateMp3) },
onStop = { (viewModel::onAction)(MainAction.StopMp3) }, modifier = Modifier.padding(top = 8.dp) // Add some padding
onCancel = { (viewModel::onAction)(MainAction.CancelMp3Generation) }, ) {
onSeek = { positionMs -> (viewModel::onAction)(MainAction.SeekMp3(positionMs)) }, Text("Create MP3")
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) }
)
} }
// --- // --- End MP3 Generation/Save/Seek UI ---
// Optional: Show subtle indicator if background audio is playing (Consider removing if controls are clear) // 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 @Composable
fun TtsControls( fun UnifiedPlaybackControls(
ttsState: TTSState, ttsState: TTSState,
enabled: Boolean, playbackState: PlaybackState,
onPlayPauseToggle: () -> Unit, isContentAvailable: Boolean,
onStop: () -> Unit isMp3Mode: Boolean, // True if MP3 is loaded/active, false for TTS
onTogglePlayback: () -> Unit,
onStopPlayback: () -> Unit
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly, horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically 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 // Play/Pause Button
IconButton(onClick = onPlayPauseToggle, enabled = enabled) { IconButton(onClick = onTogglePlayback, enabled = canPlayback) {
Icon( Icon(
imageVector = if (ttsState.isSpeaking && !ttsState.isPaused) Icons.Filled.Pause else Icons.Filled.PlayArrow, imageVector = if (isPlaying) Icons.Filled.Pause else Icons.Filled.PlayArrow,
contentDescription = if (ttsState.isSpeaking && !ttsState.isPaused) "Pause Speech" else "Play Speech", contentDescription = when {
isPlaying -> "Pause"
isPaused -> "Resume"
else -> "Play"
},
modifier = Modifier.size(48.dp) modifier = Modifier.size(48.dp)
) )
} }
// Stop Button (Enabled only if speaking or paused) // Stop Button (Enabled if either TTS or MP3 is playing or paused)
IconButton(onClick = onStop, enabled = ttsState.isSpeaking || ttsState.isPaused) { val canStop = (isMp3Mode && (playbackState.isPlaying || playbackState.isPaused)) ||
(!isMp3Mode && (ttsState.isSpeaking || ttsState.isPaused))
IconButton(onClick = onStopPlayback, enabled = canStop) {
Icon( Icon(
imageVector = Icons.Filled.Stop, imageVector = Icons.Filled.Stop,
contentDescription = "Stop Speech", contentDescription = "Stop",
modifier = Modifier.size(48.dp) modifier = Modifier.size(48.dp)
) )
} }
} }
} }
// --- END Unified Playback Controls Composable ---
// Simple composable for TTS control buttons - REMOVED
/*
@Composable
fun TtsControls(...) { ... }
*/
// --- Preview Data Structure --- // --- Preview Data Structure ---
// Simple data class to hold state for preview purposes, avoiding complex ViewModel mocking // 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)) Spacer(modifier = Modifier.height(16.dp))
} }
TtsControls( // Use UnifiedPlaybackControls in Preview
enabled = true, UnifiedPlaybackControls(
ttsState = ttsState, ttsState = ttsState, // Pass dummy state
onPlayPauseToggle = { playbackState = PlaybackState(), // Pass dummy state
if (ttsState.isSpeaking && !ttsState.isPaused) { isContentAvailable = text.isNotEmpty(),
state.onAction(MainAction.PauseSpeech) isMp3Mode = false, // Assume TTS mode for preview
} else { onTogglePlayback = { state.onAction(MainAction.TogglePlayback) }, // Use new action
state.onAction(MainAction.PlayText) onStopPlayback = { state.onAction(MainAction.StopPlayback) } // Use new action
}
},
onStop = { state.onAction(MainAction.StopSpeaking) }
) )
when(genState) { 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. * 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 ProcessSelectedDocument(val uri: Uri) : MainAction
data class ProcessUri(val uri: Uri) : MainAction data class ProcessUri(val uri: Uri) : MainAction
data class ProcessSharedText(val text: String) : MainAction data class ProcessSharedText(val text: String) : MainAction
data object PlayText : MainAction // Plays current page via TTS data class SetTextAndPlay(val text: String) : MainAction // Set text and immediately play TTS - Keep for specific use cases
data object PauseSpeech : MainAction
data object ResumeSpeech : MainAction // --- NEW Unified Playback Actions ---
data object StopSpeaking : MainAction data object TogglePlayback : MainAction // Unified Play/Pause/Resume
data class SetTextAndPlay(val text: String) : MainAction // Set text and immediately play TTS 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 GenerateMp3 : MainAction
data object CancelMp3Generation : MainAction // New action to cancel MP3 generation data object CancelMp3Generation : MainAction // New action to cancel MP3 generation
data class SaveMp3(val desiredFileName: String? = null) : MainAction // Optional desired filename 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 // Keep Seek action for MP3
data object ResumeMp3 : MainAction
data object StopMp3 : MainAction
// Change parameter type to Long
data class SeekMp3(val positionMs: Long) : MainAction data class SeekMp3(val positionMs: Long) : MainAction
data object NextPage : MainAction data object NextPage : MainAction
data object PreviousPage : MainAction data object PreviousPage : MainAction
data object TriggerDocumentPicker : MainAction data object TriggerDocumentPicker : MainAction
@ -332,18 +343,16 @@ class MainViewModel(
is MainAction.ProcessUri -> handleLoadDocumentAndInitiateAudioProcessing(action.uri) is MainAction.ProcessUri -> handleLoadDocumentAndInitiateAudioProcessing(action.uri)
is MainAction.ProcessSharedText -> handleUpdateText(action.text) is MainAction.ProcessSharedText -> handleUpdateText(action.text)
is MainAction.UpdateText -> 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 -> { is MainAction.UpdateInputText -> {
_userInputText.value = action.text _userInputText.value = action.text
} }
is MainAction.TriggerDocumentPicker -> handleTriggerDocumentPicker() is MainAction.TriggerDocumentPicker -> handleTriggerDocumentPicker()
// TTS Actions // --- UPDATED Playback Actions ---
MainAction.PlayText -> handlePlayText() // Plays current page MainAction.TogglePlayback -> handleTogglePlayback()
MainAction.PauseSpeech -> handlePauseSpeech() MainAction.StopPlayback -> handleStopPlayback()
MainAction.ResumeSpeech -> handleResumeSpeech() // --- END UPDATED ---
MainAction.StopSpeaking -> handleStopSpeaking()
// MainAction.ToggleTTS -> handleToggleTTS() // Removed as separate Pause/Resume/Play are preferred
// MP3 Generation & Saving (Now combined or manual) // MP3 Generation & Saving (Now combined or manual)
MainAction.GenerateMp3 -> handleGenerateAndSaveMp3() // Manual trigger for combined process 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 is MainAction.SaveMp3 -> Unit // Explicitly ignore if saving is purely automatic now
// MP3 Playback // MP3 Playback
MainAction.PlayMp3 -> handlePlayMp3()
MainAction.PauseMp3 -> handlePauseMp3()
MainAction.ResumeMp3 -> handleResumeMp3()
MainAction.StopMp3 -> handleStopMp3()
is MainAction.SeekMp3 -> handleSeekMp3(action.positionMs) is MainAction.SeekMp3 -> handleSeekMp3(action.positionMs)
// Pagination // Pagination
@ -519,41 +524,54 @@ class MainViewModel(
} }
} }
private fun handlePlayText() { // --- NEW Handler for Unified Playback Toggle ---
Timber.d("handlePlayText called.") private fun handleTogglePlayback() {
Timber.d("handleTogglePlayback called.")
viewModelScope.launch { viewModelScope.launch {
// --- ADD CHECK: Do not start TTS if an MP3 is loaded --- val mp3Uri = _currentMp3Uri.value
if (_currentMp3Uri.value != null) { val currentPlaybackState = playbackState.value
Timber.w("handlePlayText: MP3 URI is loaded ($_currentMp3Uri.value). Skipping TTS playback.") val currentTtsState = ttsStateFlow.value
return@launch
}
// --- END CHECK ---
val textToSpeak = getCurrentDocumentStateUseCase.currentPageText.value if (mp3Uri != null) {
if (!textToSpeak.isNullOrBlank()) { // --- MP3 Mode ---
Timber.d("Speaking current page text: '${textToSpeak.take(50)}...'") Timber.d("Toggle playback in MP3 mode. State: $currentPlaybackState")
speakTextUseCase(textToSpeak) when {
currentPlaybackState.isPlaying -> pauseMp3UseCase()
currentPlaybackState.isPaused -> resumeMp3UseCase()
else -> playMp3UseCase(mp3Uri) // Start playback if idle
}
} else { } 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() { // --- NEW Handler for Unified Stop ---
Timber.d("handlePauseSpeech called.") private fun handleStopPlayback() {
pauseTTSUseCase() Timber.d("handleStopPlayback called.")
} viewModelScope.launch {
// Stop both TTS and MP3 playback regardless of the current mode
private fun handleResumeSpeech() { Timber.d("Stopping both TTS and MP3.")
Timber.d("handleResumeSpeech called.") stopTTSUseCase()
resumeTTSUseCase() stopMp3UseCase()
} }
private fun handleStopSpeaking() {
Timber.d("handleStopSpeaking called.")
stopTTSUseCase()
} }
// --- END Handler ---
// --- ADDED BACK: Handler for SetTextAndPlay ---
private fun handleSetTextAndPlay(text: String) { private fun handleSetTextAndPlay(text: String) {
Timber.d("handleSetTextAndPlay called.") Timber.d("handleSetTextAndPlay called.")
if (text.isBlank()) { if (text.isBlank()) {
@ -566,17 +584,20 @@ class MainViewModel(
resetStateForNewContent() // Reset state FIRST resetStateForNewContent() // Reset state FIRST
delay(50) // Small delay to allow state reset propagation if needed 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 { try {
processDocumentUseCase.processText(text) processDocumentUseCase.processText(text)
delay(50) // Keep delay for now, but ideally improve state propagation delay(50) // Keep delay for now, but ideally improve state propagation
val updatedText = getCurrentDocumentStateUseCase.currentPageText.value val updatedText = getCurrentDocumentStateUseCase.currentPageText.value
if (!updatedText.isNullOrBlank()) { if (!updatedText.isNullOrBlank()) {
speakTextUseCase(updatedText) // Initiate TTS playback after processing
_processingStatus.value = PdfProcessingStatus.COMPLETED speakTextUseCase(updatedText)
_processingStatus.value = PdfProcessingStatus.COMPLETED // Mark as completed after starting TTS
} else { } else {
Timber.w("handleSetTextAndPlay: Text set, but state not updated in time for TTS.") 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) { } catch (e: Exception) {
Timber.e(e, "Error in setTextAndPlay") Timber.e(e, "Error in setTextAndPlay")
@ -586,6 +607,7 @@ class MainViewModel(
} }
} }
} }
// --- END ADDED BACK ---
private fun handleGenerateAndSaveMp3() { private fun handleGenerateAndSaveMp3() {
Timber.d("handleGenerateAndSaveMp3 called. Adding debug checkpoints.") 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) { private fun handleSeekMp3(positionMs: Long) {
Timber.d("handleSeekMp3 called with position: $positionMs ms") Timber.d("handleSeekMp3 called with position: $positionMs ms")
// No need for viewModelScope for simple seek // No need for viewModelScope for simple seek