Polish audio/TTS UI: move labels to strings.xml, fix TTS WorkManager observer leak
This commit is contained in:
parent
ccfc677ea7
commit
6ddbb01973
4 changed files with 76 additions and 33 deletions
|
|
@ -8,6 +8,8 @@ package org.dueattendant149.bookshelf.presentation.tts
|
|||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.work.Constraints
|
||||
|
|
@ -29,6 +31,7 @@ import org.dueattendant149.bookshelf.domain.use_case.tts.CreateTtsJobUseCase
|
|||
import org.dueattendant149.bookshelf.domain.use_case.tts.FetchTtsEnginesUseCase
|
||||
import org.dueattendant149.bookshelf.domain.use_case.tts.FetchTtsVoicesUseCase
|
||||
import org.dueattendant149.bookshelf.domain.use_case.tts.GetTtsJobUseCase
|
||||
import org.dueattendant149.bookshelf.R
|
||||
import javax.inject.Inject
|
||||
|
||||
data class TtsState(
|
||||
|
|
@ -60,6 +63,9 @@ class TtsModel
|
|||
|
||||
private val workManager = WorkManager.getInstance(application)
|
||||
|
||||
private var observedWork: LiveData<WorkInfo?>? = null
|
||||
private var workObserver: Observer<WorkInfo?>? = null
|
||||
|
||||
fun load(bookRemoteId: String) {
|
||||
viewModelScope.launch {
|
||||
_state.update { it.copy(isLoading = true, error = null) }
|
||||
|
|
@ -123,7 +129,7 @@ class TtsModel
|
|||
speed = _state.value.speed,
|
||||
)
|
||||
val job = result.getOrElse {
|
||||
_state.update { it.copy(isCreatingJob = false, error = it.error ?: "Failed to create TTS job") }
|
||||
_state.update { it.copy(isCreatingJob = false, error = it.error ?: application.getString(R.string.tts_create_job_failed)) }
|
||||
return@launch
|
||||
}
|
||||
_state.update { it.copy(isCreatingJob = false, job = job) }
|
||||
|
|
@ -168,9 +174,13 @@ class TtsModel
|
|||
}
|
||||
|
||||
private fun observeWorker(workId: java.util.UUID) {
|
||||
workManager.getWorkInfoByIdLiveData(workId)
|
||||
.observeForever { info: WorkInfo? ->
|
||||
info ?: return@observeForever
|
||||
workObserver?.let { observer ->
|
||||
observedWork?.removeObserver(observer)
|
||||
}
|
||||
|
||||
val liveData = workManager.getWorkInfoByIdLiveData(workId)
|
||||
val observer: Observer<WorkInfo?> = Observer { info ->
|
||||
info ?: return@Observer
|
||||
when (info.state) {
|
||||
WorkInfo.State.SUCCEEDED -> {
|
||||
val filePath = info.outputData.getString(TtsDownloadWorker.KEY_OUTPUT_FILE_PATH)
|
||||
|
|
@ -179,7 +189,7 @@ class TtsModel
|
|||
|
||||
WorkInfo.State.FAILED -> {
|
||||
val message = info.outputData.getString(TtsDownloadWorker.KEY_ERROR_MESSAGE)
|
||||
_state.update { it.copy(error = message ?: "TTS download failed") }
|
||||
_state.update { it.copy(error = message ?: application.getString(R.string.tts_download_failed)) }
|
||||
}
|
||||
|
||||
WorkInfo.State.RUNNING -> {
|
||||
|
|
@ -195,5 +205,16 @@ class TtsModel
|
|||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
observedWork = liveData
|
||||
workObserver = observer
|
||||
liveData.observeForever(observer)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
workObserver?.let { observer ->
|
||||
observedWork?.removeObserver(observer)
|
||||
}
|
||||
super.onCleared()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,10 +41,12 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.dueattendant149.bookshelf.R
|
||||
import org.dueattendant149.bookshelf.presentation.player.PlayerState
|
||||
import org.dueattendant149.bookshelf.ui.navigator.LocalNavigator
|
||||
import kotlin.math.roundToInt
|
||||
|
|
@ -73,7 +75,7 @@ fun PlayerContent(
|
|||
IconButton(onClick = { navigator.pop() }) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "Back"
|
||||
contentDescription = stringResource(R.string.go_back_content_desc)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -97,7 +99,7 @@ fun PlayerContent(
|
|||
|
||||
val currentTrack = playerState.tracks.getOrNull(playerState.currentTrackIndex)
|
||||
Text(
|
||||
text = currentTrack?.title ?: "No track",
|
||||
text = currentTrack?.title ?: stringResource(R.string.player_no_track),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -145,10 +147,10 @@ fun PlayerContent(
|
|||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(onClick = onSeekBackward) {
|
||||
Text("-10s")
|
||||
Text(stringResource(R.string.player_seek_backward))
|
||||
}
|
||||
IconButton(onClick = onSkipPrevious) {
|
||||
Icon(Icons.Default.SkipPrevious, contentDescription = "Previous")
|
||||
Icon(Icons.Default.SkipPrevious, contentDescription = stringResource(R.string.player_previous))
|
||||
}
|
||||
IconButton(
|
||||
onClick = onPlayPause,
|
||||
|
|
@ -156,15 +158,17 @@ fun PlayerContent(
|
|||
) {
|
||||
Icon(
|
||||
imageVector = if (playerState.isPlaying) Icons.Default.Pause else Icons.Default.PlayArrow,
|
||||
contentDescription = if (playerState.isPlaying) "Pause" else "Play",
|
||||
contentDescription = stringResource(
|
||||
if (playerState.isPlaying) R.string.player_pause else R.string.player_play
|
||||
),
|
||||
modifier = Modifier.size(48.dp)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = onSkipNext) {
|
||||
Icon(Icons.Default.SkipNext, contentDescription = "Next")
|
||||
Icon(Icons.Default.SkipNext, contentDescription = stringResource(R.string.player_next))
|
||||
}
|
||||
IconButton(onClick = onSeekForward) {
|
||||
Text("+30s")
|
||||
Text(stringResource(R.string.player_seek_forward))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -226,7 +226,15 @@ private fun VoiceItem(
|
|||
) {
|
||||
ListItem(
|
||||
headlineContent = { Text(voice.name) },
|
||||
supportingContent = { Text("${voice.language} • ${voice.engine}") },
|
||||
supportingContent = {
|
||||
Text(
|
||||
stringResource(
|
||||
R.string.tts_voice_description,
|
||||
voice.language,
|
||||
voice.engine
|
||||
)
|
||||
)
|
||||
},
|
||||
trailingContent = {
|
||||
if (selected) {
|
||||
Text(stringResource(R.string.tts_selected_label))
|
||||
|
|
|
|||
|
|
@ -12,6 +12,13 @@
|
|||
<!-- Audio playback -->
|
||||
<string name="audio_playback_channel">Audio playback</string>
|
||||
<string name="audio_playback_channel_description">Shows the currently playing audiobook</string>
|
||||
<string name="player_no_track">No track</string>
|
||||
<string name="player_seek_backward">-10s</string>
|
||||
<string name="player_seek_forward">+30s</string>
|
||||
<string name="player_previous">Previous</string>
|
||||
<string name="player_next">Next</string>
|
||||
<string name="player_play">Play</string>
|
||||
<string name="player_pause">Pause</string>
|
||||
|
||||
<!-- Screens -->
|
||||
<string name="library_screen">Library</string>
|
||||
|
|
@ -569,5 +576,8 @@
|
|||
<string name="tts_speed_label">Speed: %1$.2fx</string>
|
||||
<string name="tts_create_job_button">Create TTS job</string>
|
||||
<string name="tts_book_id_label">Book: %1$s</string>
|
||||
<string name="tts_voice_description">%1$s • %2$s</string>
|
||||
<string name="tts_create_job_failed">Failed to create TTS job</string>
|
||||
<string name="tts_download_failed">TTS download failed</string>
|
||||
|
||||
</resources>
|
||||
Loading…
Add table
Add a link
Reference in a new issue