Compare commits

...

24 commits

Author SHA1 Message Date
Luqman Fadlli
31d6272091
Merge 978c775954 into b60995324b 2026-07-26 13:20:29 +05:30
tapframe
b60995324b Merge branch 'cmp-rewrite' of https://github.com/NuvioMedia/NuvioMobile into cmp-rewrite 2026-07-26 09:56:37 +05:30
Nayif
f1e4f88bc3
Merge pull request #1497 from guuilp/fix/profile-isolation
fix: per-profile isolation for Trakt auth and storage
2026-07-26 01:44:21 +05:30
tapframe
7f254a9f2e refactor: remove catalog header accent line 2026-07-25 16:34:53 +05:30
Nayif
2fda777249
Merge pull request #1610 from Laskco/feat/cloud-library-search
feat(library): Search the cloud library
2026-07-24 19:12:46 +05:30
YLaskco
79269f461a feat(library): search the cloud library
The Cloud view only had provider and type filters, so finding a specific
release in a large debrid account meant scrolling the whole list, even
though the item names and file names are already loaded on the client.

Adds a local search field under the toolbar that narrows the already
loaded items. It matches the item name or any of its file names, since
the useful identifier is often in the filename rather than the title,
and it stacks on top of the existing provider and type filters.
2026-07-23 10:20:23 -04:00
guuilp
71961de4a2 fix: per-profile isolation for Trakt auth and storage
1. TraktAuthStorage: Added profileId parameter to loadPayload(profileId)
   and savePayload(profileId, payload) in Android and iOS actual
   implementations, passing it explicitly to ProfileScopedKey.of().

2. TraktAuthRepository: Added currentProfileId and profileGeneration
   for cross-profile operation detection. Methods now accept explicit
   profileId to isolate auth state per profile. loadFromDisk() and
   persist() use the scoped storage key.

3. TraktCredentialSync: Updated to pass profileId to currentStateForSync()
   and replaceStateFromSync().

4. ProfileRepository/LibraryRepository: Updated calls to
   TraktAuthRepository.onProfileChanged(profileId).

5. WatchedRepository/WatchProgressRepository: Fixed
   pullFromServer/forceSnapshotRefreshFromServer to pass profileId
   to TraktAuthRepository.ensureLoaded() before checking
   activeOperationGeneration.

6. SyncManager: Passes profileId to ensureLoaded().
2026-07-17 22:26:14 +00:00
Luqman Fadlli
978c775954 Merge remote-tracking branch 'upstream/cmp-rewrite' into feat-volume-boost 2026-07-15 16:24:12 +07:00
Luqman Fadlli
2465d4e6b2 Merge remote-tracking branch 'upstream/cmp-rewrite' into feat-volume-boost 2026-06-25 17:14:22 +07:00
Luqman Fadlli
a495222f12
Merge branch 'NuvioMedia:cmp-rewrite' into feat-volume-boost 2026-06-21 03:50:22 +07:00
Luqman Fadlli
a3e309fef9
Merge branch 'NuvioMedia:cmp-rewrite' into feat-volume-boost 2026-06-19 17:20:16 +07:00
Luqman Fadlli
83bc15441b
Merge branch 'NuvioMedia:cmp-rewrite' into feat-volume-boost 2026-06-12 16:17:26 +07:00
Luqman Fadlli
bfd8c3483e
Merge branch 'NuvioMedia:cmp-rewrite' into feat-volume-boost 2026-06-10 08:16:34 +07:00
Luqman Fadlli
240cde9db3 Merge remote-tracking branch 'upstream/cmp-rewrite' into feat-volume-boost 2026-06-09 11:11:16 +07:00
Luqman Fadlli
80e7373aa2 resolve MPVKit submodule 2026-06-07 20:38:46 +07:00
Luqman Fadlli
32fbe672a5
Merge branch 'NuvioMedia:cmp-rewrite' into feat-volume-boost 2026-06-07 19:05:11 +07:00
Luqman Fadlli
86e35fd2fa Merge remote-tracking branch 'upstream/cmp-rewrite' into feat-volume-boost
This patch starts from the uploaded upstream refactor source and reapplies the working VolumeBoost implementation without reverting upstream MPV audio-output changes.
2026-06-07 06:59:15 +07:00
Luqman Fadlli
645a5e58a7 Merge remote-tracking branch 'upstream/cmp-rewrite' into feat-volume-boost 2026-06-07 06:52:05 +07:00
Luqman Fadlli
dc003852e1
Fix unclosed setVolume scope in MPV player bridge 2026-06-06 09:23:47 +07:00
Luqman Fadlli
220bef6d1a
fix: removing the enableOffload parameter and .setOffloadMode()
Updated `buildAudioSink` signature in `PlayerEngine.android.kt` by removing the `enableOffload` parameter and `.setOffloadMode()` call to comply with recent Media3/ExoPlayer API changes.
2026-06-06 08:06:04 +07:00
Luqman Fadlli
c075178ff8 Merge remote-tracking branch 'upstream/cmp-rewrite' into feat-volume-boost 2026-06-06 03:17:16 +07:00
Luqman Fadlli
65a8162cbb Merge branch 'cmp-rewrite' into feat-volume-boost 2026-06-04 05:37:03 +07:00
Luqman Fadlli
3afd58c3c1 Add hybrid system volume and software boost control
Implemented hybrid volume control for the player with support for volume levels up to 200%.

Changes include:
- Added combined volume APIs to the player engine controller.
- Updated player gestures so volume levels from 0–100% control the system/media volume.
- Enabled software boost only when the volume level exceeds 100%.
- Locked system/media volume to 100% while applying software boost from 101–200%.
- Added Android software gain support through the player audio pipeline.
- Added iOS MPV volume boost support through the native player bridge.
- Updated volume feedback to display boosted levels up to 200%.
- Added a distinct boosted-state indicator for volume levels above 100%.
- Preserved fallback behavior to system volume when the player engine is unavailable.

This improves volume behavior by keeping normal volume control intuitive while applying player-level amplification only beyond the standard 100% range.
2026-06-04 05:26:10 +07:00
Luqman Fadlli
123549a1a5 feat: Add 200% in-player volume boost for Android and iOS
Implemented in-player volume boost support up to 200% across Android and iOS playback engines.

Changes include:
- Added player-level volume control APIs to PlayerEngineController.
- Routed volume gestures through the active player engine instead of system volume when available.
- Added Android support by mapping boosted volume to ExoPlayer volume from 0.0 to 2.0.
- Added iOS support by exposing volume control through NuvioPlayerBridge.
- Configured MPV volume-max to 200 and mapped shared volume values to MPV volume 0–200.
- Updated player volume feedback to display boosted levels up to 200%.
- Preserved fallback behavior to system volume when the player controller is unavailable.

This keeps brightness and system volume behavior intact while allowing playback-specific volume amplification during video playback.
2026-06-03 17:41:39 +07:00
47 changed files with 268 additions and 188 deletions

2
MPVKit

@ -1 +1 @@
Subproject commit d5cf091c80368bbbc1bbf2d195fbc55d926df888 Subproject commit 97923266e43d52bbca33a911fd6a7f9a1bcf35cb

View file

@ -568,8 +568,6 @@
<string name="settings_homescreen_show_hero_description">Prikaži glavni vrtuljak na vrhu početnog zaslona.</string> <string name="settings_homescreen_show_hero_description">Prikaži glavni vrtuljak na vrhu početnog zaslona.</string>
<string name="layout_hide_unreleased">Sakrij neobjavljeni sadržaj</string> <string name="layout_hide_unreleased">Sakrij neobjavljeni sadržaj</string>
<string name="layout_hide_unreleased_sub">Sakrij filmove i serije koji još nisu objavljeni.</string> <string name="layout_hide_unreleased_sub">Sakrij filmove i serije koji još nisu objavljeni.</string>
<string name="settings_homescreen_hide_catalog_underline">Sakrij podcrtavanje u katalogu</string>
<string name="settings_homescreen_hide_catalog_underline_description">Uklonite istaknutu liniju ispod naziva kataloga i zbirki u cijeloj aplikaciji.</string>
<string name="settings_homescreen_summary">%1$d od %2$d vidljivih kataloga • Odabrano %3$d izvora za glavni odjeljak</string> <string name="settings_homescreen_summary">%1$d od %2$d vidljivih kataloga • Odabrano %3$d izvora za glavni odjeljak</string>
<string name="settings_homescreen_summary_hint">Otvorite katalog samo kada ga trebate preimenovati ili promijeniti njegov redoslijed.</string> <string name="settings_homescreen_summary_hint">Otvorite katalog samo kada ga trebate preimenovati ili promijeniti njegov redoslijed.</string>
<string name="settings_homescreen_visible">Vidljivo</string> <string name="settings_homescreen_visible">Vidljivo</string>

View file

