mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2026-10-16 04:31:49 +00:00
add video audio screen and viewmodel
This commit is contained in:
committed by
Aayush Gupta
parent
1baecc704b
commit
ac3fb1d8ab
@@ -74,4 +74,19 @@
|
||||
<string name="card">Card</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
|
||||
<!-- Video/Audio Settings -->
|
||||
<string name="default_resolution_title">Default resolution</string>
|
||||
<string name="default_popup_resolution_title">Default popup resolution</string>
|
||||
<string name="limit_mobile_data_usage_title">Limit resolution when using mobile data</string>
|
||||
<string name="show_higher_resolutions_title">Show higher resolutions</string>
|
||||
<string name="show_higher_resolutions_summary">Only some devices can play 2K/4K videos</string>
|
||||
<string name="default_video_format_title">Default video format</string>
|
||||
<string name="default_audio_format_title">Default audio format</string>
|
||||
<string name="prefer_original_audio_title">Prefer original audio</string>
|
||||
<string name="prefer_original_audio_summary">Select the original audio track regardless of the language</string>
|
||||
<string name="prefer_descriptive_audio_title">Prefer descriptive audio</string>
|
||||
<string name="prefer_descriptive_audio_summary">Select an audio track with descriptions for visually impaired people if available</string>
|
||||
<string name="best_resolution">Best resolution</string>
|
||||
<string name="limit_data_usage_none_description">No limit</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -16,6 +16,7 @@ object VideoAudioPreferences {
|
||||
const val KEY_SHOW_HIGHER_RESOLUTIONS = "show_higher_resolutions"
|
||||
const val KEY_DEFAULT_VIDEO_FORMAT = "default_video_format"
|
||||
const val KEY_DEFAULT_AUDIO_FORMAT = "default_audio_format"
|
||||
const val KEY_PREFER_ORIGINAL_AUDIO = "prefer_original_audio"
|
||||
const val KEY_PREFER_DESCRIPTIVE_AUDIO = "prefer_descriptive_audio"
|
||||
|
||||
// Special resolution values that need a localized label
|
||||
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.screen.settings
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.tooling.preview.PreviewWrapper
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import net.newpipe.app.composable.ListPreference
|
||||
import net.newpipe.app.composable.ListPreferenceEntry
|
||||
import net.newpipe.app.composable.SwitchPreference
|
||||
import net.newpipe.app.composable.TopAppBar
|
||||
import net.newpipe.app.navigation.Navigator
|
||||
import net.newpipe.app.preferences.VideoAudioPreferences
|
||||
import net.newpipe.app.preview.ThemePreviewProvider
|
||||
import net.newpipe.app.viewmodel.settings.VideoAudioSettingsViewModel
|
||||
import newpipe.shared.generated.resources.Res
|
||||
import newpipe.shared.generated.resources.best_resolution
|
||||
import newpipe.shared.generated.resources.default_audio_format_title
|
||||
import newpipe.shared.generated.resources.default_popup_resolution_title
|
||||
import newpipe.shared.generated.resources.default_resolution_title
|
||||
import newpipe.shared.generated.resources.default_video_format_title
|
||||
import newpipe.shared.generated.resources.limit_data_usage_none_description
|
||||
import newpipe.shared.generated.resources.limit_mobile_data_usage_title
|
||||
import newpipe.shared.generated.resources.prefer_descriptive_audio_summary
|
||||
import newpipe.shared.generated.resources.prefer_descriptive_audio_title
|
||||
import newpipe.shared.generated.resources.prefer_original_audio_summary
|
||||
import newpipe.shared.generated.resources.prefer_original_audio_title
|
||||
import newpipe.shared.generated.resources.settings_category_video_audio_title
|
||||
import newpipe.shared.generated.resources.show_higher_resolutions_summary
|
||||
import newpipe.shared.generated.resources.show_higher_resolutions_title
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
|
||||
@Composable
|
||||
fun VideoAudioSettingsScreen(
|
||||
navigator: Navigator = koinInject(),
|
||||
viewModel: VideoAudioSettingsViewModel = koinViewModel()
|
||||
) {
|
||||
val defaultResolution by viewModel.defaultResolution.collectAsStateWithLifecycle()
|
||||
val defaultPopupResolution by viewModel.defaultPopupResolution.collectAsStateWithLifecycle()
|
||||
val mobileDataResolution by viewModel.mobileDataResolution.collectAsStateWithLifecycle()
|
||||
val showHigherResolutions by viewModel.showHigherResolutions.collectAsStateWithLifecycle()
|
||||
val resolutionValues by viewModel.resolutionValues.collectAsStateWithLifecycle()
|
||||
val mobileDataResolutionValues by viewModel.mobileDataResolutionValues.collectAsStateWithLifecycle()
|
||||
val videoFormat by viewModel.videoFormat.collectAsStateWithLifecycle()
|
||||
val audioFormat by viewModel.audioFormat.collectAsStateWithLifecycle()
|
||||
val preferOriginalAudio by viewModel.preferOriginalAudio.collectAsStateWithLifecycle()
|
||||
val preferDescriptiveAudio by viewModel.preferDescriptiveAudio.collectAsStateWithLifecycle()
|
||||
|
||||
VideoAudioSettingsScreenContent(
|
||||
defaultResolution = defaultResolution,
|
||||
defaultPopupResolution = defaultPopupResolution,
|
||||
mobileDataResolution = mobileDataResolution,
|
||||
showHigherResolutions = showHigherResolutions,
|
||||
resolutionValues = resolutionValues,
|
||||
mobileDataResolutionValues = mobileDataResolutionValues,
|
||||
videoFormat = videoFormat,
|
||||
audioFormat = audioFormat,
|
||||
preferOriginalAudio = preferOriginalAudio,
|
||||
preferDescriptiveAudio = preferDescriptiveAudio,
|
||||
onDefaultResolutionChange = viewModel::setDefaultResolution,
|
||||
onDefaultPopupResolutionChange = viewModel::setDefaultPopupResolution,
|
||||
onMobileDataResolutionChange = viewModel::setMobileDataResolution,
|
||||
onShowHigherResolutionsChange = viewModel::setShowHigherResolutions,
|
||||
onVideoFormatChange = viewModel::setVideoFormat,
|
||||
onAudioFormatChange = viewModel::setAudioFormat,
|
||||
onPreferOriginalAudioChange = viewModel::setPreferOriginalAudio,
|
||||
onPreferDescriptiveAudioChange = viewModel::setPreferDescriptiveAudio,
|
||||
onNavigateUp = { navigator.navigateUp() }
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun VideoAudioSettingsScreenContent(
|
||||
defaultResolution: String = VideoAudioPreferences.DEFAULT_RESOLUTION,
|
||||
defaultPopupResolution: String = VideoAudioPreferences.DEFAULT_POPUP_RESOLUTION,
|
||||
mobileDataResolution: String = VideoAudioPreferences.DEFAULT_LIMIT_MOBILE_DATA_USAGE,
|
||||
showHigherResolutions: Boolean = VideoAudioPreferences.DEFAULT_SHOW_HIGHER_RESOLUTIONS,
|
||||
resolutionValues: List<String> = VideoAudioPreferences.BASE_RESOLUTIONS,
|
||||
mobileDataResolutionValues: List<String> = VideoAudioPreferences.BASE_MOBILE_DATA_RESOLUTIONS,
|
||||
videoFormat: String = VideoAudioPreferences.DEFAULT_VIDEO_FORMAT,
|
||||
audioFormat: String = VideoAudioPreferences.DEFAULT_AUDIO_FORMAT,
|
||||
preferOriginalAudio: Boolean = VideoAudioPreferences.DEFAULT_PREFER_ORIGINAL_AUDIO,
|
||||
preferDescriptiveAudio: Boolean = VideoAudioPreferences.DEFAULT_PREFER_DESCRIPTIVE_AUDIO,
|
||||
onDefaultResolutionChange: (String) -> Unit = {},
|
||||
onDefaultPopupResolutionChange: (String) -> Unit = {},
|
||||
onMobileDataResolutionChange: (String) -> Unit = {},
|
||||
onShowHigherResolutionsChange: (Boolean) -> Unit = {},
|
||||
onVideoFormatChange: (String) -> Unit = {},
|
||||
onAudioFormatChange: (String) -> Unit = {},
|
||||
onPreferOriginalAudioChange: (Boolean) -> Unit = {},
|
||||
onPreferDescriptiveAudioChange: (Boolean) -> Unit = {},
|
||||
onNavigateUp: () -> Unit = {}
|
||||
) {
|
||||
val bestResolutionLabel = stringResource(Res.string.best_resolution)
|
||||
val noLimitLabel = stringResource(Res.string.limit_data_usage_none_description)
|
||||
|
||||
fun resolutionEntry(value: String) = ListPreferenceEntry(
|
||||
value = value,
|
||||
title = if (value == VideoAudioPreferences.BEST_RESOLUTION) bestResolutionLabel else value
|
||||
)
|
||||
|
||||
fun mobileDataEntry(value: String) = ListPreferenceEntry(
|
||||
value = value,
|
||||
title = if (value == VideoAudioPreferences.LIMIT_DATA_USAGE_NONE) noLimitLabel else value
|
||||
)
|
||||
|
||||
val resolutionEntries = resolutionValues.map(::resolutionEntry)
|
||||
val mobileDataEntries = mobileDataResolutionValues.map(::mobileDataEntry)
|
||||
val videoFormatEntries = VideoAudioPreferences.VIDEO_FORMATS.map { (value, label) ->
|
||||
ListPreferenceEntry(value, label)
|
||||
}
|
||||
val audioFormatEntries = VideoAudioPreferences.AUDIO_FORMATS.map { (value, label) ->
|
||||
ListPreferenceEntry(value, label)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = stringResource(Res.string.settings_category_video_audio_title),
|
||||
onNavigateUp = onNavigateUp
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = paddingValues.calculateTopPadding())
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(WindowInsets.navigationBars.asPaddingValues())
|
||||
) {
|
||||
ListPreference(
|
||||
title = stringResource(Res.string.default_resolution_title),
|
||||
entries = resolutionEntries,
|
||||
selectedValue = defaultResolution,
|
||||
onValueSelected = onDefaultResolutionChange
|
||||
)
|
||||
ListPreference(
|
||||
title = stringResource(Res.string.default_popup_resolution_title),
|
||||
entries = resolutionEntries,
|
||||
selectedValue = defaultPopupResolution,
|
||||
onValueSelected = onDefaultPopupResolutionChange
|
||||
)
|
||||
ListPreference(
|
||||
title = stringResource(Res.string.limit_mobile_data_usage_title),
|
||||
entries = mobileDataEntries,
|
||||
selectedValue = mobileDataResolution,
|
||||
onValueSelected = onMobileDataResolutionChange
|
||||
)
|
||||
SwitchPreference(
|
||||
title = stringResource(Res.string.show_higher_resolutions_title),
|
||||
summary = stringResource(Res.string.show_higher_resolutions_summary),
|
||||
checked = showHigherResolutions,
|
||||
onCheckedChange = onShowHigherResolutionsChange
|
||||
)
|
||||
ListPreference(
|
||||
title = stringResource(Res.string.default_video_format_title),
|
||||
entries = videoFormatEntries,
|
||||
selectedValue = videoFormat,
|
||||
onValueSelected = onVideoFormatChange
|
||||
)
|
||||
ListPreference(
|
||||
title = stringResource(Res.string.default_audio_format_title),
|
||||
entries = audioFormatEntries,
|
||||
selectedValue = audioFormat,
|
||||
onValueSelected = onAudioFormatChange
|
||||
)
|
||||
SwitchPreference(
|
||||
title = stringResource(Res.string.prefer_original_audio_title),
|
||||
summary = stringResource(Res.string.prefer_original_audio_summary),
|
||||
checked = preferOriginalAudio,
|
||||
onCheckedChange = onPreferOriginalAudioChange
|
||||
)
|
||||
SwitchPreference(
|
||||
title = stringResource(Res.string.prefer_descriptive_audio_title),
|
||||
summary = stringResource(Res.string.prefer_descriptive_audio_summary),
|
||||
checked = preferDescriptiveAudio,
|
||||
onCheckedChange = onPreferDescriptiveAudioChange
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewWrapper(ThemePreviewProvider::class)
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun VideoAudioSettingsScreenPreview() {
|
||||
VideoAudioSettingsScreenContent()
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package net.newpipe.app.viewmodel.settings
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.russhwolf.settings.Settings
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import net.newpipe.app.preferences.VideoAudioPreferences
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
|
||||
@KoinViewModel
|
||||
class VideoAudioSettingsViewModel(private val settings: Settings) : ViewModel() {
|
||||
|
||||
val defaultResolution: StateFlow<String>
|
||||
field = MutableStateFlow(
|
||||
settings.getString(
|
||||
VideoAudioPreferences.KEY_DEFAULT_RESOLUTION,
|
||||
VideoAudioPreferences.DEFAULT_RESOLUTION
|
||||
)
|
||||
)
|
||||
|
||||
val defaultPopupResolution: StateFlow<String>
|
||||
field = MutableStateFlow(
|
||||
settings.getString(
|
||||
VideoAudioPreferences.KEY_DEFAULT_POPUP_RESOLUTION,
|
||||
VideoAudioPreferences.DEFAULT_POPUP_RESOLUTION
|
||||
)
|
||||
)
|
||||
|
||||
val mobileDataResolution: StateFlow<String>
|
||||
field = MutableStateFlow(
|
||||
settings.getString(
|
||||
VideoAudioPreferences.KEY_LIMIT_MOBILE_DATA_USAGE,
|
||||
VideoAudioPreferences.DEFAULT_LIMIT_MOBILE_DATA_USAGE
|
||||
)
|
||||
)
|
||||
|
||||
val showHigherResolutions: StateFlow<Boolean>
|
||||
field = MutableStateFlow(
|
||||
settings.getBoolean(
|
||||
VideoAudioPreferences.KEY_SHOW_HIGHER_RESOLUTIONS,
|
||||
VideoAudioPreferences.DEFAULT_SHOW_HIGHER_RESOLUTIONS
|
||||
)
|
||||
)
|
||||
|
||||
/** Resolution values available for the default & popup pickers; grows with [showHigherResolutions]. */
|
||||
val resolutionValues: StateFlow<List<String>>
|
||||
field = MutableStateFlow(VideoAudioPreferences.resolutions(showHigherResolutions.value))
|
||||
|
||||
/** Resolution values for the mobile-data limit picker; grows with [showHigherResolutions]. */
|
||||
val mobileDataResolutionValues: StateFlow<List<String>>
|
||||
field = MutableStateFlow(VideoAudioPreferences.mobileDataResolutions(showHigherResolutions.value))
|
||||
|
||||
val videoFormat: StateFlow<String>
|
||||
field = MutableStateFlow(
|
||||
settings.getString(
|
||||
VideoAudioPreferences.KEY_DEFAULT_VIDEO_FORMAT,
|
||||
VideoAudioPreferences.DEFAULT_VIDEO_FORMAT
|
||||
)
|
||||
)
|
||||
|
||||
val audioFormat: StateFlow<String>
|
||||
field = MutableStateFlow(
|
||||
settings.getString(
|
||||
VideoAudioPreferences.KEY_DEFAULT_AUDIO_FORMAT,
|
||||
VideoAudioPreferences.DEFAULT_AUDIO_FORMAT
|
||||
)
|
||||
)
|
||||
|
||||
val preferOriginalAudio: StateFlow<Boolean>
|
||||
field = MutableStateFlow(
|
||||
settings.getBoolean(
|
||||
VideoAudioPreferences.KEY_PREFER_ORIGINAL_AUDIO,
|
||||
VideoAudioPreferences.DEFAULT_PREFER_ORIGINAL_AUDIO
|
||||
)
|
||||
)
|
||||
|
||||
val preferDescriptiveAudio: StateFlow<Boolean>
|
||||
field = MutableStateFlow(
|
||||
settings.getBoolean(
|
||||
VideoAudioPreferences.KEY_PREFER_DESCRIPTIVE_AUDIO,
|
||||
VideoAudioPreferences.DEFAULT_PREFER_DESCRIPTIVE_AUDIO
|
||||
)
|
||||
)
|
||||
|
||||
fun setDefaultResolution(value: String) {
|
||||
settings.putString(VideoAudioPreferences.KEY_DEFAULT_RESOLUTION, value)
|
||||
defaultResolution.value = value
|
||||
}
|
||||
|
||||
fun setDefaultPopupResolution(value: String) {
|
||||
settings.putString(VideoAudioPreferences.KEY_DEFAULT_POPUP_RESOLUTION, value)
|
||||
defaultPopupResolution.value = value
|
||||
}
|
||||
|
||||
fun setMobileDataResolution(value: String) {
|
||||
settings.putString(VideoAudioPreferences.KEY_LIMIT_MOBILE_DATA_USAGE, value)
|
||||
mobileDataResolution.value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggling higher resolutions rebuilds the pickers' option lists. When turning the option
|
||||
* off, any currently selected high resolution is reset to the top entry, matching the legacy
|
||||
* screen (which called `setValueIndex(0)`).
|
||||
*/
|
||||
fun setShowHigherResolutions(value: Boolean) {
|
||||
settings.putBoolean(VideoAudioPreferences.KEY_SHOW_HIGHER_RESOLUTIONS, value)
|
||||
showHigherResolutions.value = value
|
||||
resolutionValues.value = VideoAudioPreferences.resolutions(value)
|
||||
mobileDataResolutionValues.value = VideoAudioPreferences.mobileDataResolutions(value)
|
||||
|
||||
if (!value) {
|
||||
if (defaultResolution.value in VideoAudioPreferences.HIGH_RESOLUTIONS) {
|
||||
setDefaultResolution(VideoAudioPreferences.BEST_RESOLUTION)
|
||||
}
|
||||
if (defaultPopupResolution.value in VideoAudioPreferences.HIGH_RESOLUTIONS) {
|
||||
setDefaultPopupResolution(VideoAudioPreferences.BEST_RESOLUTION)
|
||||
}
|
||||
if (mobileDataResolution.value in VideoAudioPreferences.HIGH_RESOLUTIONS) {
|
||||
setMobileDataResolution(VideoAudioPreferences.LIMIT_DATA_USAGE_NONE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setVideoFormat(value: String) {
|
||||
settings.putString(VideoAudioPreferences.KEY_DEFAULT_VIDEO_FORMAT, value)
|
||||
videoFormat.value = value
|
||||
}
|
||||
|
||||
fun setAudioFormat(value: String) {
|
||||
settings.putString(VideoAudioPreferences.KEY_DEFAULT_AUDIO_FORMAT, value)
|
||||
audioFormat.value = value
|
||||
}
|
||||
|
||||
fun setPreferOriginalAudio(value: Boolean) {
|
||||
settings.putBoolean(VideoAudioPreferences.KEY_PREFER_ORIGINAL_AUDIO, value)
|
||||
preferOriginalAudio.value = value
|
||||
}
|
||||
|
||||
fun setPreferDescriptiveAudio(value: Boolean) {
|
||||
settings.putBoolean(VideoAudioPreferences.KEY_PREFER_DESCRIPTIVE_AUDIO, value)
|
||||
preferDescriptiveAudio.value = value
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user