new: add subtitle preferences
This commit is contained in:
@@ -4,6 +4,8 @@ import fr.ajaury.gwenedeg.core.logging.di.loggingModule
|
|||||||
import fr.ajaury.gwenedeg.player.data.PlaybackRepositoryImpl
|
import fr.ajaury.gwenedeg.player.data.PlaybackRepositoryImpl
|
||||||
import fr.ajaury.gwenedeg.player.domain.PlaybackRepository
|
import fr.ajaury.gwenedeg.player.domain.PlaybackRepository
|
||||||
import fr.ajaury.gwenedeg.player.ui.viewmodel.PlayerViewModel
|
import fr.ajaury.gwenedeg.player.ui.viewmodel.PlayerViewModel
|
||||||
|
import fr.ajaury.gwenedeg.preferences.data.PreferencesRepositoryImpl
|
||||||
|
import fr.ajaury.gwenedeg.preferences.domain.PreferencesRepository
|
||||||
import fr.ajaury.gwenedeg.records.data.InMemoryRecordRepository
|
import fr.ajaury.gwenedeg.records.data.InMemoryRecordRepository
|
||||||
import fr.ajaury.gwenedeg.records.domain.RecordRepository
|
import fr.ajaury.gwenedeg.records.domain.RecordRepository
|
||||||
import fr.ajaury.gwenedeg.records.ui.viewmodel.RecordsViewModel
|
import fr.ajaury.gwenedeg.records.ui.viewmodel.RecordsViewModel
|
||||||
@@ -19,6 +21,7 @@ val sharedModule = module {
|
|||||||
includes(loggingModule)
|
includes(loggingModule)
|
||||||
factoryOf(::InMemoryRecordRepository) { bind<RecordRepository>() }
|
factoryOf(::InMemoryRecordRepository) { bind<RecordRepository>() }
|
||||||
singleOf(::PlaybackRepositoryImpl) { bind<PlaybackRepository>() }
|
singleOf(::PlaybackRepositoryImpl) { bind<PlaybackRepository>() }
|
||||||
|
singleOf(::PreferencesRepositoryImpl) { bind<PreferencesRepository>() }
|
||||||
viewModelOf(::RecordsViewModel)
|
viewModelOf(::RecordsViewModel)
|
||||||
viewModelOf(::PlayerViewModel)
|
viewModelOf(::PlayerViewModel)
|
||||||
factoryOf(::ComposeResourceReader) { bind<ResourceReader>() }
|
factoryOf(::ComposeResourceReader) { bind<ResourceReader>() }
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import fr.ajaury.gwenedeg.theme.GwenedegTheme
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PlayerControl(
|
fun PlayerControl(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
playbackState: PlaybackState,
|
playbackState: PlaybackState,
|
||||||
playbackTiming: PlaybackTiming,
|
playbackTiming: PlaybackTiming,
|
||||||
onSeek: (Float) -> Unit = {},
|
onSeek: (Float) -> Unit = {},
|
||||||
@@ -40,7 +41,7 @@ fun PlayerControl(
|
|||||||
onNextSentenceClicked: () -> Unit = {},
|
onNextSentenceClicked: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Subtitles
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@@ -16,6 +17,9 @@ import androidx.compose.material3.TopAppBar
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
@@ -28,6 +32,7 @@ import fr.ajaury.gwenedeg.player.model.PlaybackTiming
|
|||||||
import fr.ajaury.gwenedeg.player.model.PlayerState
|
import fr.ajaury.gwenedeg.player.model.PlayerState
|
||||||
import fr.ajaury.gwenedeg.player.ui.viewmodel.PlayerUiState
|
import fr.ajaury.gwenedeg.player.ui.viewmodel.PlayerUiState
|
||||||
import fr.ajaury.gwenedeg.player.ui.viewmodel.PlayerViewModel
|
import fr.ajaury.gwenedeg.player.ui.viewmodel.PlayerViewModel
|
||||||
|
import fr.ajaury.gwenedeg.preferences.ui.SubtitlePreferencesBottomSheet
|
||||||
import fr.ajaury.gwenedeg.subtitle.model.SubtitleLine
|
import fr.ajaury.gwenedeg.subtitle.model.SubtitleLine
|
||||||
import fr.ajaury.gwenedeg.theme.GwenedegTheme
|
import fr.ajaury.gwenedeg.theme.GwenedegTheme
|
||||||
import org.koin.compose.viewmodel.koinViewModel
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
@@ -57,6 +62,8 @@ fun PlayerScreen(
|
|||||||
onSeekToSentence = viewModel::seekToSentence,
|
onSeekToSentence = viewModel::seekToSentence,
|
||||||
onPreviousSentenceClicked = viewModel::goToPreviousSentence,
|
onPreviousSentenceClicked = viewModel::goToPreviousSentence,
|
||||||
onNextSentenceClicked = viewModel::goToNextSentence,
|
onNextSentenceClicked = viewModel::goToNextSentence,
|
||||||
|
onShowTranscriptionChange = viewModel::setShowTranscription,
|
||||||
|
onShowTranslationChange = viewModel::setShowTranslation,
|
||||||
onBackClicked = onBackClicked,
|
onBackClicked = onBackClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -70,8 +77,12 @@ fun PlayerScreen(
|
|||||||
onSeekToSentence: (position: Int) -> Unit = {},
|
onSeekToSentence: (position: Int) -> Unit = {},
|
||||||
onPreviousSentenceClicked: () -> Unit = {},
|
onPreviousSentenceClicked: () -> Unit = {},
|
||||||
onNextSentenceClicked: () -> Unit = {},
|
onNextSentenceClicked: () -> Unit = {},
|
||||||
|
onShowTranscriptionChange: (Boolean) -> Unit = {},
|
||||||
|
onShowTranslationChange: (Boolean) -> Unit = {},
|
||||||
onBackClicked: () -> Unit = {},
|
onBackClicked: () -> Unit = {},
|
||||||
) {
|
) {
|
||||||
|
var showPreferences by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
@@ -102,6 +113,14 @@ fun PlayerScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = { showPreferences = true }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Subtitles,
|
||||||
|
contentDescription = "Préférences de sous-titrage",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
@@ -115,6 +134,7 @@ fun PlayerScreen(
|
|||||||
SubtitleList(
|
SubtitleList(
|
||||||
lines = uiState.playerState.subtitleLines,
|
lines = uiState.playerState.subtitleLines,
|
||||||
currentIndex = uiState.playerState.currentSubtitleIndex,
|
currentIndex = uiState.playerState.currentSubtitleIndex,
|
||||||
|
subtitlePreferences = uiState.subtitlePreferences,
|
||||||
onSeek = onSeekToSentence,
|
onSeek = onSeekToSentence,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
@@ -130,6 +150,15 @@ fun PlayerScreen(
|
|||||||
onNextSentenceClicked = onNextSentenceClicked,
|
onNextSentenceClicked = onNextSentenceClicked,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showPreferences) {
|
||||||
|
SubtitlePreferencesBottomSheet(
|
||||||
|
subtitlePreferences = uiState.subtitlePreferences,
|
||||||
|
onShowTranscriptionChange = onShowTranscriptionChange,
|
||||||
|
onShowTranslationChange = onShowTranslationChange,
|
||||||
|
onDismiss = { showPreferences = false },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import androidx.compose.ui.text.style.TextAlign
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import fr.ajaury.gwenedeg.core.model.Phrase
|
import fr.ajaury.gwenedeg.core.model.Phrase
|
||||||
|
import fr.ajaury.gwenedeg.preferences.model.SubtitlePreferences
|
||||||
import fr.ajaury.gwenedeg.subtitle.model.SubtitleLine
|
import fr.ajaury.gwenedeg.subtitle.model.SubtitleLine
|
||||||
import fr.ajaury.gwenedeg.theme.GwenedegTheme
|
import fr.ajaury.gwenedeg.theme.GwenedegTheme
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
@@ -38,6 +39,7 @@ fun SubtitleList(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
lines: List<SubtitleLine>,
|
lines: List<SubtitleLine>,
|
||||||
currentIndex: Int?,
|
currentIndex: Int?,
|
||||||
|
subtitlePreferences: SubtitlePreferences = SubtitlePreferences(),
|
||||||
onSeek: (position: Int) -> Unit = {},
|
onSeek: (position: Int) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
@@ -98,6 +100,7 @@ fun SubtitleList(
|
|||||||
Subtitle(
|
Subtitle(
|
||||||
line = line,
|
line = line,
|
||||||
isCurrent = isCurrent,
|
isCurrent = isCurrent,
|
||||||
|
subtitlePreferences = subtitlePreferences,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,6 +124,7 @@ private suspend fun LazyListState.centerItem(index: Int) {
|
|||||||
private fun Subtitle(
|
private fun Subtitle(
|
||||||
line: SubtitleLine,
|
line: SubtitleLine,
|
||||||
isCurrent: Boolean,
|
isCurrent: Boolean,
|
||||||
|
subtitlePreferences: SubtitlePreferences = SubtitlePreferences(),
|
||||||
) {
|
) {
|
||||||
val contentColor = if (isCurrent) {
|
val contentColor = if (isCurrent) {
|
||||||
Color.Unspecified
|
Color.Unspecified
|
||||||
@@ -133,18 +137,22 @@ private fun Subtitle(
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
) {
|
) {
|
||||||
TranscriptionLine(
|
if (subtitlePreferences.showTranscription) {
|
||||||
text = line.phrase.transcription,
|
TranscriptionLine(
|
||||||
isCurrent = isCurrent,
|
text = line.phrase.transcription,
|
||||||
contentColor = contentColor,
|
|
||||||
)
|
|
||||||
line.phrase.translation?.let { translation ->
|
|
||||||
TranslationLine(
|
|
||||||
text = translation,
|
|
||||||
isCurrent = isCurrent,
|
isCurrent = isCurrent,
|
||||||
contentColor = contentColor,
|
contentColor = contentColor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
line.phrase.translation
|
||||||
|
?.takeIf { subtitlePreferences.showTranslation }
|
||||||
|
?.let { translation ->
|
||||||
|
TranslationLine(
|
||||||
|
text = translation,
|
||||||
|
isCurrent = isCurrent,
|
||||||
|
contentColor = contentColor,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package fr.ajaury.gwenedeg.player.ui.viewmodel
|
|||||||
|
|
||||||
import fr.ajaury.gwenedeg.core.model.Phrase
|
import fr.ajaury.gwenedeg.core.model.Phrase
|
||||||
import fr.ajaury.gwenedeg.player.model.PlayerState
|
import fr.ajaury.gwenedeg.player.model.PlayerState
|
||||||
|
import fr.ajaury.gwenedeg.preferences.model.SubtitlePreferences
|
||||||
|
|
||||||
data class PlayerUiState(
|
data class PlayerUiState(
|
||||||
val recordTitle: Phrase = Phrase(transcription = ""),
|
val recordTitle: Phrase = Phrase(transcription = ""),
|
||||||
val playerState: PlayerState = PlayerState(),
|
val playerState: PlayerState = PlayerState(),
|
||||||
|
val subtitlePreferences: SubtitlePreferences = SubtitlePreferences(),
|
||||||
)
|
)
|
||||||
|
|||||||
+13
-1
@@ -8,6 +8,7 @@ import fr.ajaury.gwenedeg.core.model.Record
|
|||||||
import fr.ajaury.gwenedeg.player.domain.AudioSessionManager
|
import fr.ajaury.gwenedeg.player.domain.AudioSessionManager
|
||||||
import fr.ajaury.gwenedeg.player.domain.PlaybackRepository
|
import fr.ajaury.gwenedeg.player.domain.PlaybackRepository
|
||||||
import fr.ajaury.gwenedeg.player.model.PlaybackState
|
import fr.ajaury.gwenedeg.player.model.PlaybackState
|
||||||
|
import fr.ajaury.gwenedeg.preferences.domain.PreferencesRepository
|
||||||
import fr.ajaury.gwenedeg.records.domain.RecordRepository
|
import fr.ajaury.gwenedeg.records.domain.RecordRepository
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
@@ -23,6 +24,7 @@ class PlayerViewModel(
|
|||||||
private val recordId: Int,
|
private val recordId: Int,
|
||||||
private val recordRepository: RecordRepository,
|
private val recordRepository: RecordRepository,
|
||||||
private val playbackRepository: PlaybackRepository,
|
private val playbackRepository: PlaybackRepository,
|
||||||
|
private val preferencesRepository: PreferencesRepository,
|
||||||
private val audioSessionManager: AudioSessionManager,
|
private val audioSessionManager: AudioSessionManager,
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@@ -31,10 +33,12 @@ class PlayerViewModel(
|
|||||||
val uiState: StateFlow<PlayerUiState> = combine(
|
val uiState: StateFlow<PlayerUiState> = combine(
|
||||||
record,
|
record,
|
||||||
playbackRepository.playerState,
|
playbackRepository.playerState,
|
||||||
) { record, playerState ->
|
preferencesRepository.subtitlePreferences,
|
||||||
|
) { record, playerState, subtitlePreferences ->
|
||||||
PlayerUiState(
|
PlayerUiState(
|
||||||
recordTitle = record?.title ?: Phrase(transcription = ""),
|
recordTitle = record?.title ?: Phrase(transcription = ""),
|
||||||
playerState = playerState,
|
playerState = playerState,
|
||||||
|
subtitlePreferences = subtitlePreferences,
|
||||||
)
|
)
|
||||||
}.stateIn(
|
}.stateIn(
|
||||||
scope = viewModelScope,
|
scope = viewModelScope,
|
||||||
@@ -93,6 +97,14 @@ class PlayerViewModel(
|
|||||||
playbackRepository.goToNextSentence()
|
playbackRepository.goToNextSentence()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setShowTranscription(enabled: Boolean) {
|
||||||
|
preferencesRepository.setShowTranscription(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setShowTranslation(enabled: Boolean) {
|
||||||
|
preferencesRepository.setShowTranslation(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
playbackRepository.stop()
|
playbackRepository.stop()
|
||||||
}
|
}
|
||||||
|
|||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package fr.ajaury.gwenedeg.preferences.data
|
||||||
|
|
||||||
|
import fr.ajaury.gwenedeg.preferences.domain.PreferencesRepository
|
||||||
|
import fr.ajaury.gwenedeg.preferences.model.SubtitlePreferences
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
||||||
|
internal class PreferencesRepositoryImpl : PreferencesRepository {
|
||||||
|
private val state = MutableStateFlow(SubtitlePreferences())
|
||||||
|
override val subtitlePreferences: StateFlow<SubtitlePreferences> = state.asStateFlow()
|
||||||
|
|
||||||
|
override fun setShowTranscription(enabled: Boolean) {
|
||||||
|
state.update { it.copy(showTranscription = enabled) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setShowTranslation(enabled: Boolean) {
|
||||||
|
state.update { it.copy(showTranslation = enabled) }
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package fr.ajaury.gwenedeg.preferences.domain
|
||||||
|
|
||||||
|
import fr.ajaury.gwenedeg.preferences.model.SubtitlePreferences
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
interface PreferencesRepository {
|
||||||
|
val subtitlePreferences: StateFlow<SubtitlePreferences>
|
||||||
|
|
||||||
|
fun setShowTranscription(enabled: Boolean)
|
||||||
|
|
||||||
|
fun setShowTranslation(enabled: Boolean)
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package fr.ajaury.gwenedeg.preferences.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User preferences controlling which subtitle lines are displayed on the player.
|
||||||
|
*
|
||||||
|
* @property showTranscription whether the Breton (BZH) transcription line is shown.
|
||||||
|
* @property showTranslation whether the French (FR) translation line is shown.
|
||||||
|
*/
|
||||||
|
data class SubtitlePreferences(
|
||||||
|
val showTranscription: Boolean = true,
|
||||||
|
val showTranslation: Boolean = true,
|
||||||
|
)
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package fr.ajaury.gwenedeg.preferences.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.selection.toggleable
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Subtitles
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import fr.ajaury.gwenedeg.preferences.model.SubtitlePreferences
|
||||||
|
import fr.ajaury.gwenedeg.theme.GwenedegTheme
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PreferenceToggle(
|
||||||
|
title: String,
|
||||||
|
subtitle: String,
|
||||||
|
checked: Boolean,
|
||||||
|
onCheckedChange: (Boolean) -> Unit = {},
|
||||||
|
icon: ImageVector? = null,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.toggleable(
|
||||||
|
value = checked,
|
||||||
|
role = Role.Switch,
|
||||||
|
onValueChange = onCheckedChange,
|
||||||
|
),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
if (icon != null) {
|
||||||
|
Icon(imageVector = icon, contentDescription = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(modifier = Modifier.weight(1f).padding(vertical = 8.dp)) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = subtitle,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch(
|
||||||
|
checked = checked,
|
||||||
|
onCheckedChange = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun PreferenceTogglePreview() {
|
||||||
|
GwenedegTheme {
|
||||||
|
PreferenceToggle(
|
||||||
|
title = "Subtitle",
|
||||||
|
subtitle = "Subtitle",
|
||||||
|
checked = true,
|
||||||
|
icon = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun PreferenceToggleWithIconPreview() {
|
||||||
|
GwenedegTheme {
|
||||||
|
PreferenceToggle(
|
||||||
|
title = "Subtitle",
|
||||||
|
subtitle = "Subtitle",
|
||||||
|
checked = true,
|
||||||
|
icon = Icons.Default.Subtitles,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
package fr.ajaury.gwenedeg.preferences.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.SheetState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import fr.ajaury.gwenedeg.preferences.model.SubtitlePreferences
|
||||||
|
import fr.ajaury.gwenedeg.theme.GwenedegTheme
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bottom sheet letting the user toggle the display of the Breton transcription (BZH) and the
|
||||||
|
* French translation (FR) on the player.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SubtitlePreferencesBottomSheet(
|
||||||
|
subtitlePreferences: SubtitlePreferences,
|
||||||
|
onShowTranscriptionChange: (Boolean) -> Unit = {},
|
||||||
|
onShowTranslationChange: (Boolean) -> Unit = {},
|
||||||
|
onDismiss: () -> Unit = {},
|
||||||
|
sheetState: SheetState = rememberModalBottomSheetState(),
|
||||||
|
) {
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
sheetState = sheetState,
|
||||||
|
) {
|
||||||
|
SubtitlePreferencesContent(
|
||||||
|
subtitlePreferences = subtitlePreferences,
|
||||||
|
onShowTranscriptionChange = onShowTranscriptionChange,
|
||||||
|
onShowTranslationChange = onShowTranslationChange,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SubtitlePreferencesContent(
|
||||||
|
subtitlePreferences: SubtitlePreferences,
|
||||||
|
onShowTranscriptionChange: (Boolean) -> Unit = {},
|
||||||
|
onShowTranslationChange: (Boolean) -> Unit = {},
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.padding(bottom = 24.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Sous-titres",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
|
PreferenceToggle(
|
||||||
|
title = "Transcription (BZH)",
|
||||||
|
subtitle = "Afficher la transcription bretonne",
|
||||||
|
checked = subtitlePreferences.showTranscription,
|
||||||
|
onCheckedChange = onShowTranscriptionChange,
|
||||||
|
)
|
||||||
|
PreferenceToggle(
|
||||||
|
title = "Traduction (FR)",
|
||||||
|
subtitle = "Afficher la traduction française",
|
||||||
|
checked = subtitlePreferences.showTranslation,
|
||||||
|
onCheckedChange = onShowTranslationChange,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun SubtitlePreferencesContentPreview() {
|
||||||
|
GwenedegTheme {
|
||||||
|
SubtitlePreferencesContent(
|
||||||
|
subtitlePreferences = SubtitlePreferences(
|
||||||
|
showTranscription = true,
|
||||||
|
showTranslation = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user