@ -42,10 +42,14 @@ import androidx.media3.common.TrackSelectionOverride
import androidx.media3.common.text.Cue import androidx.media3.common.text.Cue
import androidx.media3.common.text.CueGroup import androidx.media3.common.text.CueGroup
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.common.audio.AudioProcessor
import androidx.media3.common.audio.BaseAudioProcessor
import androidx.media3.datasource.DataSource import androidx.media3.datasource.DataSource
import androidx.media3.datasource.DataSpec import androidx.media3.datasource.DataSpec
import androidx.media3.datasource.DefaultDataSource import androidx.media3.datasource.DefaultDataSource
import androidx.media3.datasource.TransferListener import androidx.media3.datasource.TransferListener
import androidx.media3.exoplayer.audio.AudioSink
import androidx.media3.exoplayer.audio.DefaultAudioSink
import androidx.media3.exoplayer.DefaultLoadControl import androidx.media3.exoplayer.DefaultLoadControl
import androidx.media3.exoplayer.DefaultRenderersFactory import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
@ -78,6 +82,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
import java.nio.ByteBuffer
import java.nio.ByteOrder
private const val TAG = "NuvioPlayer" private const val TAG = "NuvioPlayer"
private const val PLAYER_DIAGNOSTIC_TAG = "NuvioPlayerDiag" private const val PLAYER_DIAGNOSTIC_TAG = "NuvioPlayerDiag"
@ -257,6 +263,7 @@ private fun ExoPlayerSurface(
var decoderPriorityOverride by remember(playerSourceKey) { mutableStateOf<Int?>(null) } var decoderPriorityOverride by remember(playerSourceKey) { mutableStateOf<Int?>(null) }
var fallbackStartPositionMs by remember(playerSourceKey) { mutableStateOf<Long?>(null) } var fallbackStartPositionMs by remember(playerSourceKey) { mutableStateOf<Long?>(null) }
val effectiveDecoderPriority = decoderPriorityOverride ?: playerSettings.decoderPriority val effectiveDecoderPriority = decoderPriorityOverride ?: playerSettings.decoderPriority
val volumeBoostAudioProcessor = remember(playerSourceKey) { VolumeBoostAudioProcessor() }
val initialMediaItem = remember(playerSourceKey, externalSubtitles) { val initialMediaItem = remember(playerSourceKey, externalSubtitles) {
val subtitleConfigs = externalSubtitles.mapNotNull { subtitle -> val subtitleConfigs = externalSubtitles.mapNotNull { subtitle ->
@ -342,6 +349,7 @@ private fun ExoPlayerSurface(
shouldNormalizeCuePositionProvider = { shouldNormalizeCuePositionProvider = {
latestExternalSubtitleMimeType.value == MimeTypes.TEXT_VTT latestExternalSubtitleMimeType.value == MimeTypes.TEXT_VTT
}, },
volumeBoostAudioProcessor = volumeBoostAudioProcessor,
) )
.setExtensionRendererMode(effectiveDecoderPriority) .setExtensionRendererMode(effectiveDecoderPriority)
.setEnableDecoderFallback(true) .setEnableDecoderFallback(true)
@ -702,6 +710,27 @@ private fun ExoPlayerSurface(
exoPlayer.setPlaybackSpeed(speed) exoPlayer.setPlaybackSpeed(speed)
} }
override fun currentPlayerVolume(): PlayerAudioLevel {
val current = volumeBoostAudioProcessor.gain.coerceIn(0f, 2f)
return PlayerAudioLevel(
fraction = current,
isMuted = current <= 0.001f,
)
}
override fun setPlayerVolume(level: Float): PlayerAudioLevel {
val target = level.coerceIn(0f, 2f)
// Keep ExoPlayer's own volume at unity and apply gain in the PCM audio processor.
// ExoPlayer#setVolume is effectively a normal 0..1 output volume control on many devices,
// so values above 1 may not create audible amplification.
exoPlayer.volume = 1f
volumeBoostAudioProcessor.gain = target
return PlayerAudioLevel(
fraction = target,
isMuted = target <= 0.001f,
)
}
override fun updateNowPlayingMetadata(info: PlayerNowPlayingInfo) { override fun updateNowPlayingMetadata(info: PlayerNowPlayingInfo) {
nowPlayingController.updateMetadata(info) nowPlayingController.updateMetadata(info)
} }
@ -1794,12 +1823,60 @@ private fun ExoPlayer.logCurrentTracks(context: String) {
Log.d(TAG, "--- end logCurrentTracks ---") Log.d(TAG, "--- end logCurrentTracks ---")
} }
@androidx.annotation.OptIn(UnstableApi::class)
private class VolumeBoostAudioProcessor : BaseAudioProcessor() {
@Volatile
var gain: Float = 1f
set(value) {
field = value.coerceIn(0f, 2f)
}
override fun onConfigure(inputAudioFormat: AudioProcessor.AudioFormat): AudioProcessor.AudioFormat {
return if (inputAudioFormat.encoding == C.ENCODING_PCM_16BIT) {
inputAudioFormat
} else {
AudioProcessor.AudioFormat.NOT_SET
}
}
override fun queueInput(inputBuffer: ByteBuffer) {
val inputSize = inputBuffer.remaining()
val outputBuffer = replaceOutputBuffer(inputSize).order(ByteOrder.nativeOrder())
val input = inputBuffer.order(ByteOrder.nativeOrder())
val localGain = gain
while (input.remaining() >= 2) {
val sample = input.short.toInt()
val amplified = (sample * localGain)
.toInt()
.coerceIn(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt())
outputBuffer.putShort(amplified.toShort())
}
input.position(input.limit())
outputBuffer.flip()
}
}
@androidx.annotation.OptIn(UnstableApi::class) @androidx.annotation.OptIn(UnstableApi::class)
private class SubtitleOffsetRenderersFactory( private class SubtitleOffsetRenderersFactory(
context: Context, context: Context,
private val subtitleDelayUsProvider: () -> Long, private val subtitleDelayUsProvider: () -> Long,
private val shouldNormalizeCuePositionProvider: () -> Boolean, private val shouldNormalizeCuePositionProvider: () -> Boolean,
private val volumeBoostAudioProcessor: VolumeBoostAudioProcessor,
) : DefaultRenderersFactory(context) { ) : DefaultRenderersFactory(context) {
override fun buildAudioSink(
context: Context,
enableFloatOutput: Boolean,
enableAudioTrackPlaybackParams: Boolean,
): AudioSink? {
return DefaultAudioSink.Builder(context)
.setEnableFloatOutput(false)
.setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
.setAudioProcessors(arrayOf(volumeBoostAudioProcessor))
.build()
}
override fun buildTextRenderers( override fun buildTextRenderers(
context: Context, context: Context,
output: TextOutput, output: TextOutput,

View file

@ -570,8 +570,6 @@
<string name="settings_homescreen_show_hero_description">Покажи въртележка в горната част на началото.</string> <string name="settings_homescreen_show_hero_description">Покажи въртележка в горната част на началото.</string>
<string name="layout_hide_unreleased">Скрий невипуснатото съдържание</string> <string name="layout_hide_unreleased">Скрий невипуснатото съдържание</string>
<string name="layout_hide_unreleased_sub">Скрий филми и сериали, които все още не са излезли.</string> <string name="layout_hide_unreleased_sub">Скрий филми и сериали, които все още не са излезли.</string>
<string name="settings_homescreen_hide_catalog_underline">Скрий подчертаването на каталога</string>
<string name="settings_homescreen_hide_catalog_underline_description">Премахни акцентната линия под заглавията на каталозите и колекциите в цялото приложение.</string>
<string name="settings_homescreen_summary">%1$d от %2$d каталога видими • %3$d основни извора избрани</string> <string name="settings_homescreen_summary">%1$d от %2$d каталога видими • %3$d основни извора избрани</string>
<string name="settings_homescreen_summary_hint">Отворете каталог само когато трябва да го преименувате или пренаредите.</string> <string name="settings_homescreen_summary_hint">Отворете каталог само когато трябва да го преименувате или пренаредите.</string>
<string name="settings_homescreen_visible">Видим</string> <string name="settings_homescreen_visible">Видим</string>

View file

@ -582,8 +582,6 @@
<string name="settings_homescreen_show_hero_description">Zobrazit hlavní karusel v horní části domovské stránky.</string> <string name="settings_homescreen_show_hero_description">Zobrazit hlavní karusel v horní části domovské stránky.</string>
<string name="layout_hide_unreleased">Skrýt nevydaný obsah</string> <string name="layout_hide_unreleased">Skrýt nevydaný obsah</string>
<string name="layout_hide_unreleased_sub">Skrýt filmy a pořady, které ještě neměly premiéru.</string> <string name="layout_hide_unreleased_sub">Skrýt filmy a pořady, které ještě neměly premiéru.</string>
<string name="settings_homescreen_hide_catalog_underline">Skrýt podtržení katalogu</string>
<string name="settings_homescreen_hide_catalog_underline_description">Odstranit barevnou linii pod názvy katalogů a sbírek v celé aplikaci.</string>
<string name="settings_homescreen_summary">%1$d z %2$d katalogů viditelných • %3$d zdrojů Hero sekce vybráno</string> <string name="settings_homescreen_summary">%1$d z %2$d katalogů viditelných • %3$d zdrojů Hero sekce vybráno</string>
<string name="settings_homescreen_summary_hint">Otevřete katalog pouze tehdy, když jej potřebujete přejmenovat nebo změnit jeho pořadí.</string> <string name="settings_homescreen_summary_hint">Otevřete katalog pouze tehdy, když jej potřebujete přejmenovat nebo změnit jeho pořadí.</string>
<string name="settings_homescreen_visible">Viditelné</string> <string name="settings_homescreen_visible">Viditelné</string>

View file

@ -1723,8 +1723,6 @@
<string name="settings_fusion_badges_empty">Δεν έχουν εισαχθεί URL badge Fusion.</string> <string name="settings_fusion_badges_empty">Δεν έχουν εισαχθεί URL badge Fusion.</string>
<string name="settings_fusion_badges_summary">%1$d/%2$d URLs, %3$d active Fusion badges</string> <string name="settings_fusion_badges_summary">%1$d/%2$d URLs, %3$d active Fusion badges</string>
<string name="settings_hide_secret">Απόκρυψη value</string> <string name="settings_hide_secret">Απόκρυψη value</string>
<string name="settings_homescreen_hide_catalog_underline">Απόκρυψη Catalog Underline</string>
<string name="settings_homescreen_hide_catalog_underline_description">Αφαίρεση της γραμμής τονισμού κάτω από τους τίτλους καταλόγων και συλλογών σε όλη την εφαρμογή.</string>
<string name="settings_integrations_debrid_description">Συνδέστε λογαριασμούς για συνδέσμους και πρόσβαση στη βιβλιοθήκη</string> <string name="settings_integrations_debrid_description">Συνδέστε λογαριασμούς για συνδέσμους και πρόσβαση στη βιβλιοθήκη</string>
<string name="settings_licenses_attributions_exoplayer_body">Χρησιμοποιείται για αναπαραγωγή σε εκδόσεις Android.</string> <string name="settings_licenses_attributions_exoplayer_body">Χρησιμοποιείται για αναπαραγωγή σε εκδόσεις Android.</string>
<string name="settings_licenses_attributions_exoplayer_license">Licensed under the Apache License, Έκδοση 2.0.</string> <string name="settings_licenses_attributions_exoplayer_license">Licensed under the Apache License, Έκδοση 2.0.</string>

View file

@ -582,8 +582,6 @@
<string name="settings_homescreen_show_hero_description">Mostrar un carrusel destacado en la parte superior del inicio. Elige hasta 2 catálogos de origen abajo.</string> <string name="settings_homescreen_show_hero_description">Mostrar un carrusel destacado en la parte superior del inicio. Elige hasta 2 catálogos de origen abajo.</string>
<string name="layout_hide_unreleased">Ocultar contenido inédito</string> <string name="layout_hide_unreleased">Ocultar contenido inédito</string>
<string name="layout_hide_unreleased_sub">Oculta películas y programas que aún no se han estrenado.</string> <string name="layout_hide_unreleased_sub">Oculta películas y programas que aún no se han estrenado.</string>
<string name="settings_homescreen_hide_catalog_underline">Ocultar subrayado del catálogo</string>
<string name="settings_homescreen_hide_catalog_underline_description">Elimine la línea de acento debajo de los títulos de catálogos y colecciones en toda la aplicación.</string>
<string name="settings_homescreen_summary">%1$d de %2$d catálogos visibles • %3$d fuentes de Destacado seleccionadas</string> <string name="settings_homescreen_summary">%1$d de %2$d catálogos visibles • %3$d fuentes de Destacado seleccionadas</string>
<string name="settings_homescreen_summary_hint">Abre un catálogo solo cuando necesites cambiarle el nombre o reordenarlo.</string> <string name="settings_homescreen_summary_hint">Abre un catálogo solo cuando necesites cambiarle el nombre o reordenarlo.</string>
<string name="settings_homescreen_visible">Visible</string> <string name="settings_homescreen_visible">Visible</string>

View file

@ -592,8 +592,6 @@
<string name="settings_homescreen_show_hero_description">Afficher un carrousel Hero en vedette en haut de laccueil. Choisissez jusquà 2 catalogues sources ci-dessous.</string> <string name="settings_homescreen_show_hero_description">Afficher un carrousel Hero en vedette en haut de laccueil. Choisissez jusquà 2 catalogues sources ci-dessous.</string>
<string name="layout_hide_unreleased">Masquer le contenu non sorti</string> <string name="layout_hide_unreleased">Masquer le contenu non sorti</string>
<string name="layout_hide_unreleased_sub">Masque les films et séries qui ne sont pas encore sortis.</string> <string name="layout_hide_unreleased_sub">Masque les films et séries qui ne sont pas encore sortis.</string>
<string name="settings_homescreen_hide_catalog_underline">Masquer le soulignement des catalogues</string>
<string name="settings_homescreen_hide_catalog_underline_description">Supprime la ligne daccent sous les titres de catalogues et de collections dans toute lapp.</string>
<string name="settings_homescreen_summary">%1$d sur %2$d catalogues visibles • %3$d sources Hero sélectionnées</string> <string name="settings_homescreen_summary">%1$d sur %2$d catalogues visibles • %3$d sources Hero sélectionnées</string>
<string name="settings_homescreen_summary_hint">Ouvrez un catalogue uniquement si vous avez besoin de le renommer ou de le réorganiser.</string> <string name="settings_homescreen_summary_hint">Ouvrez un catalogue uniquement si vous avez besoin de le renommer ou de le réorganiser.</string>
<string name="settings_homescreen_visible">Visible</string> <string name="settings_homescreen_visible">Visible</string>

View file

@ -525,8 +525,6 @@
<string name="settings_homescreen_show_hero_description">Kiemelt galéria (carousel) megjelenítése a kezdőlap tetején.</string> <string name="settings_homescreen_show_hero_description">Kiemelt galéria (carousel) megjelenítése a kezdőlap tetején.</string>
<string name="layout_hide_unreleased">Meg nem jelent tartalmak elrejtése</string> <string name="layout_hide_unreleased">Meg nem jelent tartalmak elrejtése</string>
<string name="layout_hide_unreleased_sub">A még meg nem jelent filmek és sorozatok elrejtése.</string> <string name="layout_hide_unreleased_sub">A még meg nem jelent filmek és sorozatok elrejtése.</string>
<string name="settings_homescreen_hide_catalog_underline">Katalógus aláhúzás elrejtése</string>
<string name="settings_homescreen_hide_catalog_underline_description">A kiemelő vonal eltávolítása a katalógusok és gyűjtemények címei alól az alkalmazás egészében.</string>
<string name="settings_homescreen_summary">%1$d / %2$d katalógus látható • %3$d kiemelt forrás kiválasztva</string> <string name="settings_homescreen_summary">%1$d / %2$d katalógus látható • %3$d kiemelt forrás kiválasztva</string>
<string name="settings_homescreen_summary_hint">Csak akkor nyiss meg egy katalógust, ha át szeretnéd nevezni vagy át szeretnéd rendezni.</string> <string name="settings_homescreen_summary_hint">Csak akkor nyiss meg egy katalógust, ha át szeretnéd nevezni vagy át szeretnéd rendezni.</string>
<string name="settings_homescreen_visible">Látható</string> <string name="settings_homescreen_visible">Látható</string>

View file

@ -547,8 +547,6 @@
<string name="settings_homescreen_show_hero_description">Tampilkan karousel hero di bagian atas beranda.</string> <string name="settings_homescreen_show_hero_description">Tampilkan karousel hero di bagian atas beranda.</string>
<string name="layout_hide_unreleased">Sembunyikan Konten Belum Rilis</string> <string name="layout_hide_unreleased">Sembunyikan Konten Belum Rilis</string>
<string name="layout_hide_unreleased_sub">Sembunyikan film dan serial yang belum dirilis.</string> <string name="layout_hide_unreleased_sub">Sembunyikan film dan serial yang belum dirilis.</string>
<string name="settings_homescreen_hide_catalog_underline">Sembunyikan Garis Bawah Katalog</string>
<string name="settings_homescreen_hide_catalog_underline_description">Hapus garis aksen di bawah judul katalog dan koleksi di seluruh aplikasi.</string>
<string name="settings_homescreen_summary">%1$d dari %2$d katalog terlihat • %3$d sumber hero dipilih</string> <string name="settings_homescreen_summary">%1$d dari %2$d katalog terlihat • %3$d sumber hero dipilih</string>
<string name="settings_homescreen_summary_hint">Buka katalog hanya jika perlu mengganti nama atau mengurutkannya.</string> <string name="settings_homescreen_summary_hint">Buka katalog hanya jika perlu mengganti nama atau mengurutkannya.</string>
<string name="settings_homescreen_visible">Terlihat</string> <string name="settings_homescreen_visible">Terlihat</string>

View file

@ -547,8 +547,6 @@
<string name="settings_homescreen_show_hero_description">Tampilkan karousel hero di bagian atas beranda.</string> <string name="settings_homescreen_show_hero_description">Tampilkan karousel hero di bagian atas beranda.</string>
<string name="layout_hide_unreleased">Sembunyikan Konten Belum Rilis</string> <string name="layout_hide_unreleased">Sembunyikan Konten Belum Rilis</string>
<string name="layout_hide_unreleased_sub">Sembunyikan film dan serial yang belum dirilis.</string> <string name="layout_hide_unreleased_sub">Sembunyikan film dan serial yang belum dirilis.</string>
<string name="settings_homescreen_hide_catalog_underline">Sembunyikan Garis Bawah Katalog</string>
<string name="settings_homescreen_hide_catalog_underline_description">Hapus garis aksen di bawah judul katalog dan koleksi di seluruh aplikasi.</string>
<string name="settings_homescreen_summary">%1$d dari %2$d katalog terlihat • %3$d sumber hero dipilih</string> <string name="settings_homescreen_summary">%1$d dari %2$d katalog terlihat • %3$d sumber hero dipilih</string>
<string name="settings_homescreen_summary_hint">Buka katalog hanya jika perlu mengganti nama atau mengurutkannya.</string> <string name="settings_homescreen_summary_hint">Buka katalog hanya jika perlu mengganti nama atau mengurutkannya.</string>
<string name="settings_homescreen_visible">Terlihat</string> <string name="settings_homescreen_visible">Terlihat</string>

View file

@ -1315,8 +1315,6 @@
<string name="settings_appearance_liquid_glass_description">Usa la barra dei pannelli nativa dell'iPhone su iOS 26 e versioni successive.</string> <string name="settings_appearance_liquid_glass_description">Usa la barra dei pannelli nativa dell'iPhone su iOS 26 e versioni successive.</string>
<string name="layout_hide_unreleased">Nascondi contenuti non rilasciati</string> <string name="layout_hide_unreleased">Nascondi contenuti non rilasciati</string>
<string name="layout_hide_unreleased_sub">Nascondi i film e le serie TV che non sono ancora stati rilasciati.</string> <string name="layout_hide_unreleased_sub">Nascondi i film e le serie TV che non sono ancora stati rilasciati.</string>
<string name="settings_homescreen_hide_catalog_underline">Nascondi sottolineatura catalogo</string>
<string name="settings_homescreen_hide_catalog_underline_description">Rimuove la linea di evidenziazione sotto i titoli dei cataloghi e delle collezioni in tutta l'app.</string>
<string name="settings_hide_secret">Nascondi valore</string> <string name="settings_hide_secret">Nascondi valore</string>
<string name="settings_show_secret">Mostra valore</string> <string name="settings_show_secret">Mostra valore</string>
<string name="settings_continue_watching_blur_next_up_description">Sfoca le miniature del prossimo episodio in Inizia a guardare per evitare spoiler.</string> <string name="settings_continue_watching_blur_next_up_description">Sfoca le miniature del prossimo episodio in Inizia a guardare per evitare spoiler.</string>

View file

@ -582,8 +582,6 @@
<string name="settings_homescreen_show_hero_description">ホーム上部にヒーローカルーセルを表示します。</string> <string name="settings_homescreen_show_hero_description">ホーム上部にヒーローカルーセルを表示します。</string>
<string name="layout_hide_unreleased">未公開コンテンツを非表示</string> <string name="layout_hide_unreleased">未公開コンテンツを非表示</string>
<string name="layout_hide_unreleased_sub">まだ公開されていない映画やシリーズを非表示にします。</string> <string name="layout_hide_unreleased_sub">まだ公開されていない映画やシリーズを非表示にします。</string>
<string name="settings_homescreen_hide_catalog_underline">カタログの下線を非表示</string>
<string name="settings_homescreen_hide_catalog_underline_description">アプリ全体のカタログ・コレクションタイトル下のアクセントラインを非表示にします。</string>
<string name="settings_homescreen_summary">%1$d / %2$d カタログ表示中 • %3$d ヒーローソース選択中</string> <string name="settings_homescreen_summary">%1$d / %2$d カタログ表示中 • %3$d ヒーローソース選択中</string>
<string name="settings_homescreen_summary_hint">カタログは名前の変更や並べ替えが必要なときのみ開いてください。</string> <string name="settings_homescreen_summary_hint">カタログは名前の変更や並べ替えが必要なときのみ開いてください。</string>
<string name="settings_homescreen_visible">表示</string> <string name="settings_homescreen_visible">表示</string>

View file

@ -531,8 +531,6 @@
<string name="settings_homescreen_show_hero_description">Vis hero-karusell øverst på hjemskjermen.</string> <string name="settings_homescreen_show_hero_description">Vis hero-karusell øverst på hjemskjermen.</string>
<string name="layout_hide_unreleased">Skjul ikke-utgitt innhold</string> <string name="layout_hide_unreleased">Skjul ikke-utgitt innhold</string>
<string name="layout_hide_unreleased_sub">Skjul filmer og serier som ikke er utgitt ennå.</string> <string name="layout_hide_unreleased_sub">Skjul filmer og serier som ikke er utgitt ennå.</string>
<string name="settings_homescreen_hide_catalog_underline">Skjul katalog-understrek</string>
<string name="settings_homescreen_hide_catalog_underline_description">Fjern aksentlinjen under katalog- og samlingstitler.</string>
<string name="settings_homescreen_summary">%1$d av %2$d kataloger synlige • %3$d hero-kilder valgt</string> <string name="settings_homescreen_summary">%1$d av %2$d kataloger synlige • %3$d hero-kilder valgt</string>
<string name="settings_homescreen_summary_hint">Åpne en katalog kun når du skal gi nytt navn eller endre rekkefølge.</string> <string name="settings_homescreen_summary_hint">Åpne en katalog kun når du skal gi nytt navn eller endre rekkefølge.</string>
<string name="settings_homescreen_visible">Synlig</string> <string name="settings_homescreen_visible">Synlig</string>

View file

@ -600,8 +600,6 @@
<string name="settings_homescreen_show_hero_description">Toon herocarrousel bovenaan de startpagina.</string> <string name="settings_homescreen_show_hero_description">Toon herocarrousel bovenaan de startpagina.</string>
<string name="layout_hide_unreleased">Niet-uitgebrachte inhoud verbergen</string> <string name="layout_hide_unreleased">Niet-uitgebrachte inhoud verbergen</string>
<string name="layout_hide_unreleased_sub">Verberg films en series die nog niet zijn uitgebracht.</string> <string name="layout_hide_unreleased_sub">Verberg films en series die nog niet zijn uitgebracht.</string>
<string name="settings_homescreen_hide_catalog_underline">Catalogusonderstreping verbergen</string>
<string name="settings_homescreen_hide_catalog_underline_description">Verwijder de accentlijn onder catalogus- en collectietitels in de hele app.</string>
<string name="settings_homescreen_summary">%1$d van %2$d catalogi zichtbaar • %3$d herobronnen geselecteerd</string> <string name="settings_homescreen_summary">%1$d van %2$d catalogi zichtbaar • %3$d herobronnen geselecteerd</string>
<string name="settings_homescreen_summary_hint">Open een catalogus alleen als je deze wilt hernoemen of herordenen.</string> <string name="settings_homescreen_summary_hint">Open een catalogus alleen als je deze wilt hernoemen of herordenen.</string>
<string name="settings_homescreen_visible">Zichtbaar</string> <string name="settings_homescreen_visible">Zichtbaar</string>

View file

@ -547,8 +547,6 @@
<string name="settings_homescreen_show_hero_description">Wyświetl wyróżnioną karuzelę hero na górze ekranu głównego. Wybierz do 2 katalogów źródłowych poniżej.</string> <string name="settings_homescreen_show_hero_description">Wyświetl wyróżnioną karuzelę hero na górze ekranu głównego. Wybierz do 2 katalogów źródłowych poniżej.</string>
<string name="layout_hide_unreleased">Ukryj niewydane treści</string> <string name="layout_hide_unreleased">Ukryj niewydane treści</string>
<string name="layout_hide_unreleased_sub">Ukryj filmy i seriale, które nie zostały jeszcze wydane.</string> <string name="layout_hide_unreleased_sub">Ukryj filmy i seriale, które nie zostały jeszcze wydane.</string>
<string name="settings_homescreen_hide_catalog_underline">Ukryj podkreślenie katalogu</string>
<string name="settings_homescreen_hide_catalog_underline_description">Usuń linię akcentu pod tytułami katalogów i kolekcji w całej aplikacji.</string>
<string name="settings_homescreen_summary">%1$d z %2$d katalogów widocznych • %3$d źródeł hero wybranych</string> <string name="settings_homescreen_summary">%1$d z %2$d katalogów widocznych • %3$d źródeł hero wybranych</string>
<string name="settings_homescreen_summary_hint">Otwórz katalog tylko wtedy, gdy chcesz zmienić jego nazwę lub kolejność.</string> <string name="settings_homescreen_summary_hint">Otwórz katalog tylko wtedy, gdy chcesz zmienić jego nazwę lub kolejność.</string>
<string name="settings_homescreen_visible">Widoczne</string> <string name="settings_homescreen_visible">Widoczne</string>

View file

@ -532,8 +532,6 @@
<string name="settings_homescreen_show_hero_description">Exibir carrossel hero no topo da página inicial.</string> <string name="settings_homescreen_show_hero_description">Exibir carrossel hero no topo da página inicial.</string>
<string name="layout_hide_unreleased">Ocultar Conteúdo Não Lançado</string> <string name="layout_hide_unreleased">Ocultar Conteúdo Não Lançado</string>
<string name="layout_hide_unreleased_sub">Ocultar filmes e séries que ainda não foram lançados.</string> <string name="layout_hide_unreleased_sub">Ocultar filmes e séries que ainda não foram lançados.</string>
<string name="settings_homescreen_hide_catalog_underline">Ocultar Linha Sublinhada do Catálogo</string>
<string name="settings_homescreen_hide_catalog_underline_description">Remover a linha de destaque abaixo dos títulos de catálogo e coleção em todo o app.</string>
<string name="settings_homescreen_summary">%1$d de %2$d catálogos visíveis • %3$d fontes de hero selecionadas</string> <string name="settings_homescreen_summary">%1$d de %2$d catálogos visíveis • %3$d fontes de hero selecionadas</string>
<string name="settings_homescreen_summary_hint">Abra um catálogo apenas quando precisar renomeá-lo ou reordená-lo.</string> <string name="settings_homescreen_summary_hint">Abra um catálogo apenas quando precisar renomeá-lo ou reordená-lo.</string>
<string name="settings_homescreen_visible">Visível</string> <string name="settings_homescreen_visible">Visível</string>

View file

@ -576,8 +576,6 @@
<string name="settings_homescreen_show_hero_description">Apresenta o carrossel de destaques no topo da página inicial.</string> <string name="settings_homescreen_show_hero_description">Apresenta o carrossel de destaques no topo da página inicial.</string>
<string name="layout_hide_unreleased">Ocultar conteúdo não lançado</string> <string name="layout_hide_unreleased">Ocultar conteúdo não lançado</string>
<string name="layout_hide_unreleased_sub">Ocultar filmes e séries que ainda não foram lançados.</string> <string name="layout_hide_unreleased_sub">Ocultar filmes e séries que ainda não foram lançados.</string>
<string name="settings_homescreen_hide_catalog_underline">Ocultar sublinhado dos catálogos</string>
<string name="settings_homescreen_hide_catalog_underline_description">Remove a linha de destaque sob os títulos de catálogos e coleções em toda a app.</string>
<string name="settings_homescreen_summary">%1$d de %2$d catálogos visíveis • %3$d fontes de destaque selecionadas</string> <string name="settings_homescreen_summary">%1$d de %2$d catálogos visíveis • %3$d fontes de destaque selecionadas</string>
<string name="settings_homescreen_summary_hint">Abre um catálogo apenas quando precisares de o renomear ou reordenar.</string> <string name="settings_homescreen_summary_hint">Abre um catálogo apenas quando precisares de o renomear ou reordenar.</string>
<string name="settings_homescreen_visible">Visível</string> <string name="settings_homescreen_visible">Visível</string>

View file

@ -600,8 +600,6 @@
<string name="settings_homescreen_show_hero_description">Afișează un carusel în partea de sus a ecranului principal.</string> <string name="settings_homescreen_show_hero_description">Afișează un carusel în partea de sus a ecranului principal.</string>
<string name="layout_hide_unreleased">Ascunde conținutul nelansat</string> <string name="layout_hide_unreleased">Ascunde conținutul nelansat</string>
<string name="layout_hide_unreleased_sub">Ascunde filmele și serialele care nu au fost lansate încă.</string> <string name="layout_hide_unreleased_sub">Ascunde filmele și serialele care nu au fost lansate încă.</string>
<string name="settings_homescreen_hide_catalog_underline">Ascunde sublinierea catalogului</string>
<string name="settings_homescreen_hide_catalog_underline_description">Elimină linia de accent de sub titlurile cataloagelor și colecțiilor din întreaga aplicație.</string>
<string name="settings_homescreen_summary">%1$d din %2$d cataloage vizibile • %3$d surse hero selectate</string> <string name="settings_homescreen_summary">%1$d din %2$d cataloage vizibile • %3$d surse hero selectate</string>
<string name="settings_homescreen_summary_hint">Deschide un catalog doar când trebuie să îl redenumești sau să îl rearanjezi.</string> <string name="settings_homescreen_summary_hint">Deschide un catalog doar când trebuie să îl redenumești sau să îl rearanjezi.</string>
<string name="settings_homescreen_visible">Vizibil</string> <string name="settings_homescreen_visible">Vizibil</string>

View file

@ -571,8 +571,6 @@
<string name="settings_homescreen_show_hero_description">Zobraziť hero karusel v hornej časti domovskej obrazovky.</string> <string name="settings_homescreen_show_hero_description">Zobraziť hero karusel v hornej časti domovskej obrazovky.</string>
<string name="layout_hide_unreleased">Skryť nevydaný obsah</string> <string name="layout_hide_unreleased">Skryť nevydaný obsah</string>
<string name="layout_hide_unreleased_sub">Skryť filmy a seriály, ktoré ešte neboli vydané.</string> <string name="layout_hide_unreleased_sub">Skryť filmy a seriály, ktoré ešte neboli vydané.</string>
<string name="settings_homescreen_hide_catalog_underline">Skryť podčiarknutie katalógu</string>
<string name="settings_homescreen_hide_catalog_underline_description">Odstrániť zvýrazňovací riadok pod názvami katalógov a kolekcií v celej aplikácii.</string>
<string name="settings_homescreen_summary">%1$d z %2$d katalógov viditeľných • %3$d zdrojov hero sekcie vybraných</string> <string name="settings_homescreen_summary">%1$d z %2$d katalógov viditeľných • %3$d zdrojov hero sekcie vybraných</string>
<string name="settings_homescreen_summary_hint">Otvorte katalóg iba vtedy, keď ho potrebujete premenovať alebo zmeniť poradie.</string> <string name="settings_homescreen_summary_hint">Otvorte katalóg iba vtedy, keď ho potrebujete premenovať alebo zmeniť poradie.</string>
<string name="settings_homescreen_visible">Viditeľné</string> <string name="settings_homescreen_visible">Viditeľné</string>

View file

@ -537,8 +537,6 @@
<string name="settings_homescreen_show_hero_description">Ana sayfanın üstünde öne çıkan bir kaydırmalı alan göster. Aşağıdan en fazla 2 kaynak katalog seç.</string> <string name="settings_homescreen_show_hero_description">Ana sayfanın üstünde öne çıkan bir kaydırmalı alan göster. Aşağıdan en fazla 2 kaynak katalog seç.</string>
<string name="layout_hide_unreleased">Henüz Yayınlanmamış İçerikleri Gizle</string> <string name="layout_hide_unreleased">Henüz Yayınlanmamış İçerikleri Gizle</string>
<string name="layout_hide_unreleased_sub">Henüz yayınlanmamış film ve dizileri gizle.</string> <string name="layout_hide_unreleased_sub">Henüz yayınlanmamış film ve dizileri gizle.</string>
<string name="settings_homescreen_hide_catalog_underline">Katalog Alt Çizgisini Gizle</string>
<string name="settings_homescreen_hide_catalog_underline_description">Uygulama genelinde katalog ve koleksiyon başlıklarının altındaki vurgu çizgisini kaldır.</string>
<string name="settings_homescreen_summary">%1$d / %2$d katalog görünür • %3$d öne çıkan kaynak seçili</string> <string name="settings_homescreen_summary">%1$d / %2$d katalog görünür • %3$d öne çıkan kaynak seçili</string>
<string name="settings_homescreen_summary_hint">Bir kataloğu yalnızca adını değiştirmek veya sıralamak gerektiğinde aç.</string> <string name="settings_homescreen_summary_hint">Bir kataloğu yalnızca adını değiştirmek veya sıralamak gerektiğinde aç.</string>
<string name="settings_homescreen_visible">Görünür</string> <string name="settings_homescreen_visible">Görünür</string>

View file

@ -579,8 +579,6 @@
<string name="settings_homescreen_show_hero_description">Hiển thị Hero ở đầu Trang chủ</string> <string name="settings_homescreen_show_hero_description">Hiển thị Hero ở đầu Trang chủ</string>
<string name="layout_hide_unreleased">Ẩn nội dung chưa phát hành</string> <string name="layout_hide_unreleased">Ẩn nội dung chưa phát hành</string>
<string name="layout_hide_unreleased_sub">Ẩn phim và chương trình chưa phát hành</string> <string name="layout_hide_unreleased_sub">Ẩn phim và chương trình chưa phát hành</string>
<string name="settings_homescreen_hide_catalog_underline">Ẩn gạch chân danh mục</string>
<string name="settings_homescreen_hide_catalog_underline_description">Ẩn gạch chân dưới tiêu đề danh mục và bộ sưu tập</string>
<string name="settings_homescreen_summary">%1$d/%2$d danh mục • %3$d nguồn Hero</string> <string name="settings_homescreen_summary">%1$d/%2$d danh mục • %3$d nguồn Hero</string>
<string name="settings_homescreen_summary_hint">Chỉ mở danh mục khi cần đổi tên hoặc sắp xếp</string> <string name="settings_homescreen_summary_hint">Chỉ mở danh mục khi cần đổi tên hoặc sắp xếp</string>
<string name="settings_homescreen_visible">Hiển thị</string> <string name="settings_homescreen_visible">Hiển thị</string>

View file

@ -609,8 +609,6 @@
<string name="layout_hide_unreleased_sub">Hide movies and shows that haven't been released yet.</string> <string name="layout_hide_unreleased_sub">Hide movies and shows that haven't been released yet.</string>
<string name="layout_catalog_type">Show Catalog Type</string> <string name="layout_catalog_type">Show Catalog Type</string>
<string name="layout_catalog_type_sub">Show type suffix next to catalog name (Movie/Series).</string> <string name="layout_catalog_type_sub">Show type suffix next to catalog name (Movie/Series).</string>
<string name="settings_homescreen_hide_catalog_underline">Hide Catalog Underline</string>
<string name="settings_homescreen_hide_catalog_underline_description">Remove the accent line under catalog and collection titles throughout the app.</string>
<string name="settings_homescreen_summary">%1$d of %2$d catalogs visible • %3$d hero sources selected</string> <string name="settings_homescreen_summary">%1$d of %2$d catalogs visible • %3$d hero sources selected</string>
<string name="settings_homescreen_summary_hint">Open a catalog only when you need to rename or reorder it.</string> <string name="settings_homescreen_summary_hint">Open a catalog only when you need to rename or reorder it.</string>
<string name="settings_homescreen_visible">Visible</string> <string name="settings_homescreen_visible">Visible</string>
@ -1658,6 +1656,7 @@
<string name="cloud_library_playable_file_count">%1$d playable files</string> <string name="cloud_library_playable_file_count">%1$d playable files</string>
<string name="cloud_library_provider_all">All</string> <string name="cloud_library_provider_all">All</string>
<string name="cloud_library_refresh">Refresh cloud library</string> <string name="cloud_library_refresh">Refresh cloud library</string>
<string name="cloud_library_search_label">Search cloud library</string>
<string name="cloud_library_select_provider">Select provider</string> <string name="cloud_library_select_provider">Select provider</string>
<string name="cloud_library_select_type">Select type</string> <string name="cloud_library_select_type">Select type</string>
<string name="cloud_library_status_ready">Ready to play</string> <string name="cloud_library_status_ready">Ready to play</string>

View file

@ -68,7 +68,6 @@ fun <T> NuvioShelfSection(
rowContentPadding: PaddingValues = PaddingValues(0.dp), rowContentPadding: PaddingValues = PaddingValues(0.dp),
rowModifier: Modifier = Modifier, rowModifier: Modifier = Modifier,
itemSpacing: Dp = 10.dp, itemSpacing: Dp = 10.dp,
showHeaderAccent: Boolean = true,
onViewAllClick: (() -> Unit)? = null, onViewAllClick: (() -> Unit)? = null,
viewAllPillSize: NuvioViewAllPillSize = NuvioViewAllPillSize.Default, viewAllPillSize: NuvioViewAllPillSize = NuvioViewAllPillSize.Default,
key: ((T) -> Any)? = null, key: ((T) -> Any)? = null,
@ -85,7 +84,6 @@ fun <T> NuvioShelfSection(
NuvioShelfSectionHeader( NuvioShelfSectionHeader(
title = title, title = title,
modifier = Modifier.padding(horizontal = headerHorizontalPadding), modifier = Modifier.padding(horizontal = headerHorizontalPadding),
showAccent = showHeaderAccent,
onViewAllClick = onViewAllClick, onViewAllClick = onViewAllClick,
viewAllPillSize = viewAllPillSize, viewAllPillSize = viewAllPillSize,
) )
@ -244,7 +242,6 @@ fun NuvioPosterCard(
private fun NuvioShelfSectionHeader( private fun NuvioShelfSectionHeader(
title: String, title: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
showAccent: Boolean = true,
onViewAllClick: (() -> Unit)? = null, onViewAllClick: (() -> Unit)? = null,
viewAllPillSize: NuvioViewAllPillSize = NuvioViewAllPillSize.Default, viewAllPillSize: NuvioViewAllPillSize = NuvioViewAllPillSize.Default,
) { ) {
@ -278,18 +275,6 @@ private fun NuvioShelfSectionHeader(
modifier = viewAllPlaceholderModifier, modifier = viewAllPlaceholderModifier,
) )
} }
if (showAccent) {
Box(
modifier = Modifier
.padding(top = NuvioTokens.Space.s6)
.width(NuvioTokens.Space.s64 - NuvioTokens.Space.s4)
.height(NuvioTokens.Space.s4)
.background(
color = tokens.colors.accent,
shape = tokens.shapes.chip,
),
)
}
} }
} }

View file

@ -43,7 +43,6 @@ fun DetailPosterRailSection(
horizontal = headerHorizontalPadding + horizontalScrollPadding, horizontal = headerHorizontalPadding + horizontalScrollPadding,
), ),
rowModifier = Modifier.nuvioHorizontalScrollBleed(horizontalScrollPadding), rowModifier = Modifier.nuvioHorizontalScrollBleed(horizontalScrollPadding),
showHeaderAccent = false,
key = { item -> item.stableKey() }, key = { item -> item.stableKey() },
) { item -> ) { item ->
HomePosterCard( HomePosterCard(

View file

@ -34,7 +34,6 @@ data class HomeCatalogSettingsUiState(
val heroEnabled: Boolean = true, val heroEnabled: Boolean = true,
val showCatalogType: Boolean = true, val showCatalogType: Boolean = true,
val hideUnreleasedContent: Boolean = false, val hideUnreleasedContent: Boolean = false,
val hideCatalogUnderline: Boolean = false,
val items: List<HomeCatalogSettingsItem> = emptyList(), val items: List<HomeCatalogSettingsItem> = emptyList(),
) { ) {
val signature: String val signature: String
@ -45,8 +44,6 @@ data class HomeCatalogSettingsUiState(
append('|') append('|')
append(hideUnreleasedContent) append(hideUnreleasedContent)
append('|') append('|')
append(hideCatalogUnderline)
append('|')
append( append(
items.joinToString(separator = "|") { item -> items.joinToString(separator = "|") { item ->
"${item.key}:${item.order}:${item.enabled}:${item.heroSourceEnabled}:${item.customTitle}" "${item.key}:${item.order}:${item.enabled}:${item.heroSourceEnabled}:${item.customTitle}"
@ -66,7 +63,6 @@ internal data class HomeCatalogSettingsSnapshot(
val heroEnabled: Boolean, val heroEnabled: Boolean,
val showCatalogType: Boolean, val showCatalogType: Boolean,
val hideUnreleasedContent: Boolean, val hideUnreleasedContent: Boolean,
val hideCatalogUnderline: Boolean,
val preferences: Map<String, HomeCatalogPreference>, val preferences: Map<String, HomeCatalogPreference>,
) )
@ -84,7 +80,6 @@ private data class StoredHomeCatalogSettingsPayload(
val heroEnabled: Boolean = true, val heroEnabled: Boolean = true,
val showCatalogType: Boolean = true, val showCatalogType: Boolean = true,
val hideUnreleasedContent: Boolean = false, val hideUnreleasedContent: Boolean = false,
val hideCatalogUnderline: Boolean = false,
val items: List<StoredHomeCatalogPreference> = emptyList(), val items: List<StoredHomeCatalogPreference> = emptyList(),
) )
@ -106,7 +101,6 @@ object HomeCatalogSettingsRepository {
private var heroEnabled = true private var heroEnabled = true
private var showCatalogType = true private var showCatalogType = true
private var hideUnreleasedContent = false private var hideUnreleasedContent = false
private var hideCatalogUnderline = false
fun onProfileChanged() { fun onProfileChanged() {
hasLoaded = false hasLoaded = false
@ -114,7 +108,6 @@ object HomeCatalogSettingsRepository {
heroEnabled = true heroEnabled = true
showCatalogType = true showCatalogType = true
hideUnreleasedContent = false hideUnreleasedContent = false
hideCatalogUnderline = false
definitions = emptyList() definitions = emptyList()
collectionDefinitions = emptyList() collectionDefinitions = emptyList()
_uiState.value = HomeCatalogSettingsUiState() _uiState.value = HomeCatalogSettingsUiState()
@ -128,7 +121,6 @@ object HomeCatalogSettingsRepository {
heroEnabled = true heroEnabled = true
showCatalogType = true showCatalogType = true
hideUnreleasedContent = false hideUnreleasedContent = false
hideCatalogUnderline = false
_uiState.value = HomeCatalogSettingsUiState() _uiState.value = HomeCatalogSettingsUiState()
} }
@ -162,7 +154,6 @@ object HomeCatalogSettingsRepository {
heroEnabled = heroEnabled, heroEnabled = heroEnabled,
showCatalogType = showCatalogType, showCatalogType = showCatalogType,
hideUnreleasedContent = hideUnreleasedContent, hideUnreleasedContent = hideUnreleasedContent,
hideCatalogUnderline = hideCatalogUnderline,
preferences = preferences.mapValues { (_, value) -> preferences = preferences.mapValues { (_, value) ->
HomeCatalogPreference( HomeCatalogPreference(
customTitle = value.customTitle, customTitle = value.customTitle,
@ -202,15 +193,6 @@ object HomeCatalogSettingsRepository {
HomeCatalogSettingsSyncService.triggerPush() HomeCatalogSettingsSyncService.triggerPush()
} }
fun setHideCatalogUnderline(enabled: Boolean) {
ensureLoaded()
if (hideCatalogUnderline == enabled) return
hideCatalogUnderline = enabled
publish()
persist()
HomeCatalogSettingsSyncService.triggerPush()
}
fun setHeroSourceEnabled(key: String, enabled: Boolean) { fun setHeroSourceEnabled(key: String, enabled: Boolean) {
updatePreference(key, pushRemote = false) { preference -> updatePreference(key, pushRemote = false) { preference ->
if (!enabled) { if (!enabled) {
@ -240,7 +222,6 @@ object HomeCatalogSettingsRepository {
heroEnabled = true heroEnabled = true
showCatalogType = true showCatalogType = true
hideUnreleasedContent = false hideUnreleasedContent = false
hideCatalogUnderline = false
preferences.clear() preferences.clear()
normalizePreferences() normalizePreferences()
publish() publish()
@ -290,7 +271,6 @@ object HomeCatalogSettingsRepository {
heroEnabled = parsedPayload.heroEnabled heroEnabled = parsedPayload.heroEnabled
showCatalogType = parsedPayload.showCatalogType showCatalogType = parsedPayload.showCatalogType
hideUnreleasedContent = parsedPayload.hideUnreleasedContent hideUnreleasedContent = parsedPayload.hideUnreleasedContent
hideCatalogUnderline = parsedPayload.hideCatalogUnderline
preferences = parsedPayload.items.associateBy { it.key }.toMutableMap() preferences = parsedPayload.items.associateBy { it.key }.toMutableMap()
publish() publish()
return return
@ -391,7 +371,6 @@ object HomeCatalogSettingsRepository {
heroEnabled = heroEnabled, heroEnabled = heroEnabled,
showCatalogType = showCatalogType, showCatalogType = showCatalogType,
hideUnreleasedContent = hideUnreleasedContent, hideUnreleasedContent = hideUnreleasedContent,
hideCatalogUnderline = hideCatalogUnderline,
items = items, items = items,
) )
} }
@ -403,7 +382,6 @@ object HomeCatalogSettingsRepository {
heroEnabled = heroEnabled, heroEnabled = heroEnabled,
showCatalogType = showCatalogType, showCatalogType = showCatalogType,
hideUnreleasedContent = hideUnreleasedContent, hideUnreleasedContent = hideUnreleasedContent,
hideCatalogUnderline = hideCatalogUnderline,
items = preferences.values.sortedBy { it.order }, items = preferences.values.sortedBy { it.order },
), ),
), ),
@ -500,7 +478,6 @@ object HomeCatalogSettingsRepository {
return SyncHomeCatalogPayload( return SyncHomeCatalogPayload(
showCatalogType = showCatalogType, showCatalogType = showCatalogType,
hideUnreleasedContent = hideUnreleasedContent, hideUnreleasedContent = hideUnreleasedContent,
hideCatalogUnderline = hideCatalogUnderline,
items = items, items = items,
) )
} }
@ -509,7 +486,6 @@ object HomeCatalogSettingsRepository {
ensureLoaded() ensureLoaded()
showCatalogType = payload.showCatalogType showCatalogType = payload.showCatalogType
hideUnreleasedContent = payload.hideUnreleasedContent hideUnreleasedContent = payload.hideUnreleasedContent
hideCatalogUnderline = payload.hideCatalogUnderline
if (payload.items.isNotEmpty()) { if (payload.items.isNotEmpty()) {
val existingHeroState = preferences.mapValues { it.value.heroSourceEnabled } val existingHeroState = preferences.mapValues { it.value.heroSourceEnabled }
val remotePreferences = payload.items.associate { item -> val remotePreferences = payload.items.associate { item ->

View file

@ -42,7 +42,6 @@ data class SyncCatalogItem(
data class SyncHomeCatalogPayload( data class SyncHomeCatalogPayload(
@SerialName("show_catalog_type") val showCatalogType: Boolean = true, @SerialName("show_catalog_type") val showCatalogType: Boolean = true,
@SerialName("hide_unreleased_content") val hideUnreleasedContent: Boolean = false, @SerialName("hide_unreleased_content") val hideUnreleasedContent: Boolean = false,
@SerialName("hide_catalog_underline") val hideCatalogUnderline: Boolean = false,
val items: List<SyncCatalogItem> = emptyList(), val items: List<SyncCatalogItem> = emptyList(),
) )
@ -59,7 +58,6 @@ private data class RemoteHomeCatalogSettings(
val updatedAt: String?, val updatedAt: String?,
val hasShowCatalogType: Boolean, val hasShowCatalogType: Boolean,
val hasHideUnreleasedContent: Boolean, val hasHideUnreleasedContent: Boolean,
val hasHideCatalogUnderline: Boolean,
) )
private data class PullToken( private data class PullToken(
@ -77,7 +75,6 @@ object HomeCatalogSettingsSyncService {
private const val HIDE_UNRELEASED_CONTENT_KEY = "hide_unreleased_content" private const val HIDE_UNRELEASED_CONTENT_KEY = "hide_unreleased_content"
private const val SHOW_CATALOG_TYPE_KEY = "show_catalog_type" private const val SHOW_CATALOG_TYPE_KEY = "show_catalog_type"
private const val HIDE_CATALOG_UNDERLINE_KEY = "hide_catalog_underline"
@Volatile @Volatile
var isSyncingFromRemote: Boolean = false var isSyncingFromRemote: Boolean = false
@ -221,7 +218,6 @@ object HomeCatalogSettingsSyncService {
updatedAt = blob.updatedAt, updatedAt = blob.updatedAt,
hasShowCatalogType = blob.settingsJson.containsKey(SHOW_CATALOG_TYPE_KEY), hasShowCatalogType = blob.settingsJson.containsKey(SHOW_CATALOG_TYPE_KEY),
hasHideUnreleasedContent = blob.settingsJson.containsKey(HIDE_UNRELEASED_CONTENT_KEY), hasHideUnreleasedContent = blob.settingsJson.containsKey(HIDE_UNRELEASED_CONTENT_KEY),
hasHideCatalogUnderline = blob.settingsJson.containsKey(HIDE_CATALOG_UNDERLINE_KEY),
) )
} }
@ -234,9 +230,6 @@ object HomeCatalogSettingsSyncService {
val showCatalogTypeSource = rows val showCatalogTypeSource = rows
.filter { it.hasShowCatalogType } .filter { it.hasShowCatalogType }
.maxByOrNull { it.updatedAt.orEmpty() } .maxByOrNull { it.updatedAt.orEmpty() }
val hideUnderlineSource = rows
.filter { it.hasHideCatalogUnderline }
.maxByOrNull { it.updatedAt.orEmpty() }
return copy( return copy(
payload = payload.copy( payload = payload.copy(
@ -244,8 +237,6 @@ object HomeCatalogSettingsSyncService {
?: payload.showCatalogType, ?: payload.showCatalogType,
hideUnreleasedContent = hideUnreleasedSource?.payload?.hideUnreleasedContent hideUnreleasedContent = hideUnreleasedSource?.payload?.hideUnreleasedContent
?: payload.hideUnreleasedContent, ?: payload.hideUnreleasedContent,
hideCatalogUnderline = hideUnderlineSource?.payload?.hideCatalogUnderline
?: payload.hideCatalogUnderline,
), ),
) )
} }
@ -278,11 +269,6 @@ object HomeCatalogSettingsSyncService {
} else { } else {
localPayload.hideUnreleasedContent localPayload.hideUnreleasedContent
}, },
hideCatalogUnderline = if (settingsJson.containsKey(HIDE_CATALOG_UNDERLINE_KEY)) {
decoded.hideCatalogUnderline
} else {
localPayload.hideCatalogUnderline
},
) )
}.getOrNull() }.getOrNull()

View file

@ -861,7 +861,6 @@ fun HomeScreen(
hasContinueWatchingRows, hasContinueWatchingRows,
continueWatchingLayout, continueWatchingLayout,
posterCardStyle.widthDp, posterCardStyle.widthDp,
homeSettingsUiState.hideCatalogUnderline,
nativeBottomNavigationOverlayHeight, nativeBottomNavigationOverlayHeight,
) { ) {
if ( if (
@ -873,7 +872,6 @@ fun HomeScreen(
style = continueWatchingPreferences.style, style = continueWatchingPreferences.style,
layout = continueWatchingLayout, layout = continueWatchingLayout,
basePosterWidthDp = posterCardStyle.widthDp, basePosterWidthDp = posterCardStyle.widthDp,
showHeaderAccent = !homeSettingsUiState.hideCatalogUnderline,
) + nativeBottomNavigationOverlayHeight ) + nativeBottomNavigationOverlayHeight
} else { } else {
null null
@ -958,7 +956,6 @@ fun HomeScreen(
items(3) { items(3) {
HomeSkeletonRow( HomeSkeletonRow(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),
showHeaderAccent = !homeSettingsUiState.hideCatalogUnderline,
) )
} }
} }

View file

@ -4,15 +4,11 @@ import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.nuvio.app.core.ui.NuvioShelfSection import com.nuvio.app.core.ui.NuvioShelfSection
import com.nuvio.app.core.ui.NuvioViewAllPillSize import com.nuvio.app.core.ui.NuvioViewAllPillSize
import com.nuvio.app.core.ui.rememberPosterCardStyleUiState import com.nuvio.app.core.ui.rememberPosterCardStyleUiState
import com.nuvio.app.features.home.HomeCatalogSettingsRepository
import com.nuvio.app.features.home.HomeCatalogSection import com.nuvio.app.features.home.HomeCatalogSection
import com.nuvio.app.features.home.MetaPreview import com.nuvio.app.features.home.MetaPreview
import com.nuvio.app.features.home.stableKey import com.nuvio.app.features.home.stableKey
@ -72,10 +68,6 @@ private fun HomeCatalogRowSectionContent(
onPosterLongClick: ((MetaPreview) -> Unit)?, onPosterLongClick: ((MetaPreview) -> Unit)?,
) { ) {
val posterCardStyle = rememberPosterCardStyleUiState() val posterCardStyle = rememberPosterCardStyleUiState()
val homeCatalogSettings by remember {
HomeCatalogSettingsRepository.snapshot()
HomeCatalogSettingsRepository.uiState
}.collectAsStateWithLifecycle()
NuvioShelfSection( NuvioShelfSection(
title = section.title, title = section.title,
@ -83,7 +75,6 @@ private fun HomeCatalogRowSectionContent(
modifier = modifier, modifier = modifier,
headerHorizontalPadding = sectionPadding, headerHorizontalPadding = sectionPadding,
rowContentPadding = PaddingValues(horizontal = sectionPadding), rowContentPadding = PaddingValues(horizontal = sectionPadding),
showHeaderAccent = !homeCatalogSettings.hideCatalogUnderline,
onViewAllClick = onViewAllClick, onViewAllClick = onViewAllClick,
viewAllPillSize = NuvioViewAllPillSize.Compact, viewAllPillSize = NuvioViewAllPillSize.Compact,
key = { item -> item.stableKey() }, key = { item -> item.stableKey() },

View file

@ -15,8 +15,6 @@ import androidx.compose.material3.CardDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
@ -25,7 +23,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.nuvio.app.core.ui.NuvioCardDepthSurface import com.nuvio.app.core.ui.NuvioCardDepthSurface
import com.nuvio.app.core.ui.NuvioShelfSection import com.nuvio.app.core.ui.NuvioShelfSection
import com.nuvio.app.core.ui.PosterLandscapeAspectRatio import com.nuvio.app.core.ui.PosterLandscapeAspectRatio
@ -35,7 +32,6 @@ import com.nuvio.app.core.ui.posterCardClickable
import com.nuvio.app.core.ui.rememberPosterCardStyleUiState import com.nuvio.app.core.ui.rememberPosterCardStyleUiState
import com.nuvio.app.features.collection.Collection import com.nuvio.app.features.collection.Collection
import com.nuvio.app.features.collection.CollectionFolder import com.nuvio.app.features.collection.CollectionFolder
import com.nuvio.app.features.home.HomeCatalogSettingsRepository
import com.nuvio.app.features.home.PosterShape import com.nuvio.app.features.home.PosterShape
@Composable @Composable
@ -77,18 +73,12 @@ private fun HomeCollectionRowSectionContent(
animateGifs: Boolean, animateGifs: Boolean,
onFolderClick: ((collectionId: String, folderId: String) -> Unit)?, onFolderClick: ((collectionId: String, folderId: String) -> Unit)?,
) { ) {
val homeCatalogSettings by remember {
HomeCatalogSettingsRepository.snapshot()
HomeCatalogSettingsRepository.uiState
}.collectAsStateWithLifecycle()
NuvioShelfSection( NuvioShelfSection(
title = collection.title, title = collection.title,
entries = collection.folders, entries = collection.folders,
modifier = modifier, modifier = modifier,
headerHorizontalPadding = sectionPadding, headerHorizontalPadding = sectionPadding,
rowContentPadding = PaddingValues(horizontal = sectionPadding), rowContentPadding = PaddingValues(horizontal = sectionPadding),
showHeaderAccent = !homeCatalogSettings.hideCatalogUnderline,
key = { folder -> "collection_${collection.id}_folder_${folder.id}" }, key = { folder -> "collection_${collection.id}_folder_${folder.id}" },
) { folder -> ) { folder ->
CollectionFolderCard( CollectionFolderCard(

View file

@ -47,7 +47,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import com.nuvio.app.core.ui.DisintegratingContainer import com.nuvio.app.core.ui.DisintegratingContainer
import com.nuvio.app.core.ui.NuvioCardDepthSurface import com.nuvio.app.core.ui.NuvioCardDepthSurface
@ -62,7 +61,6 @@ import com.nuvio.app.core.ui.posterCardClickable
import com.nuvio.app.core.ui.rememberPosterCardStyleUiState import com.nuvio.app.core.ui.rememberPosterCardStyleUiState
import com.nuvio.app.features.cloud.CloudLibraryContentType import com.nuvio.app.features.cloud.CloudLibraryContentType
import com.nuvio.app.features.cloud.cloudLibraryDisplayArtworkUrl import com.nuvio.app.features.cloud.cloudLibraryDisplayArtworkUrl
import com.nuvio.app.features.home.HomeCatalogSettingsRepository
import com.nuvio.app.features.watchprogress.ContinueWatchingItem import com.nuvio.app.features.watchprogress.ContinueWatchingItem
import com.nuvio.app.features.watchprogress.ContinueWatchingSectionStyle import com.nuvio.app.features.watchprogress.ContinueWatchingSectionStyle
import com.nuvio.app.features.watchprogress.CurrentDateProvider import com.nuvio.app.features.watchprogress.CurrentDateProvider
@ -87,13 +85,8 @@ internal fun continueWatchingSectionHeightEstimate(
style: ContinueWatchingSectionStyle, style: ContinueWatchingSectionStyle,
layout: ContinueWatchingLayout, layout: ContinueWatchingLayout,
basePosterWidthDp: Int, basePosterWidthDp: Int,
showHeaderAccent: Boolean,
): Dp { ): Dp {
val headerHeight = NuvioTokens.Space.s40 + if (showHeaderAccent) { val headerHeight = NuvioTokens.Space.s40
NuvioTokens.Space.s6 + NuvioTokens.Space.s4
} else {
0.dp
}
val headerToRowGap = NuvioTokens.Space.s8 + NuvioTokens.Space.s2 val headerToRowGap = NuvioTokens.Space.s8 + NuvioTokens.Space.s2
val rowHeight = when (style) { val rowHeight = when (style) {
ContinueWatchingSectionStyle.Card -> continueWatchingLandscapeCardHeight(basePosterWidthDp) ContinueWatchingSectionStyle.Card -> continueWatchingLandscapeCardHeight(basePosterWidthDp)
@ -107,7 +100,6 @@ internal fun continueWatchingHeroViewportReserveHeight(
style: ContinueWatchingSectionStyle, style: ContinueWatchingSectionStyle,
layout: ContinueWatchingLayout, layout: ContinueWatchingLayout,
basePosterWidthDp: Int, basePosterWidthDp: Int,
showHeaderAccent: Boolean,
): Dp { ): Dp {
val bottomNavigationClearance = when (style) { val bottomNavigationClearance = when (style) {
ContinueWatchingSectionStyle.Card, ContinueWatchingSectionStyle.Card,
@ -118,7 +110,6 @@ internal fun continueWatchingHeroViewportReserveHeight(
style = style, style = style,
layout = layout, layout = layout,
basePosterWidthDp = basePosterWidthDp, basePosterWidthDp = basePosterWidthDp,
showHeaderAccent = showHeaderAccent,
) + bottomNavigationClearance ) + bottomNavigationClearance
} }
@ -291,11 +282,6 @@ private fun HomeContinueWatchingSectionContent(
onItemClick: ((ContinueWatchingItem) -> Unit)?, onItemClick: ((ContinueWatchingItem) -> Unit)?,
onItemLongPress: ((ContinueWatchingItem) -> Unit)?, onItemLongPress: ((ContinueWatchingItem) -> Unit)?,
) { ) {
val homeCatalogSettings by remember {
HomeCatalogSettingsRepository.snapshot()
HomeCatalogSettingsRepository.uiState
}.collectAsStateWithLifecycle()
val disintegration = remember { ContinueWatchingDisintegrationHolder() } val disintegration = remember { ContinueWatchingDisintegrationHolder() }
val displayEntries = disintegration.sync(items) val displayEntries = disintegration.sync(items)
@ -306,7 +292,6 @@ private fun HomeContinueWatchingSectionContent(
headerHorizontalPadding = sectionPadding, headerHorizontalPadding = sectionPadding,
rowContentPadding = PaddingValues(horizontal = sectionPadding), rowContentPadding = PaddingValues(horizontal = sectionPadding),
itemSpacing = layout.itemGap, itemSpacing = layout.itemGap,
showHeaderAccent = !homeCatalogSettings.hideCatalogUnderline,
key = { entry -> entry.videoId }, key = { entry -> entry.videoId },
animatePlacement = true, animatePlacement = true,
state = listState, state = listState,

View file

@ -183,7 +183,6 @@ fun HomeSkeletonHero(
@Composable @Composable
fun HomeSkeletonRow( fun HomeSkeletonRow(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
showHeaderAccent: Boolean = true,
) { ) {
val brush = rememberHomeSkeletonBrush() val brush = rememberHomeSkeletonBrush()
val posterCardStyle = rememberPosterCardStyleUiState() val posterCardStyle = rememberPosterCardStyleUiState()
@ -210,17 +209,6 @@ fun HomeSkeletonRow(
.clip(RoundedCornerShape(6.dp)) .clip(RoundedCornerShape(6.dp))
.background(brush), .background(brush),
) )
if (showHeaderAccent) {
// Accent bar
Box(
modifier = Modifier
.width(60.dp)
.height(4.dp)
.clip(RoundedCornerShape(999.dp))
.background(brush),
)
Spacer(modifier = Modifier.height(2.dp))
}
// Poster row // Poster row
Row( Row(
horizontalArrangement = Arrangement.spacedBy(10.dp), horizontalArrangement = Arrangement.spacedBy(10.dp),

View file

@ -31,15 +31,18 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
import androidx.compose.material.icons.automirrored.rounded.ArrowBack import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material.icons.rounded.Close
import androidx.compose.material.icons.rounded.GridView import androidx.compose.material.icons.rounded.GridView
import androidx.compose.material.icons.rounded.PlayArrow import androidx.compose.material.icons.rounded.PlayArrow
import androidx.compose.material.icons.rounded.Refresh import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material.icons.rounded.ViewAgenda import androidx.compose.material.icons.rounded.ViewAgenda
import com.nuvio.app.core.ui.NuvioLoadingIndicator import com.nuvio.app.core.ui.NuvioLoadingIndicator
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -78,7 +81,6 @@ import com.nuvio.app.features.cloud.CloudLibraryItemType
import com.nuvio.app.features.cloud.CloudLibraryRepository import com.nuvio.app.features.cloud.CloudLibraryRepository
import com.nuvio.app.features.cloud.CloudLibraryUiState import com.nuvio.app.features.cloud.CloudLibraryUiState
import com.nuvio.app.features.debrid.DebridSettingsRepository import com.nuvio.app.features.debrid.DebridSettingsRepository
import com.nuvio.app.features.home.HomeCatalogSettingsRepository
import com.nuvio.app.features.home.components.HomeEmptyStateCard import com.nuvio.app.features.home.components.HomeEmptyStateCard
import com.nuvio.app.features.home.components.HomePosterCard import com.nuvio.app.features.home.components.HomePosterCard
import com.nuvio.app.features.home.components.HomeSkeletonRow import com.nuvio.app.features.home.components.HomeSkeletonRow
@ -120,10 +122,6 @@ fun LibraryScreen(
LibraryDisplaySettingsRepository.ensureLoaded() LibraryDisplaySettingsRepository.ensureLoaded()
LibraryDisplaySettingsRepository.uiState LibraryDisplaySettingsRepository.uiState
}.collectAsStateWithLifecycle() }.collectAsStateWithLifecycle()
val homeCatalogSettingsUiState by remember {
HomeCatalogSettingsRepository.snapshot()
HomeCatalogSettingsRepository.uiState
}.collectAsStateWithLifecycle()
val networkStatusUiState by NetworkStatusRepository.uiState.collectAsStateWithLifecycle() val networkStatusUiState by NetworkStatusRepository.uiState.collectAsStateWithLifecycle()
var observedOfflineState by remember { mutableStateOf(false) } var observedOfflineState by remember { mutableStateOf(false) }
var sourceModeName by rememberSaveable { mutableStateOf(LibraryViewMode.Saved.name) } var sourceModeName by rememberSaveable { mutableStateOf(LibraryViewMode.Saved.name) }
@ -132,6 +130,7 @@ fun LibraryScreen(
} }
var selectedProviderId by rememberSaveable { mutableStateOf<String?>(null) } var selectedProviderId by rememberSaveable { mutableStateOf<String?>(null) }
var selectedTypeName by rememberSaveable { mutableStateOf<String?>(null) } var selectedTypeName by rememberSaveable { mutableStateOf<String?>(null) }
var cloudSearchQuery by rememberSaveable { mutableStateOf("") }
val selectedType = remember(selectedTypeName) { val selectedType = remember(selectedTypeName) {
selectedTypeName?.let { runCatching { CloudLibraryItemType.valueOf(it) }.getOrNull() } selectedTypeName?.let { runCatching { CloudLibraryItemType.valueOf(it) }.getOrNull() }
} }
@ -305,6 +304,11 @@ fun LibraryScreen(
selectedProviderId = selectedProviderId, selectedProviderId = selectedProviderId,
selectedType = selectedType, selectedType = selectedType,
selectedCloudItemKey = selectedCloudItemKey, selectedCloudItemKey = selectedCloudItemKey,
searchQuery = cloudSearchQuery,
onSearchQueryChange = {
cloudSearchQuery = it
selectedCloudItemKey = null
},
onProviderSelected = { onProviderSelected = {
selectedProviderId = it selectedProviderId = it
selectedTypeName = null selectedTypeName = null
@ -335,7 +339,6 @@ fun LibraryScreen(
items(3) { items(3) {
HomeSkeletonRow( HomeSkeletonRow(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),
showHeaderAccent = !homeCatalogSettingsUiState.hideCatalogUnderline,
) )
} }
} }
@ -415,7 +418,6 @@ fun LibraryScreen(
LibraryLayoutMode.HORIZONTAL -> librarySections( LibraryLayoutMode.HORIZONTAL -> librarySections(
displaySections = librarySectionsDisplay, displaySections = librarySectionsDisplay,
watchedKeys = watchedUiState.watchedKeys, watchedKeys = watchedUiState.watchedKeys,
showHeaderAccent = !homeCatalogSettingsUiState.hideCatalogUnderline,
sortOption = effectiveSortOption, sortOption = effectiveSortOption,
onPosterClick = onPosterClick, onPosterClick = onPosterClick,
onSectionViewAllClick = onSectionViewAllClick, onSectionViewAllClick = onSectionViewAllClick,
@ -443,6 +445,8 @@ private fun LazyListScope.cloudLibraryContent(
selectedProviderId: String?, selectedProviderId: String?,
selectedType: CloudLibraryItemType?, selectedType: CloudLibraryItemType?,
selectedCloudItemKey: String?, selectedCloudItemKey: String?,
searchQuery: String,
onSearchQueryChange: (String) -> Unit,
onProviderSelected: (String?) -> Unit, onProviderSelected: (String?) -> Unit,
onTypeSelected: (CloudLibraryItemType?) -> Unit, onTypeSelected: (CloudLibraryItemType?) -> Unit,
onItemSelected: (CloudLibraryItem) -> Unit, onItemSelected: (CloudLibraryItem) -> Unit,
@ -488,8 +492,19 @@ private fun LazyListScope.cloudLibraryContent(
.distinct() .distinct()
.sortedBy { type -> type.ordinal } .sortedBy { type -> type.ordinal }
val effectiveSelectedType = selectedType?.takeIf { type -> type in availableTypes } val effectiveSelectedType = selectedType?.takeIf { type -> type in availableTypes }
val filteredItems = providerItems val typeFilteredItems = providerItems
.filter { item -> effectiveSelectedType == null || item.type == effectiveSelectedType } .filter { item -> effectiveSelectedType == null || item.type == effectiveSelectedType }
// Local filter over the already-loaded library. Matches the item name or any of its
// file names, since the useful identifier is often in the filename, not the title.
val trimmedQuery = searchQuery.trim()
val filteredItems = if (trimmedQuery.isEmpty()) {
typeFilteredItems
} else {
typeFilteredItems.filter { item ->
item.name.contains(trimmedQuery, ignoreCase = true) ||
item.files.any { file -> file.name.contains(trimmedQuery, ignoreCase = true) }
}
}
val selectedItem = filteredItems.firstOrNull { it.stableKey == selectedCloudItemKey } val selectedItem = filteredItems.firstOrNull { it.stableKey == selectedCloudItemKey }
if (selectedItem != null) { if (selectedItem != null) {
@ -514,6 +529,14 @@ private fun LazyListScope.cloudLibraryContent(
) )
} }
item(key = "cloud-library-search") {
CloudLibrarySearchField(
query = searchQuery,
onQueryChange = onSearchQueryChange,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
)
}
uiState.providers uiState.providers
.filter { providerState -> selectedProviderId == null || providerState.providerId == selectedProviderId } .filter { providerState -> selectedProviderId == null || providerState.providerId == selectedProviderId }
.filter { providerState -> !providerState.errorMessage.isNullOrBlank() && providerState.items.isEmpty() } .filter { providerState -> !providerState.errorMessage.isNullOrBlank() && providerState.items.isEmpty() }
@ -557,6 +580,38 @@ private fun LazyListScope.cloudLibraryContent(
} }
} }
@Composable
private fun CloudLibrarySearchField(
query: String,
onQueryChange: (String) -> Unit,
modifier: Modifier = Modifier,
) {
OutlinedTextField(
value = query,
onValueChange = onQueryChange,
modifier = modifier.fillMaxWidth(),
singleLine = true,
shape = RoundedCornerShape(12.dp),
placeholder = { Text(stringResource(Res.string.cloud_library_search_label)) },
leadingIcon = {
Icon(
imageVector = Icons.Rounded.Search,
contentDescription = null,
)
},
trailingIcon = {
if (query.isNotEmpty()) {
IconButton(onClick = { onQueryChange("") }) {
Icon(
imageVector = Icons.Rounded.Close,
contentDescription = stringResource(Res.string.compose_search_clear),
)
}
}
},
)
}
private fun LazyListScope.cloudLibrarySkeletonItems() { private fun LazyListScope.cloudLibrarySkeletonItems() {
item(key = "cloud-library-skeleton-toolbar") { item(key = "cloud-library-skeleton-toolbar") {
CloudLibrarySkeletonToolbar( CloudLibrarySkeletonToolbar(
@ -1113,7 +1168,6 @@ private enum class LibraryViewMode {
private fun LazyListScope.librarySections( private fun LazyListScope.librarySections(
displaySections: List<LibraryDisplaySection>, displaySections: List<LibraryDisplaySection>,
watchedKeys: Set<String>, watchedKeys: Set<String>,
showHeaderAccent: Boolean,
sortOption: LibrarySortOption, sortOption: LibrarySortOption,
onPosterClick: ((LibraryItem) -> Unit)?, onPosterClick: ((LibraryItem) -> Unit)?,
onSectionViewAllClick: ((LibrarySection, LibrarySortOption) -> Unit)?, onSectionViewAllClick: ((LibrarySection, LibrarySortOption) -> Unit)?,
@ -1130,7 +1184,6 @@ private fun LazyListScope.librarySections(
modifier = libraryContentTransitionModifier(), modifier = libraryContentTransitionModifier(),
headerHorizontalPadding = 16.dp, headerHorizontalPadding = 16.dp,
rowContentPadding = PaddingValues(horizontal = 16.dp), rowContentPadding = PaddingValues(horizontal = 16.dp),
showHeaderAccent = showHeaderAccent,
onViewAllClick = section.source onViewAllClick = section.source
?.takeIf { it.items.size > LIBRARY_SECTION_PREVIEW_LIMIT } ?.takeIf { it.items.size > LIBRARY_SECTION_PREVIEW_LIMIT }
?.let { source -> onSectionViewAllClick?.let { { it(source, sortOption) } } }, ?.let { source -> onSectionViewAllClick?.let { { it(source, sortOption) } } },

View file

@ -10,6 +10,14 @@ interface PlayerEngineController {
fun seekBy(offsetMs: Long) fun seekBy(offsetMs: Long)
fun retry() fun retry()
fun setPlaybackSpeed(speed: Float) fun setPlaybackSpeed(speed: Float)
fun currentPlayerVolume(): PlayerAudioLevel = PlayerAudioLevel(
fraction = 1f,
isMuted = false,
)
fun setPlayerVolume(level: Float): PlayerAudioLevel = PlayerAudioLevel(
fraction = 1f,
isMuted = false,
)
fun setMuted(muted: Boolean) {} fun setMuted(muted: Boolean) {}
fun getAudioTracks(): List<AudioTrack> fun getAudioTracks(): List<AudioTrack>
fun getSubtitleTracks(): List<SubtitleTrack> fun getSubtitleTracks(): List<SubtitleTrack>

View file

@ -134,7 +134,9 @@ internal fun PlayerScreenRuntime.showBrightnessFeedback(level: Float) {
} }
internal fun PlayerScreenRuntime.showVolumeFeedback(level: PlayerAudioLevel) { internal fun PlayerScreenRuntime.showVolumeFeedback(level: PlayerAudioLevel) {
val percentage = (level.fraction.coerceIn(0f, 1f) * 100f).roundToInt() val normalized = level.fraction.coerceIn(0f, PlayerMaxVolumeBoost)
val percentage = (normalized * 100f).roundToInt()
val isBoosted = normalized > PlayerNormalVolumeCeiling
showGestureFeedback( showGestureFeedback(
GestureFeedbackState( GestureFeedbackState(
messageRes = if (level.isMuted) { messageRes = if (level.isMuted) {
@ -144,7 +146,8 @@ internal fun PlayerScreenRuntime.showVolumeFeedback(level: PlayerAudioLevel) {
}, },
messageArgs = if (level.isMuted) emptyList() else listOf("$percentage%"), messageArgs = if (level.isMuted) emptyList() else listOf("$percentage%"),
icon = if (level.isMuted) GestureFeedbackIcon.VolumeMuted else GestureFeedbackIcon.Volume, icon = if (level.isMuted) GestureFeedbackIcon.VolumeMuted else GestureFeedbackIcon.Volume,
isDanger = level.isMuted, // Reuse the existing alternate feedback color path to distinguish boosted volume.
isDanger = level.isMuted || isBoosted,
), ),
) )
} }

View file

@ -121,6 +121,7 @@ internal fun PlayerScreenRuntime.RenderPlayerRuntimeUi() {
) )
.playerSurfaceDragGestures( .playerSurfaceDragGestures(
gestureController = gestureController, gestureController = gestureController,
playerController = playerController,
layoutSize = layoutSize, layoutSize = layoutSize,
sideGestureSystemEdgeExclusionPx = sideGestureSystemEdgeExclusionPx, sideGestureSystemEdgeExclusionPx = sideGestureSystemEdgeExclusionPx,
playerControlsLockedState = gestureCallbacks.playerControlsLocked, playerControlsLockedState = gestureCallbacks.playerControlsLocked,

View file

@ -11,6 +11,8 @@ internal const val PlayerLockedOverlayDurationMs = 2_000L
internal const val PlayerLeftGestureBoundary = 0.4f internal const val PlayerLeftGestureBoundary = 0.4f
internal const val PlayerRightGestureBoundary = 0.6f internal const val PlayerRightGestureBoundary = 0.6f
internal const val PlayerVerticalGestureSensitivity = 0.65f internal const val PlayerVerticalGestureSensitivity = 0.65f
internal const val PlayerNormalVolumeCeiling = 1f
internal const val PlayerMaxVolumeBoost = 2f
internal const val PlayerVerticalGestureTouchSlopMultiplier = 3f internal const val PlayerVerticalGestureTouchSlopMultiplier = 3f
internal const val PlayerVerticalGestureMinHeightFraction = 0.06f internal const val PlayerVerticalGestureMinHeightFraction = 0.06f
internal const val PlayerVerticalGestureDominanceRatio = 1.2f internal const val PlayerVerticalGestureDominanceRatio = 1.2f
@ -87,3 +89,45 @@ internal data class PendingPlayerP2pSwitch(
val episode: MetaVideo?, val episode: MetaVideo?,
val isAutoPlay: Boolean, val isAutoPlay: Boolean,
) )
internal fun currentCombinedVolumeLevel(
systemVolume: PlayerAudioLevel?,
softwareVolume: PlayerAudioLevel?,
): PlayerAudioLevel? {
if (systemVolume == null && softwareVolume == null) return null
if (systemVolume == null) return softwareVolume
if (systemVolume.fraction < PlayerNormalVolumeCeiling || softwareVolume == null) {
return systemVolume
}
val boostFraction = softwareVolume.fraction.coerceIn(PlayerNormalVolumeCeiling, PlayerMaxVolumeBoost)
return PlayerAudioLevel(
fraction = boostFraction,
isMuted = boostFraction <= 0.001f,
)
}
internal fun setCombinedVolumeLevel(
target: Float,
systemController: PlayerGestureController?,
engineController: PlayerEngineController?,
): PlayerAudioLevel? {
val normalized = target.coerceIn(0f, PlayerMaxVolumeBoost)
return if (normalized <= PlayerNormalVolumeCeiling) {
// Normal range: change device/media volume and keep software gain neutral.
engineController?.setPlayerVolume(PlayerNormalVolumeCeiling)
systemController?.setVolume(normalized)
?: PlayerAudioLevel(
fraction = normalized,
isMuted = normalized <= 0.001f,
)
} else {
// Boost range: keep device/media volume at maximum and apply software gain above 100%.
val systemLevel = systemController?.setVolume(PlayerNormalVolumeCeiling)
val boostedLevel = engineController?.setPlayerVolume(normalized)
boostedLevel?.copy(
fraction = normalized,
isMuted = false,
) ?: systemLevel
}
}

View file

@ -40,6 +40,7 @@ internal fun Modifier.playerSurfaceTapGestures(
internal fun Modifier.playerSurfaceDragGestures( internal fun Modifier.playerSurfaceDragGestures(
gestureController: PlayerGestureController?, gestureController: PlayerGestureController?,
playerController: PlayerEngineController?,
layoutSize: IntSize, layoutSize: IntSize,
sideGestureSystemEdgeExclusionPx: Float, sideGestureSystemEdgeExclusionPx: Float,
playerControlsLockedState: State<Boolean>, playerControlsLockedState: State<Boolean>,
@ -55,7 +56,7 @@ internal fun Modifier.playerSurfaceDragGestures(
revealLockedOverlayState: State<() -> Unit>, revealLockedOverlayState: State<() -> Unit>,
commitHorizontalSeekState: State<(Long) -> Unit>, commitHorizontalSeekState: State<(Long) -> Unit>,
): Modifier = ): Modifier =
pointerInput(gestureController, layoutSize, sideGestureSystemEdgeExclusionPx) { pointerInput(gestureController, playerController, layoutSize, sideGestureSystemEdgeExclusionPx) {
awaitEachGesture { awaitEachGesture {
val down = awaitFirstDown() val down = awaitFirstDown()
if (playerControlsLockedState.value) { if (playerControlsLockedState.value) {
@ -91,7 +92,10 @@ internal fun Modifier.playerSurfaceDragGestures(
null null
} }
val initialVolume = if (region == PlayerSideGesture.Volume) { val initialVolume = if (region == PlayerSideGesture.Volume) {
controller?.currentVolume() currentCombinedVolumeLevel(
systemVolume = controller?.currentVolume(),
softwareVolume = playerController?.currentPlayerVolume(),
)
} else { } else {
null null
} }
@ -185,8 +189,13 @@ internal fun Modifier.playerSurfaceDragGestures(
val activeTotalDy = totalDy - verticalGestureActivationDy val activeTotalDy = totalDy - verticalGestureActivationDy
val gestureDeltaFraction = val gestureDeltaFraction =
(-activeTotalDy / height) * PlayerVerticalGestureSensitivity (-activeTotalDy / height) * PlayerVerticalGestureSensitivity
controller?.setVolume((initialVolume?.fraction ?: 0f) + gestureDeltaFraction) val target = ((initialVolume?.fraction ?: 0f) + gestureDeltaFraction)
?.let(showVolumeFeedbackState.value) .coerceIn(0f, PlayerMaxVolumeBoost)
setCombinedVolumeLevel(
target = target,
systemController = controller,
engineController = playerController,
)?.let(showVolumeFeedbackState.value)
} }
} }
change.consume() change.consume()

View file

@ -318,7 +318,6 @@ fun SearchScreen(
items(2) { items(2) {
HomeSkeletonRow( HomeSkeletonRow(
modifier = Modifier.padding(horizontal = homeSectionPadding), modifier = Modifier.padding(horizontal = homeSectionPadding),
showHeaderAccent = !homeCatalogSettingsUiState.hideCatalogUnderline,
) )
} }
} }
@ -327,7 +326,6 @@ fun SearchScreen(
items(2) { items(2) {
HomeSkeletonRow( HomeSkeletonRow(
modifier = Modifier.padding(horizontal = homeSectionPadding), modifier = Modifier.padding(horizontal = homeSectionPadding),
showHeaderAccent = !homeCatalogSettingsUiState.hideCatalogUnderline,
) )
} }
} }
@ -371,7 +369,6 @@ fun SearchScreen(
item(key = "search_loading_more") { item(key = "search_loading_more") {
HomeSkeletonRow( HomeSkeletonRow(
modifier = Modifier.padding(horizontal = homeSectionPadding), modifier = Modifier.padding(horizontal = homeSectionPadding),
showHeaderAccent = !homeCatalogSettingsUiState.hideCatalogUnderline,
) )
} }
} }

View file

@ -44,8 +44,6 @@ import nuvio.composeapp.generated.resources.layout_catalog_type
import nuvio.composeapp.generated.resources.layout_catalog_type_sub import nuvio.composeapp.generated.resources.layout_catalog_type_sub
import nuvio.composeapp.generated.resources.settings_homescreen_empty_message import nuvio.composeapp.generated.resources.settings_homescreen_empty_message
import nuvio.composeapp.generated.resources.settings_homescreen_empty_title import nuvio.composeapp.generated.resources.settings_homescreen_empty_title
import nuvio.composeapp.generated.resources.settings_homescreen_hide_catalog_underline
import nuvio.composeapp.generated.resources.settings_homescreen_hide_catalog_underline_description
import nuvio.composeapp.generated.resources.settings_homescreen_keep_home_focused import nuvio.composeapp.generated.resources.settings_homescreen_keep_home_focused
import nuvio.composeapp.generated.resources.settings_homescreen_limit_reached import nuvio.composeapp.generated.resources.settings_homescreen_limit_reached
import nuvio.composeapp.generated.resources.settings_homescreen_no_sources_selected import nuvio.composeapp.generated.resources.settings_homescreen_no_sources_selected
@ -70,7 +68,6 @@ internal fun LazyListScope.homescreenSettingsContent(
heroEnabled: Boolean, heroEnabled: Boolean,
showCatalogType: Boolean, showCatalogType: Boolean,
hideUnreleasedContent: Boolean, hideUnreleasedContent: Boolean,
hideCatalogUnderline: Boolean,
items: List<HomeCatalogSettingsItem>, items: List<HomeCatalogSettingsItem>,
) { ) {
val selectedHeroSourceCount = items.count { it.heroSourceEnabled } val selectedHeroSourceCount = items.count { it.heroSourceEnabled }
@ -112,14 +109,6 @@ internal fun LazyListScope.homescreenSettingsContent(
isTablet = isTablet, isTablet = isTablet,
onCheckedChange = HomeCatalogSettingsRepository::setHideUnreleasedContent, onCheckedChange = HomeCatalogSettingsRepository::setHideUnreleasedContent,
) )
SettingsGroupDivider(isTablet = isTablet)
SettingsSwitchRow(
title = stringResource(Res.string.settings_homescreen_hide_catalog_underline),
description = stringResource(Res.string.settings_homescreen_hide_catalog_underline_description),
checked = hideCatalogUnderline,
isTablet = isTablet,
onCheckedChange = HomeCatalogSettingsRepository::setHideCatalogUnderline,
)
} }
} }
} }

View file

@ -81,7 +81,6 @@ fun HomescreenSettingsScreen(
heroEnabled = homescreenSettingsUiState.heroEnabled, heroEnabled = homescreenSettingsUiState.heroEnabled,
showCatalogType = homescreenSettingsUiState.showCatalogType, showCatalogType = homescreenSettingsUiState.showCatalogType,
hideUnreleasedContent = homescreenSettingsUiState.hideUnreleasedContent, hideUnreleasedContent = homescreenSettingsUiState.hideUnreleasedContent,
hideCatalogUnderline = homescreenSettingsUiState.hideCatalogUnderline,
items = homescreenSettingsUiState.items, items = homescreenSettingsUiState.items,
) )
} }

View file

@ -405,7 +405,6 @@ fun SettingsScreen(
homescreenHeroEnabled = homescreenSettingsUiState.heroEnabled, homescreenHeroEnabled = homescreenSettingsUiState.heroEnabled,
homescreenShowCatalogType = homescreenSettingsUiState.showCatalogType, homescreenShowCatalogType = homescreenSettingsUiState.showCatalogType,
homescreenHideUnreleasedContent = homescreenSettingsUiState.hideUnreleasedContent, homescreenHideUnreleasedContent = homescreenSettingsUiState.hideUnreleasedContent,
homescreenHideCatalogUnderline = homescreenSettingsUiState.hideCatalogUnderline,
homescreenItems = homescreenSettingsUiState.items, homescreenItems = homescreenSettingsUiState.items,
metaScreenSettingsUiState = metaScreenSettingsUiState, metaScreenSettingsUiState = metaScreenSettingsUiState,
continueWatchingPreferencesUiState = continueWatchingPreferencesUiState, continueWatchingPreferencesUiState = continueWatchingPreferencesUiState,
@ -466,7 +465,6 @@ fun SettingsScreen(
homescreenHeroEnabled = homescreenSettingsUiState.heroEnabled, homescreenHeroEnabled = homescreenSettingsUiState.heroEnabled,
homescreenShowCatalogType = homescreenSettingsUiState.showCatalogType, homescreenShowCatalogType = homescreenSettingsUiState.showCatalogType,
homescreenHideUnreleasedContent = homescreenSettingsUiState.hideUnreleasedContent, homescreenHideUnreleasedContent = homescreenSettingsUiState.hideUnreleasedContent,
homescreenHideCatalogUnderline = homescreenSettingsUiState.hideCatalogUnderline,
homescreenItems = homescreenSettingsUiState.items, homescreenItems = homescreenSettingsUiState.items,
metaScreenSettingsUiState = metaScreenSettingsUiState, metaScreenSettingsUiState = metaScreenSettingsUiState,
continueWatchingPreferencesUiState = continueWatchingPreferencesUiState, continueWatchingPreferencesUiState = continueWatchingPreferencesUiState,
@ -537,7 +535,6 @@ private fun MobileSettingsScreen(
homescreenHeroEnabled: Boolean, homescreenHeroEnabled: Boolean,
homescreenShowCatalogType: Boolean, homescreenShowCatalogType: Boolean,
homescreenHideUnreleasedContent: Boolean, homescreenHideUnreleasedContent: Boolean,
homescreenHideCatalogUnderline: Boolean,
homescreenItems: List<HomeCatalogSettingsItem>, homescreenItems: List<HomeCatalogSettingsItem>,
metaScreenSettingsUiState: MetaScreenSettingsUiState, metaScreenSettingsUiState: MetaScreenSettingsUiState,
continueWatchingPreferencesUiState: ContinueWatchingPreferencesUiState, continueWatchingPreferencesUiState: ContinueWatchingPreferencesUiState,
@ -768,7 +765,6 @@ private fun MobileSettingsScreen(
heroEnabled = homescreenHeroEnabled, heroEnabled = homescreenHeroEnabled,
showCatalogType = homescreenShowCatalogType, showCatalogType = homescreenShowCatalogType,
hideUnreleasedContent = homescreenHideUnreleasedContent, hideUnreleasedContent = homescreenHideUnreleasedContent,
hideCatalogUnderline = homescreenHideCatalogUnderline,
items = homescreenItems, items = homescreenItems,
) )
SettingsPage.MetaScreen -> metaScreenSettingsContent( SettingsPage.MetaScreen -> metaScreenSettingsContent(
@ -895,7 +891,6 @@ private fun TabletSettingsScreen(
homescreenHeroEnabled: Boolean, homescreenHeroEnabled: Boolean,
homescreenShowCatalogType: Boolean, homescreenShowCatalogType: Boolean,
homescreenHideUnreleasedContent: Boolean, homescreenHideUnreleasedContent: Boolean,
homescreenHideCatalogUnderline: Boolean,
homescreenItems: List<HomeCatalogSettingsItem>, homescreenItems: List<HomeCatalogSettingsItem>,
metaScreenSettingsUiState: MetaScreenSettingsUiState, metaScreenSettingsUiState: MetaScreenSettingsUiState,
continueWatchingPreferencesUiState: ContinueWatchingPreferencesUiState, continueWatchingPreferencesUiState: ContinueWatchingPreferencesUiState,
@ -1182,7 +1177,6 @@ private fun TabletSettingsScreen(
heroEnabled = homescreenHeroEnabled, heroEnabled = homescreenHeroEnabled,
showCatalogType = homescreenShowCatalogType, showCatalogType = homescreenShowCatalogType,
hideUnreleasedContent = homescreenHideUnreleasedContent, hideUnreleasedContent = homescreenHideUnreleasedContent,
hideCatalogUnderline = homescreenHideCatalogUnderline,
items = homescreenItems, items = homescreenItems,
) )
SettingsPage.MetaScreen -> metaScreenSettingsContent( SettingsPage.MetaScreen -> metaScreenSettingsContent(

View file

@ -732,7 +732,6 @@ internal fun settingsSearchEntries(
PlaybackSearchRow("home-hero", stringResource(Res.string.settings_homescreen_show_hero), stringResource(Res.string.settings_homescreen_show_hero_description)), PlaybackSearchRow("home-hero", stringResource(Res.string.settings_homescreen_show_hero), stringResource(Res.string.settings_homescreen_show_hero_description)),
PlaybackSearchRow("home-catalog-type", stringResource(Res.string.layout_catalog_type), stringResource(Res.string.layout_catalog_type_sub)), PlaybackSearchRow("home-catalog-type", stringResource(Res.string.layout_catalog_type), stringResource(Res.string.layout_catalog_type_sub)),
PlaybackSearchRow("home-hide-unreleased", stringResource(Res.string.layout_hide_unreleased), stringResource(Res.string.layout_hide_unreleased_sub)), PlaybackSearchRow("home-hide-unreleased", stringResource(Res.string.layout_hide_unreleased), stringResource(Res.string.layout_hide_unreleased_sub)),
PlaybackSearchRow("home-hide-catalog-underline", stringResource(Res.string.settings_homescreen_hide_catalog_underline), stringResource(Res.string.settings_homescreen_hide_catalog_underline_description)),
PlaybackSearchRow("home-hero-sources", stringResource(Res.string.settings_homescreen_section_hero_sources)), PlaybackSearchRow("home-hero-sources", stringResource(Res.string.settings_homescreen_section_hero_sources)),
PlaybackSearchRow("home-catalogs", stringResource(Res.string.settings_homescreen_section_catalogs)), PlaybackSearchRow("home-catalogs", stringResource(Res.string.settings_homescreen_section_catalogs)),
).forEach { row -> ).forEach { row ->

View file

@ -36,13 +36,11 @@ class HomeHeroSectionTest {
style = ContinueWatchingSectionStyle.Card, style = ContinueWatchingSectionStyle.Card,
layout = continueWatchingLayout, layout = continueWatchingLayout,
basePosterWidthDp = 110, basePosterWidthDp = 110,
showHeaderAccent = true,
) )
val reserveHeight = continueWatchingHeroViewportReserveHeight( val reserveHeight = continueWatchingHeroViewportReserveHeight(
style = ContinueWatchingSectionStyle.Card, style = ContinueWatchingSectionStyle.Card,
layout = continueWatchingLayout, layout = continueWatchingLayout,
basePosterWidthDp = 110, basePosterWidthDp = 110,
showHeaderAccent = true,
) )
val layout = homeHeroLayout( val layout = homeHeroLayout(
maxWidthDp = 390f, maxWidthDp = 390f,

View file

@ -43,6 +43,8 @@ interface NuvioPlayerBridge {
) )
fun configureAudioOutput(audioOutput: String) fun configureAudioOutput(audioOutput: String)
fun setPlaybackSpeed(speed: Float) fun setPlaybackSpeed(speed: Float)
fun getVolume(): Float
fun setVolume(volume: Float)
fun setMuted(muted: Boolean) fun setMuted(muted: Boolean)
fun setResizeMode(mode: Int) // 0=Fit, 1=Fill, 2=Zoom fun setResizeMode(mode: Int) // 0=Fit, 1=Fill, 2=Zoom
fun syncVideoSurfaceLayout(width: Double, height: Double) fun syncVideoSurfaceLayout(width: Double, height: Double)

View file

@ -130,6 +130,23 @@ actual fun PlatformPlayerSurface(
bridge.setPlaybackSpeed(speed) bridge.setPlaybackSpeed(speed)
} }
override fun currentPlayerVolume(): PlayerAudioLevel {
val current = bridge.getVolume().coerceIn(0f, 2f)
return PlayerAudioLevel(
fraction = current,
isMuted = current <= 0.001f,
)
}
override fun setPlayerVolume(level: Float): PlayerAudioLevel {
val target = level.coerceIn(0f, 2f)
bridge.setVolume(target)
return PlayerAudioLevel(
fraction = target,
isMuted = target <= 0.001f,
)
}
override fun setMuted(muted: Boolean) { override fun setMuted(muted: Boolean) {
bridge.setMuted(muted) bridge.setMuted(muted)
} }

View file

@ -102,6 +102,8 @@ final class MPVPlayerBridgeImpl: NSObject, NuvioPlayerBridge {
playerVC?.configureAudioOutput(audioOutput: audioOutput) playerVC?.configureAudioOutput(audioOutput: audioOutput)
} }
func setPlaybackSpeed(speed: Float) { playerVC?.setSpeed(speed) } func setPlaybackSpeed(speed: Float) { playerVC?.setSpeed(speed) }
func getVolume() -> Float { playerVC?.getVolume() ?? 1.0 }
func setVolume(volume: Float) { playerVC?.setVolume(volume) }
func setMuted(muted: Bool) { playerVC?.setMuted(muted) } func setMuted(muted: Bool) { playerVC?.setMuted(muted) }
func setResizeMode(mode: Int32) { playerVC?.setResize(Int(mode)) } func setResizeMode(mode: Int32) { playerVC?.setResize(Int(mode)) }
func syncVideoSurfaceLayout(width: Double, height: Double) { func syncVideoSurfaceLayout(width: Double, height: Double) {
@ -473,6 +475,10 @@ final class MPVPlayerViewController: UIViewController {
checkError(mpv_set_option_string(mpv, "ao", Self.defaultAudioOutput)) checkError(mpv_set_option_string(mpv, "ao", Self.defaultAudioOutput))
checkError(mpv_set_option_string(mpv, "audio-channels", "auto")) checkError(mpv_set_option_string(mpv, "audio-channels", "auto"))
checkError(mpv_set_option_string(mpv, "audio-fallback-to-null", "yes")) checkError(mpv_set_option_string(mpv, "audio-fallback-to-null", "yes"))
checkError(mpv_set_option_string(mpv, "volume-max", "200"))
checkError(mpv_set_option_string(mpv, "volume", "100"))
checkError(mpv_set_option_string(mpv, "volume-gain-max", "6.1"))
checkError(mpv_set_option_string(mpv, "volume-gain", "0"))
checkError(mpv_set_option_string(mpv, "vulkan-swap-mode", "fifo")) checkError(mpv_set_option_string(mpv, "vulkan-swap-mode", "fifo"))
checkError(mpv_set_option_string(mpv, "vulkan-queue-count", "1")) checkError(mpv_set_option_string(mpv, "vulkan-queue-count", "1"))
checkError(mpv_set_option_string(mpv, "vulkan-async-compute", "no")) checkError(mpv_set_option_string(mpv, "vulkan-async-compute", "no"))
@ -695,6 +701,34 @@ final class MPVPlayerViewController: UIViewController {
mpv_set_property(mpv, "speed", MPV_FORMAT_DOUBLE, &s) mpv_set_property(mpv, "speed", MPV_FORMAT_DOUBLE, &s)
} }
func getVolume() -> Float {
guard mpv != nil else { return 1.0 }
let baseVolume = getDouble("volume") / 100.0
let gainDb = getDouble("volume-gain")
let gainMultiplier = pow(10.0, gainDb / 20.0)
return Float(max(0.0, min(2.0, baseVolume * gainMultiplier)))
}
func setVolume(_ volume: Float) {
guard mpv != nil else { return }
let clamped = max(0.0, min(2.0, Double(volume)))
if clamped <= 0.001 {
var mutedVolume = 0.0
var neutralGain = 0.0
checkError(mpv_set_property(mpv, "volume", MPV_FORMAT_DOUBLE, &mutedVolume))
checkError(mpv_set_property(mpv, "volume-gain", MPV_FORMAT_DOUBLE, &neutralGain))
return
}
// Keep mpv's base volume at 100 and apply real software amplification through volume-gain.
// 2.0x equals about +6.02 dB.
var baseVolume = 100.0
var gainDb = 20.0 * log10(clamped)
checkError(mpv_set_property(mpv, "volume", MPV_FORMAT_DOUBLE, &baseVolume))
checkError(mpv_set_property(mpv, "volume-gain", MPV_FORMAT_DOUBLE, &gainDb))
}
func setMuted(_ muted: Bool) { func setMuted(_ muted: Bool) {
guard mpv != nil else { return } guard mpv != nil else { return }
setFlag("mute", muted) setFlag("mute", muted)