refactor: improve accessibility title for player
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
package bzh.ajaury.chombev
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import bzh.ajaury.chombev.core.model.Phrase
|
||||||
|
import bzh.ajaury.chombev.core.model.Record
|
||||||
|
import bzh.ajaury.chombev.resources.generated.resources.Res
|
||||||
|
import bzh.ajaury.chombev.resources.generated.resources.records_chapter_number
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun rowDescription(record: Record): String = rowDescription(index = record.index, title = record.title)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun rowDescription(
|
||||||
|
index: Int,
|
||||||
|
title: Phrase,
|
||||||
|
): String {
|
||||||
|
val chapterNumber = stringResource(Res.string.records_chapter_number, index)
|
||||||
|
val rowDescription =
|
||||||
|
listOfNotNull(chapterNumber, title.translation)
|
||||||
|
.joinToString(separator = ", ")
|
||||||
|
return rowDescription
|
||||||
|
}
|
||||||
+30
-22
@@ -40,6 +40,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import bzh.ajaury.chombev.core.model.Phrase
|
import bzh.ajaury.chombev.core.model.Phrase
|
||||||
|
import bzh.ajaury.chombev.core.model.Record
|
||||||
import bzh.ajaury.chombev.player.model.PlaybackState
|
import bzh.ajaury.chombev.player.model.PlaybackState
|
||||||
import bzh.ajaury.chombev.player.model.PlaybackTiming
|
import bzh.ajaury.chombev.player.model.PlaybackTiming
|
||||||
import bzh.ajaury.chombev.player.model.PlayerState
|
import bzh.ajaury.chombev.player.model.PlayerState
|
||||||
@@ -51,6 +52,7 @@ import bzh.ajaury.chombev.resources.generated.resources.player_back
|
|||||||
import bzh.ajaury.chombev.resources.generated.resources.player_back_action_label
|
import bzh.ajaury.chombev.resources.generated.resources.player_back_action_label
|
||||||
import bzh.ajaury.chombev.resources.generated.resources.settings_open_action_description
|
import bzh.ajaury.chombev.resources.generated.resources.settings_open_action_description
|
||||||
import bzh.ajaury.chombev.resources.generated.resources.subtitle_preferences_action
|
import bzh.ajaury.chombev.resources.generated.resources.subtitle_preferences_action
|
||||||
|
import bzh.ajaury.chombev.rowDescription
|
||||||
import bzh.ajaury.chombev.subtitle.model.SubtitleLine
|
import bzh.ajaury.chombev.subtitle.model.SubtitleLine
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
@@ -204,35 +206,38 @@ private fun PlayerTopBar(
|
|||||||
// Animate the title in as it loads, and between records: the record data isn't there on
|
// Animate the title in as it loads, and between records: the record data isn't there on
|
||||||
// the first frame, so fade + rise it into place once uiState carries it.
|
// the first frame, so fade + rise it into place once uiState carries it.
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
targetState = uiState.recordEmoji to uiState.recordTitle,
|
targetState = uiState.record,
|
||||||
transitionSpec = {
|
transitionSpec = {
|
||||||
fadeIn(tween()) togetherWith
|
fadeIn(tween()) togetherWith
|
||||||
fadeOut(tween())
|
fadeOut(tween())
|
||||||
},
|
},
|
||||||
label = "PlayerTitle",
|
label = "PlayerTitle",
|
||||||
) { (emoji, title) ->
|
) { record ->
|
||||||
|
// Same description as the record's row in the list; none until the record is loaded.
|
||||||
|
val description =
|
||||||
|
record?.index?.let { rowDescription(index = it, title = record.title) }
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.traversalOrder(PlayerTraversalOrder.TITLE)
|
.traversalOrder(PlayerTraversalOrder.TITLE)
|
||||||
.clearAndSetSemantics {
|
.clearAndSetSemantics {
|
||||||
contentDescription = title.translation ?: title.transcription
|
description?.let { contentDescription = it }
|
||||||
},
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = emoji,
|
text = record?.emoji.orEmpty(),
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
)
|
)
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = title.transcription,
|
text = record?.title?.transcription.orEmpty(),
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
|
|
||||||
title.translation?.let { translation ->
|
record?.title?.translation?.let { translation ->
|
||||||
Text(
|
Text(
|
||||||
text = translation,
|
text = translation,
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
@@ -317,17 +322,25 @@ private val previewSubtitleLines =
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private val fakeRecord = Record(
|
||||||
|
id = 1,
|
||||||
|
index = 1,
|
||||||
|
emoji = "🙏",
|
||||||
|
title = Phrase(
|
||||||
|
transcription = "Demat",
|
||||||
|
translation = "Bonjour",
|
||||||
|
),
|
||||||
|
audioResourcePath = "",
|
||||||
|
subtitleResourcePath = "",
|
||||||
|
)
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun PlayerScreenPlayingPreview() {
|
private fun PlayerScreenPlayingPreview() {
|
||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
PlayerScreen(
|
PlayerScreen(
|
||||||
uiState = PlayerUiState(
|
uiState = PlayerUiState(
|
||||||
recordEmoji = "🙏",
|
fakeRecord,
|
||||||
recordTitle = Phrase(
|
|
||||||
transcription = "Demat",
|
|
||||||
translation = "Bonjour",
|
|
||||||
),
|
|
||||||
playerState = PlayerState(
|
playerState = PlayerState(
|
||||||
playbackState = PlaybackState.PLAYING,
|
playbackState = PlaybackState.PLAYING,
|
||||||
playbackTiming = PlaybackTiming(position = 1.2.seconds, duration = 5.seconds),
|
playbackTiming = PlaybackTiming(position = 1.2.seconds, duration = 5.seconds),
|
||||||
@@ -348,14 +361,13 @@ private fun PlayerScreenPausedPreview() {
|
|||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
PlayerScreen(
|
PlayerScreen(
|
||||||
uiState = PlayerUiState(
|
uiState = PlayerUiState(
|
||||||
recordEmoji = "🙏",
|
record = fakeRecord,
|
||||||
recordTitle = Phrase(
|
|
||||||
transcription = "Demat",
|
|
||||||
translation = "Bonjour",
|
|
||||||
),
|
|
||||||
playerState = PlayerState(
|
playerState = PlayerState(
|
||||||
playbackState = PlaybackState.PAUSED,
|
playbackState = PlaybackState.PAUSED,
|
||||||
playbackTiming = PlaybackTiming(position = 1.2.seconds, duration = 5.seconds),
|
playbackTiming = PlaybackTiming(
|
||||||
|
position = 1.2.seconds,
|
||||||
|
duration = 5.seconds,
|
||||||
|
),
|
||||||
subtitleLines = previewSubtitleLines,
|
subtitleLines = previewSubtitleLines,
|
||||||
currentSubtitleIndex = 1,
|
currentSubtitleIndex = 1,
|
||||||
),
|
),
|
||||||
@@ -373,11 +385,7 @@ private fun PlayerScreenEndedPreview() {
|
|||||||
MaterialTheme {
|
MaterialTheme {
|
||||||
PlayerScreen(
|
PlayerScreen(
|
||||||
uiState = PlayerUiState(
|
uiState = PlayerUiState(
|
||||||
recordEmoji = "🙏",
|
record = fakeRecord,
|
||||||
recordTitle = Phrase(
|
|
||||||
transcription = "Demat",
|
|
||||||
translation = "Bonjour",
|
|
||||||
),
|
|
||||||
playerState = PlayerState(
|
playerState = PlayerState(
|
||||||
playbackState = PlaybackState.ENDED,
|
playbackState = PlaybackState.ENDED,
|
||||||
playbackTiming = PlaybackTiming(position = 1.2.seconds, duration = 5.seconds),
|
playbackTiming = PlaybackTiming(position = 1.2.seconds, duration = 5.seconds),
|
||||||
|
|||||||
+6
-4
@@ -1,6 +1,6 @@
|
|||||||
package bzh.ajaury.chombev.player.ui.viewmodel
|
package bzh.ajaury.chombev.player.ui.viewmodel
|
||||||
|
|
||||||
import bzh.ajaury.chombev.core.model.Phrase
|
import bzh.ajaury.chombev.core.model.Record
|
||||||
import bzh.ajaury.chombev.player.model.PlayerState
|
import bzh.ajaury.chombev.player.model.PlayerState
|
||||||
import bzh.ajaury.chombev.preferences.model.PlaybackPreferences
|
import bzh.ajaury.chombev.preferences.model.PlaybackPreferences
|
||||||
import bzh.ajaury.chombev.preferences.model.SubtitlePreferences
|
import bzh.ajaury.chombev.preferences.model.SubtitlePreferences
|
||||||
@@ -8,8 +8,7 @@ import kotlin.time.Duration
|
|||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
data class PlayerUiState(
|
data class PlayerUiState(
|
||||||
val recordEmoji: String = "",
|
val record: Record? = null,
|
||||||
val recordTitle: Phrase = Phrase(transcription = ""),
|
|
||||||
val playerState: PlayerState = PlayerState(),
|
val playerState: PlayerState = PlayerState(),
|
||||||
val subtitlePreferences: SubtitlePreferences = SubtitlePreferences(),
|
val subtitlePreferences: SubtitlePreferences = SubtitlePreferences(),
|
||||||
val playbackSpeed: Float = PlaybackPreferences.DEFAULT_SPEED,
|
val playbackSpeed: Float = PlaybackPreferences.DEFAULT_SPEED,
|
||||||
@@ -20,7 +19,10 @@ data class PlayerUiState(
|
|||||||
get() = playerState.playbackTiming.position > 2.seconds
|
get() = playerState.playbackTiming.position > 2.seconds
|
||||||
|
|
||||||
val canGoToNextSentence: Boolean
|
val canGoToNextSentence: Boolean
|
||||||
get() = (playerState.currentSubtitleIndex ?: Int.MAX_VALUE) < playerState.subtitleLines.lastIndex
|
get() = (
|
||||||
|
playerState.currentSubtitleIndex
|
||||||
|
?: Int.MAX_VALUE
|
||||||
|
) < playerState.subtitleLines.lastIndex
|
||||||
|
|
||||||
/** True when any playback setting differs from its default, used to highlight the button. */
|
/** True when any playback setting differs from its default, used to highlight the button. */
|
||||||
val isPlaybackModified: Boolean
|
val isPlaybackModified: Boolean
|
||||||
|
|||||||
+1
-3
@@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import bzh.ajaury.chombev.core.accessibility.domain.AccessibilityRepository
|
import bzh.ajaury.chombev.core.accessibility.domain.AccessibilityRepository
|
||||||
import bzh.ajaury.chombev.core.logging.domain.Logger
|
import bzh.ajaury.chombev.core.logging.domain.Logger
|
||||||
import bzh.ajaury.chombev.core.model.Phrase
|
|
||||||
import bzh.ajaury.chombev.core.model.Record
|
import bzh.ajaury.chombev.core.model.Record
|
||||||
import bzh.ajaury.chombev.player.domain.AudioSessionManager
|
import bzh.ajaury.chombev.player.domain.AudioSessionManager
|
||||||
import bzh.ajaury.chombev.player.domain.PlaybackRepository
|
import bzh.ajaury.chombev.player.domain.PlaybackRepository
|
||||||
@@ -42,8 +41,7 @@ class PlayerViewModel(
|
|||||||
preferencesRepository.playbackPreferences,
|
preferencesRepository.playbackPreferences,
|
||||||
) { record, playerState, subtitlePreferences, playbackPreferences ->
|
) { record, playerState, subtitlePreferences, playbackPreferences ->
|
||||||
PlayerUiState(
|
PlayerUiState(
|
||||||
recordEmoji = record?.emoji ?: "",
|
record = record,
|
||||||
recordTitle = record?.title ?: Phrase(transcription = ""),
|
|
||||||
playerState = playerState,
|
playerState = playerState,
|
||||||
subtitlePreferences = subtitlePreferences,
|
subtitlePreferences = subtitlePreferences,
|
||||||
playbackSpeed = playbackPreferences.speed,
|
playbackSpeed = playbackPreferences.speed,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import bzh.ajaury.chombev.core.model.Phrase
|
|||||||
import bzh.ajaury.chombev.core.model.Record
|
import bzh.ajaury.chombev.core.model.Record
|
||||||
import bzh.ajaury.chombev.resources.generated.resources.Res
|
import bzh.ajaury.chombev.resources.generated.resources.Res
|
||||||
import bzh.ajaury.chombev.resources.generated.resources.records_chapter_action_description
|
import bzh.ajaury.chombev.resources.generated.resources.records_chapter_action_description
|
||||||
import bzh.ajaury.chombev.resources.generated.resources.records_chapter_number
|
import bzh.ajaury.chombev.rowDescription
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -29,10 +29,7 @@ fun RecordView(
|
|||||||
record: Record,
|
record: Record,
|
||||||
onClick: () -> Unit = {},
|
onClick: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val chapterNumber = stringResource(Res.string.records_chapter_number, record.index)
|
val rowDescription = rowDescription(record)
|
||||||
val rowDescription =
|
|
||||||
listOfNotNull(chapterNumber, record.title.transcription, record.title.translation)
|
|
||||||
.joinToString(separator = ", ")
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier =
|
modifier =
|
||||||
|
|||||||
Reference in New Issue
Block a user