mirror of
https://github.com/NuvioMedia/NuvioMobile.git
synced 2026-07-26 09:02:26 +00:00
Compare commits
24 commits
a0a47de629
...
31d6272091
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31d6272091 | ||
|
|
b60995324b | ||
|
|
f1e4f88bc3 | ||
|
|
7f254a9f2e | ||
|
|
2fda777249 | ||
|
|
79269f461a | ||
|
|
71961de4a2 | ||
|
|
978c775954 | ||
|
|
2465d4e6b2 | ||
|
|
a495222f12 | ||
|
|
a3e309fef9 | ||
|
|
83bc15441b | ||
|
|
bfd8c3483e | ||
|
|
240cde9db3 | ||
|
|
80e7373aa2 | ||
|
|
32fbe672a5 | ||
|
|
86e35fd2fa | ||
|
|
645a5e58a7 | ||
|
|
dc003852e1 | ||
|
|
220bef6d1a | ||
|
|
c075178ff8 | ||
|
|
65a8162cbb | ||
|
|
3afd58c3c1 | ||
|
|
123549a1a5 |
47 changed files with 268 additions and 188 deletions
2
MPVKit
2
MPVKit
|
|
@ -1 +1 @@
|
|||
Subproject commit d5cf091c80368bbbc1bbf2d195fbc55d926df888
|
||||
Subproject commit 97923266e43d52bbca33a911fd6a7f9a1bcf35cb
|
||||
|
|
@ -568,8 +568,6 @@
|
|||
<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_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_hint">Otvorite katalog samo kada ga trebate preimenovati ili promijeniti njegov redoslijed.</string>
|
||||
<string name="settings_homescreen_visible">Vidljivo</string>
|
||||
|
|
|
|||
|
|
@ -42,10 +42,14 @@ import androidx.media3.common.TrackSelectionOverride
|
|||
import androidx.media3.common.text.Cue
|
||||
import androidx.media3.common.text.CueGroup
|
||||
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.DataSpec
|
||||
import androidx.media3.datasource.DefaultDataSource
|
||||
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.DefaultRenderersFactory
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
|
|
@ -78,6 +82,8 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.withContext
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
private const val TAG = "NuvioPlayer"
|
||||
private const val PLAYER_DIAGNOSTIC_TAG = "NuvioPlayerDiag"
|
||||
|
|
@ -257,6 +263,7 @@ private fun ExoPlayerSurface(
|
|||
var decoderPriorityOverride by remember(playerSourceKey) { mutableStateOf<Int?>(null) }
|
||||
var fallbackStartPositionMs by remember(playerSourceKey) { mutableStateOf<Long?>(null) }
|
||||
val effectiveDecoderPriority = decoderPriorityOverride ?: playerSettings.decoderPriority
|
||||
val volumeBoostAudioProcessor = remember(playerSourceKey) { VolumeBoostAudioProcessor() }
|
||||
|
||||
val initialMediaItem = remember(playerSourceKey, externalSubtitles) {
|
||||
val subtitleConfigs = externalSubtitles.mapNotNull { subtitle ->
|
||||
|
|
@ -342,6 +349,7 @@ private fun ExoPlayerSurface(
|
|||
shouldNormalizeCuePositionProvider = {
|
||||
latestExternalSubtitleMimeType.value == MimeTypes.TEXT_VTT
|
||||
},
|
||||
volumeBoostAudioProcessor = volumeBoostAudioProcessor,
|
||||
)
|
||||
.setExtensionRendererMode(effectiveDecoderPriority)
|
||||
.setEnableDecoderFallback(true)
|
||||
|
|
@ -702,6 +710,27 @@ private fun ExoPlayerSurface(
|
|||
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) {
|
||||
nowPlayingController.updateMetadata(info)
|
||||
}
|
||||
|
|
@ -1794,12 +1823,60 @@ private fun ExoPlayer.logCurrentTracks(context: String) {
|
|||
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)
|
||||
private class SubtitleOffsetRenderersFactory(
|
||||
context: Context,
|
||||
private val subtitleDelayUsProvider: () -> Long,
|
||||
private val shouldNormalizeCuePositionProvider: () -> Boolean,
|
||||
private val volumeBoostAudioProcessor: VolumeBoostAudioProcessor,
|
||||
) : 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(
|
||||
context: Context,
|
||||
output: TextOutput,
|
||||
|
|
|
|||
|
|
@ -570,8 +570,6 @@
|
|||
<string name="settings_homescreen_show_hero_description">Покажи въртележка в горната част на началото.</string>
|
||||
<string name="layout_hide_unreleased">Скрий невипуснатото съдържание</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_hint">Отворете каталог само когато трябва да го преименувате или пренаредите.</string>
|
||||
<string name="settings_homescreen_visible">Видим</string>
|
||||
|
|
|
|||
|
|
@ -582,8 +582,6 @@
|
|||
<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_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_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>
|
||||
|
|
|
|||
|
|
@ -1723,8 +1723,6 @@
|
|||
<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_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_licenses_attributions_exoplayer_body">Χρησιμοποιείται για αναπαραγωγή σε εκδόσεις Android.</string>
|
||||
<string name="settings_licenses_attributions_exoplayer_license">Licensed under the Apache License, Έκδοση 2.0.</string>
|
||||
|
|
|
|||
|
|
@ -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="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="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_hint">Abre un catálogo solo cuando necesites cambiarle el nombre o reordenarlo.</string>
|
||||
<string name="settings_homescreen_visible">Visible</string>
|
||||
|
|
|
|||
|
|
@ -592,8 +592,6 @@
|
|||
<string name="settings_homescreen_show_hero_description">Afficher un carrousel Hero en vedette en haut de l’accueil. Choisissez jusqu’à 2 catalogues sources ci-dessous.</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="settings_homescreen_hide_catalog_underline">Masquer le soulignement des catalogues</string>
|
||||
<string name="settings_homescreen_hide_catalog_underline_description">Supprime la ligne d’accent sous les titres de catalogues et de collections dans toute l’app.</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_visible">Visible</string>
|
||||
|
|
|
|||
|
|
@ -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="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="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_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>
|
||||
|
|
|
|||
|
|
@ -547,8 +547,6 @@
|
|||
<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_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_hint">Buka katalog hanya jika perlu mengganti nama atau mengurutkannya.</string>
|
||||
<string name="settings_homescreen_visible">Terlihat</string>
|
||||
|
|
|
|||
|
|
@ -547,8 +547,6 @@
|
|||
<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_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_hint">Buka katalog hanya jika perlu mengganti nama atau mengurutkannya.</string>
|
||||
<string name="settings_homescreen_visible">Terlihat</string>
|
||||
|
|
|
|||
|
|
@ -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="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="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_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>
|
||||
|
|
|
|||
|
|
@ -582,8 +582,6 @@
|
|||
<string name="settings_homescreen_show_hero_description">ホーム上部にヒーローカルーセルを表示します。</string>
|
||||
<string name="layout_hide_unreleased">未公開コンテンツを非表示</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_hint">カタログは名前の変更や並べ替えが必要なときのみ開いてください。</string>
|
||||
<string name="settings_homescreen_visible">表示</string>
|
||||
|
|
|
|||
|
|
@ -531,8 +531,6 @@
|
|||
<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_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_hint">Åpne en katalog kun når du skal gi nytt navn eller endre rekkefølge.</string>
|
||||
<string name="settings_homescreen_visible">Synlig</string>
|
||||
|
|
|
|||
|
|
@ -600,8 +600,6 @@
|
|||
<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_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_hint">Open een catalogus alleen als je deze wilt hernoemen of herordenen.</string>
|
||||
<string name="settings_homescreen_visible">Zichtbaar</string>
|
||||
|
|
|
|||
|
|
@ -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="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="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_hint">Otwórz katalog tylko wtedy, gdy chcesz zmienić jego nazwę lub kolejność.</string>
|
||||
<string name="settings_homescreen_visible">Widoczne</string>
|
||||
|
|
|
|||
|
|
@ -532,8 +532,6 @@
|
|||
<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_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_hint">Abra um catálogo apenas quando precisar renomeá-lo ou reordená-lo.</string>
|
||||
<string name="settings_homescreen_visible">Visível</string>
|
||||
|
|
|
|||
|
|
@ -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="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="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_hint">Abre um catálogo apenas quando precisares de o renomear ou reordenar.</string>
|
||||
<string name="settings_homescreen_visible">Visível</string>
|
||||
|
|
|
|||
|
|
@ -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="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="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_hint">Deschide un catalog doar când trebuie să îl redenumești sau să îl rearanjezi.</string>
|
||||
<string name="settings_homescreen_visible">Vizibil</string>
|
||||
|
|
|
|||
|
|
@ -571,8 +571,6 @@
|
|||
<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_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_hint">Otvorte katalóg iba vtedy, keď ho potrebujete premenovať alebo zmeniť poradie.</string>
|
||||
<string name="settings_homescreen_visible">Viditeľné</string>
|
||||
|
|
|
|||
|
|
@ -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="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="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_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>
|
||||
|
|
|
|||
|
|
@ -579,8 +579,6 @@
|
|||
<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_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_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>
|
||||
|
|
@ -1973,4 +1971,4 @@
|
|||
<string name="settings_card_depth_surface_cast">Diễn viên</string>
|
||||
<string name="store_empty_unavailable_title">Chưa có nội dung</string>
|
||||
<string name="store_empty_unavailable_message">Các addon hiện tại không cung cấp nội dung cho mục này</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -609,8 +609,6 @@
|
|||
<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_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_hint">Open a catalog only when you need to rename or reorder it.</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_provider_all">All</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_type">Select type</string>
|
||||
<string name="cloud_library_status_ready">Ready to play</string>
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ fun <T> NuvioShelfSection(
|
|||
rowContentPadding: PaddingValues = PaddingValues(0.dp),
|
||||
rowModifier: Modifier = Modifier,
|
||||
itemSpacing: Dp = 10.dp,
|
||||
showHeaderAccent: Boolean = true,
|
||||
onViewAllClick: (() -> Unit)? = null,
|
||||
viewAllPillSize: NuvioViewAllPillSize = NuvioViewAllPillSize.Default,
|
||||
key: ((T) -> Any)? = null,
|
||||
|
|
@ -85,7 +84,6 @@ fun <T> NuvioShelfSection(
|
|||
NuvioShelfSectionHeader(
|
||||
title = title,
|
||||
modifier = Modifier.padding(horizontal = headerHorizontalPadding),
|
||||
showAccent = showHeaderAccent,
|
||||
onViewAllClick = onViewAllClick,
|
||||
viewAllPillSize = viewAllPillSize,
|
||||
)
|
||||
|
|
@ -244,7 +242,6 @@ fun NuvioPosterCard(
|
|||
private fun NuvioShelfSectionHeader(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
showAccent: Boolean = true,
|
||||
onViewAllClick: (() -> Unit)? = null,
|
||||
viewAllPillSize: NuvioViewAllPillSize = NuvioViewAllPillSize.Default,
|
||||
) {
|
||||
|
|
@ -278,18 +275,6 @@ private fun NuvioShelfSectionHeader(
|
|||
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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ fun DetailPosterRailSection(
|
|||
horizontal = headerHorizontalPadding + horizontalScrollPadding,
|
||||
),
|
||||
rowModifier = Modifier.nuvioHorizontalScrollBleed(horizontalScrollPadding),
|
||||
showHeaderAccent = false,
|
||||
key = { item -> item.stableKey() },
|
||||
) { item ->
|
||||
HomePosterCard(
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ data class HomeCatalogSettingsUiState(
|
|||
val heroEnabled: Boolean = true,
|
||||
val showCatalogType: Boolean = true,
|
||||
val hideUnreleasedContent: Boolean = false,
|
||||
val hideCatalogUnderline: Boolean = false,
|
||||
val items: List<HomeCatalogSettingsItem> = emptyList(),
|
||||
) {
|
||||
val signature: String
|
||||
|
|
@ -45,8 +44,6 @@ data class HomeCatalogSettingsUiState(
|
|||
append('|')
|
||||
append(hideUnreleasedContent)
|
||||
append('|')
|
||||
append(hideCatalogUnderline)
|
||||
append('|')
|
||||
append(
|
||||
items.joinToString(separator = "|") { item ->
|
||||
"${item.key}:${item.order}:${item.enabled}:${item.heroSourceEnabled}:${item.customTitle}"
|
||||
|
|
@ -66,7 +63,6 @@ internal data class HomeCatalogSettingsSnapshot(
|
|||
val heroEnabled: Boolean,
|
||||
val showCatalogType: Boolean,
|
||||
val hideUnreleasedContent: Boolean,
|
||||
val hideCatalogUnderline: Boolean,
|
||||
val preferences: Map<String, HomeCatalogPreference>,
|
||||
)
|
||||
|
||||
|
|
@ -84,7 +80,6 @@ private data class StoredHomeCatalogSettingsPayload(
|
|||
val heroEnabled: Boolean = true,
|
||||
val showCatalogType: Boolean = true,
|
||||
val hideUnreleasedContent: Boolean = false,
|
||||
val hideCatalogUnderline: Boolean = false,
|
||||
val items: List<StoredHomeCatalogPreference> = emptyList(),
|
||||
)
|
||||
|
||||
|
|
@ -106,7 +101,6 @@ object HomeCatalogSettingsRepository {
|
|||
private var heroEnabled = true
|
||||
private var showCatalogType = true
|
||||
private var hideUnreleasedContent = false
|
||||
private var hideCatalogUnderline = false
|
||||
|
||||
fun onProfileChanged() {
|
||||
hasLoaded = false
|
||||
|
|
@ -114,7 +108,6 @@ object HomeCatalogSettingsRepository {
|
|||
heroEnabled = true
|
||||
showCatalogType = true
|
||||
hideUnreleasedContent = false
|
||||
hideCatalogUnderline = false
|
||||
definitions = emptyList()
|
||||
collectionDefinitions = emptyList()
|
||||
_uiState.value = HomeCatalogSettingsUiState()
|
||||
|
|
@ -128,7 +121,6 @@ object HomeCatalogSettingsRepository {
|
|||
heroEnabled = true
|
||||
showCatalogType = true
|
||||
hideUnreleasedContent = false
|
||||
hideCatalogUnderline = false
|
||||
_uiState.value = HomeCatalogSettingsUiState()
|
||||
}
|
||||
|
||||
|
|
@ -162,7 +154,6 @@ object HomeCatalogSettingsRepository {
|
|||
heroEnabled = heroEnabled,
|
||||
showCatalogType = showCatalogType,
|
||||
hideUnreleasedContent = hideUnreleasedContent,
|
||||
hideCatalogUnderline = hideCatalogUnderline,
|
||||
preferences = preferences.mapValues { (_, value) ->
|
||||
HomeCatalogPreference(
|
||||
customTitle = value.customTitle,
|
||||
|
|
@ -202,15 +193,6 @@ object HomeCatalogSettingsRepository {
|
|||
HomeCatalogSettingsSyncService.triggerPush()
|
||||
}
|
||||
|
||||
fun setHideCatalogUnderline(enabled: Boolean) {
|
||||
ensureLoaded()
|
||||
if (hideCatalogUnderline == enabled) return
|
||||
hideCatalogUnderline = enabled
|
||||
publish()
|
||||
persist()
|
||||
HomeCatalogSettingsSyncService.triggerPush()
|
||||
}
|
||||
|
||||
fun setHeroSourceEnabled(key: String, enabled: Boolean) {
|
||||
updatePreference(key, pushRemote = false) { preference ->
|
||||
if (!enabled) {
|
||||
|
|
@ -240,7 +222,6 @@ object HomeCatalogSettingsRepository {
|
|||
heroEnabled = true
|
||||
showCatalogType = true
|
||||
hideUnreleasedContent = false
|
||||
hideCatalogUnderline = false
|
||||
preferences.clear()
|
||||
normalizePreferences()
|
||||
publish()
|
||||
|
|
@ -290,7 +271,6 @@ object HomeCatalogSettingsRepository {
|
|||
heroEnabled = parsedPayload.heroEnabled
|
||||
showCatalogType = parsedPayload.showCatalogType
|
||||
hideUnreleasedContent = parsedPayload.hideUnreleasedContent
|
||||
hideCatalogUnderline = parsedPayload.hideCatalogUnderline
|
||||
preferences = parsedPayload.items.associateBy { it.key }.toMutableMap()
|
||||
publish()
|
||||
return
|
||||
|
|
@ -391,7 +371,6 @@ object HomeCatalogSettingsRepository {
|
|||
heroEnabled = heroEnabled,
|
||||
showCatalogType = showCatalogType,
|
||||
hideUnreleasedContent = hideUnreleasedContent,
|
||||
hideCatalogUnderline = hideCatalogUnderline,
|
||||
items = items,
|
||||
)
|
||||
}
|
||||
|
|
@ -403,7 +382,6 @@ object HomeCatalogSettingsRepository {
|
|||
heroEnabled = heroEnabled,
|
||||
showCatalogType = showCatalogType,
|
||||
hideUnreleasedContent = hideUnreleasedContent,
|
||||
hideCatalogUnderline = hideCatalogUnderline,
|
||||
items = preferences.values.sortedBy { it.order },
|
||||
),
|
||||
),
|
||||
|
|
@ -500,7 +478,6 @@ object HomeCatalogSettingsRepository {
|
|||
return SyncHomeCatalogPayload(
|
||||
showCatalogType = showCatalogType,
|
||||
hideUnreleasedContent = hideUnreleasedContent,
|
||||
hideCatalogUnderline = hideCatalogUnderline,
|
||||
items = items,
|
||||
)
|
||||
}
|
||||
|
|
@ -509,7 +486,6 @@ object HomeCatalogSettingsRepository {
|
|||
ensureLoaded()
|
||||
showCatalogType = payload.showCatalogType
|
||||
hideUnreleasedContent = payload.hideUnreleasedContent
|
||||
hideCatalogUnderline = payload.hideCatalogUnderline
|
||||
if (payload.items.isNotEmpty()) {
|
||||
val existingHeroState = preferences.mapValues { it.value.heroSourceEnabled }
|
||||
val remotePreferences = payload.items.associate { item ->
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ data class SyncCatalogItem(
|
|||
data class SyncHomeCatalogPayload(
|
||||
@SerialName("show_catalog_type") val showCatalogType: Boolean = true,
|
||||
@SerialName("hide_unreleased_content") val hideUnreleasedContent: Boolean = false,
|
||||
@SerialName("hide_catalog_underline") val hideCatalogUnderline: Boolean = false,
|
||||
val items: List<SyncCatalogItem> = emptyList(),
|
||||
)
|
||||
|
||||
|
|
@ -59,7 +58,6 @@ private data class RemoteHomeCatalogSettings(
|
|||
val updatedAt: String?,
|
||||
val hasShowCatalogType: Boolean,
|
||||
val hasHideUnreleasedContent: Boolean,
|
||||
val hasHideCatalogUnderline: Boolean,
|
||||
)
|
||||
|
||||
private data class PullToken(
|
||||
|
|
@ -77,7 +75,6 @@ object HomeCatalogSettingsSyncService {
|
|||
|
||||
private const val HIDE_UNRELEASED_CONTENT_KEY = "hide_unreleased_content"
|
||||
private const val SHOW_CATALOG_TYPE_KEY = "show_catalog_type"
|
||||
private const val HIDE_CATALOG_UNDERLINE_KEY = "hide_catalog_underline"
|
||||
|
||||
@Volatile
|
||||
var isSyncingFromRemote: Boolean = false
|
||||
|
|
@ -221,7 +218,6 @@ object HomeCatalogSettingsSyncService {
|
|||
updatedAt = blob.updatedAt,
|
||||
hasShowCatalogType = blob.settingsJson.containsKey(SHOW_CATALOG_TYPE_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
|
||||
.filter { it.hasShowCatalogType }
|
||||
.maxByOrNull { it.updatedAt.orEmpty() }
|
||||
val hideUnderlineSource = rows
|
||||
.filter { it.hasHideCatalogUnderline }
|
||||
.maxByOrNull { it.updatedAt.orEmpty() }
|
||||
|
||||
return copy(
|
||||
payload = payload.copy(
|
||||
|
|
@ -244,8 +237,6 @@ object HomeCatalogSettingsSyncService {
|
|||
?: payload.showCatalogType,
|
||||
hideUnreleasedContent = hideUnreleasedSource?.payload?.hideUnreleasedContent
|
||||
?: payload.hideUnreleasedContent,
|
||||
hideCatalogUnderline = hideUnderlineSource?.payload?.hideCatalogUnderline
|
||||
?: payload.hideCatalogUnderline,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -278,11 +269,6 @@ object HomeCatalogSettingsSyncService {
|
|||
} else {
|
||||
localPayload.hideUnreleasedContent
|
||||
},
|
||||
hideCatalogUnderline = if (settingsJson.containsKey(HIDE_CATALOG_UNDERLINE_KEY)) {
|
||||
decoded.hideCatalogUnderline
|
||||
} else {
|
||||
localPayload.hideCatalogUnderline
|
||||
},
|
||||
)
|
||||
}.getOrNull()
|
||||
|
||||
|
|
|
|||
|
|
@ -861,7 +861,6 @@ fun HomeScreen(
|
|||
hasContinueWatchingRows,
|
||||
continueWatchingLayout,
|
||||
posterCardStyle.widthDp,
|
||||
homeSettingsUiState.hideCatalogUnderline,
|
||||
nativeBottomNavigationOverlayHeight,
|
||||
) {
|
||||
if (
|
||||
|
|
@ -873,7 +872,6 @@ fun HomeScreen(
|
|||
style = continueWatchingPreferences.style,
|
||||
layout = continueWatchingLayout,
|
||||
basePosterWidthDp = posterCardStyle.widthDp,
|
||||
showHeaderAccent = !homeSettingsUiState.hideCatalogUnderline,
|
||||
) + nativeBottomNavigationOverlayHeight
|
||||
} else {
|
||||
null
|
||||
|
|
@ -958,7 +956,6 @@ fun HomeScreen(
|
|||
items(3) {
|
||||
HomeSkeletonRow(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
showHeaderAccent = !homeSettingsUiState.hideCatalogUnderline,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,11 @@ import androidx.compose.foundation.layout.BoxWithConstraints
|
|||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.nuvio.app.core.ui.NuvioShelfSection
|
||||
import com.nuvio.app.core.ui.NuvioViewAllPillSize
|
||||
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.MetaPreview
|
||||
import com.nuvio.app.features.home.stableKey
|
||||
|
|
@ -72,10 +68,6 @@ private fun HomeCatalogRowSectionContent(
|
|||
onPosterLongClick: ((MetaPreview) -> Unit)?,
|
||||
) {
|
||||
val posterCardStyle = rememberPosterCardStyleUiState()
|
||||
val homeCatalogSettings by remember {
|
||||
HomeCatalogSettingsRepository.snapshot()
|
||||
HomeCatalogSettingsRepository.uiState
|
||||
}.collectAsStateWithLifecycle()
|
||||
|
||||
NuvioShelfSection(
|
||||
title = section.title,
|
||||
|
|
@ -83,7 +75,6 @@ private fun HomeCatalogRowSectionContent(
|
|||
modifier = modifier,
|
||||
headerHorizontalPadding = sectionPadding,
|
||||
rowContentPadding = PaddingValues(horizontal = sectionPadding),
|
||||
showHeaderAccent = !homeCatalogSettings.hideCatalogUnderline,
|
||||
onViewAllClick = onViewAllClick,
|
||||
viewAllPillSize = NuvioViewAllPillSize.Compact,
|
||||
key = { item -> item.stableKey() },
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ import androidx.compose.material3.CardDefaults
|
|||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.nuvio.app.core.ui.NuvioCardDepthSurface
|
||||
import com.nuvio.app.core.ui.NuvioShelfSection
|
||||
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.features.collection.Collection
|
||||
import com.nuvio.app.features.collection.CollectionFolder
|
||||
import com.nuvio.app.features.home.HomeCatalogSettingsRepository
|
||||
import com.nuvio.app.features.home.PosterShape
|
||||
|
||||
@Composable
|
||||
|
|
@ -77,18 +73,12 @@ private fun HomeCollectionRowSectionContent(
|
|||
animateGifs: Boolean,
|
||||
onFolderClick: ((collectionId: String, folderId: String) -> Unit)?,
|
||||
) {
|
||||
val homeCatalogSettings by remember {
|
||||
HomeCatalogSettingsRepository.snapshot()
|
||||
HomeCatalogSettingsRepository.uiState
|
||||
}.collectAsStateWithLifecycle()
|
||||
|
||||
NuvioShelfSection(
|
||||
title = collection.title,
|
||||
entries = collection.folders,
|
||||
modifier = modifier,
|
||||
headerHorizontalPadding = sectionPadding,
|
||||
rowContentPadding = PaddingValues(horizontal = sectionPadding),
|
||||
showHeaderAccent = !homeCatalogSettings.hideCatalogUnderline,
|
||||
key = { folder -> "collection_${collection.id}_folder_${folder.id}" },
|
||||
) { folder ->
|
||||
CollectionFolderCard(
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ import androidx.compose.ui.unit.Dp
|
|||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil3.compose.AsyncImage
|
||||
import com.nuvio.app.core.ui.DisintegratingContainer
|
||||
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.features.cloud.CloudLibraryContentType
|
||||
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.ContinueWatchingSectionStyle
|
||||
import com.nuvio.app.features.watchprogress.CurrentDateProvider
|
||||
|
|
@ -87,13 +85,8 @@ internal fun continueWatchingSectionHeightEstimate(
|
|||
style: ContinueWatchingSectionStyle,
|
||||
layout: ContinueWatchingLayout,
|
||||
basePosterWidthDp: Int,
|
||||
showHeaderAccent: Boolean,
|
||||
): Dp {
|
||||
val headerHeight = NuvioTokens.Space.s40 + if (showHeaderAccent) {
|
||||
NuvioTokens.Space.s6 + NuvioTokens.Space.s4
|
||||
} else {
|
||||
0.dp
|
||||
}
|
||||
val headerHeight = NuvioTokens.Space.s40
|
||||
val headerToRowGap = NuvioTokens.Space.s8 + NuvioTokens.Space.s2
|
||||
val rowHeight = when (style) {
|
||||
ContinueWatchingSectionStyle.Card -> continueWatchingLandscapeCardHeight(basePosterWidthDp)
|
||||
|
|
@ -107,7 +100,6 @@ internal fun continueWatchingHeroViewportReserveHeight(
|
|||
style: ContinueWatchingSectionStyle,
|
||||
layout: ContinueWatchingLayout,
|
||||
basePosterWidthDp: Int,
|
||||
showHeaderAccent: Boolean,
|
||||
): Dp {
|
||||
val bottomNavigationClearance = when (style) {
|
||||
ContinueWatchingSectionStyle.Card,
|
||||
|
|
@ -118,7 +110,6 @@ internal fun continueWatchingHeroViewportReserveHeight(
|
|||
style = style,
|
||||
layout = layout,
|
||||
basePosterWidthDp = basePosterWidthDp,
|
||||
showHeaderAccent = showHeaderAccent,
|
||||
) + bottomNavigationClearance
|
||||
}
|
||||
|
||||
|
|
@ -291,11 +282,6 @@ private fun HomeContinueWatchingSectionContent(
|
|||
onItemClick: ((ContinueWatchingItem) -> Unit)?,
|
||||
onItemLongPress: ((ContinueWatchingItem) -> Unit)?,
|
||||
) {
|
||||
val homeCatalogSettings by remember {
|
||||
HomeCatalogSettingsRepository.snapshot()
|
||||
HomeCatalogSettingsRepository.uiState
|
||||
}.collectAsStateWithLifecycle()
|
||||
|
||||
val disintegration = remember { ContinueWatchingDisintegrationHolder() }
|
||||
val displayEntries = disintegration.sync(items)
|
||||
|
||||
|
|
@ -306,7 +292,6 @@ private fun HomeContinueWatchingSectionContent(
|
|||
headerHorizontalPadding = sectionPadding,
|
||||
rowContentPadding = PaddingValues(horizontal = sectionPadding),
|
||||
itemSpacing = layout.itemGap,
|
||||
showHeaderAccent = !homeCatalogSettings.hideCatalogUnderline,
|
||||
key = { entry -> entry.videoId },
|
||||
animatePlacement = true,
|
||||
state = listState,
|
||||
|
|
|
|||
|
|
@ -183,7 +183,6 @@ fun HomeSkeletonHero(
|
|||
@Composable
|
||||
fun HomeSkeletonRow(
|
||||
modifier: Modifier = Modifier,
|
||||
showHeaderAccent: Boolean = true,
|
||||
) {
|
||||
val brush = rememberHomeSkeletonBrush()
|
||||
val posterCardStyle = rememberPosterCardStyleUiState()
|
||||
|
|
@ -210,17 +209,6 @@ fun HomeSkeletonRow(
|
|||
.clip(RoundedCornerShape(6.dp))
|
||||
.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
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
|
|
|
|||
|
|
@ -31,15 +31,18 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
|
||||
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.PlayArrow
|
||||
import androidx.compose.material.icons.rounded.Refresh
|
||||
import androidx.compose.material.icons.rounded.Search
|
||||
import androidx.compose.material.icons.rounded.ViewAgenda
|
||||
import com.nuvio.app.core.ui.NuvioLoadingIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
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.CloudLibraryUiState
|
||||
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.HomePosterCard
|
||||
import com.nuvio.app.features.home.components.HomeSkeletonRow
|
||||
|
|
@ -120,10 +122,6 @@ fun LibraryScreen(
|
|||
LibraryDisplaySettingsRepository.ensureLoaded()
|
||||
LibraryDisplaySettingsRepository.uiState
|
||||
}.collectAsStateWithLifecycle()
|
||||
val homeCatalogSettingsUiState by remember {
|
||||
HomeCatalogSettingsRepository.snapshot()
|
||||
HomeCatalogSettingsRepository.uiState
|
||||
}.collectAsStateWithLifecycle()
|
||||
val networkStatusUiState by NetworkStatusRepository.uiState.collectAsStateWithLifecycle()
|
||||
var observedOfflineState by remember { mutableStateOf(false) }
|
||||
var sourceModeName by rememberSaveable { mutableStateOf(LibraryViewMode.Saved.name) }
|
||||
|
|
@ -132,6 +130,7 @@ fun LibraryScreen(
|
|||
}
|
||||
var selectedProviderId by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
var selectedTypeName by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
var cloudSearchQuery by rememberSaveable { mutableStateOf("") }
|
||||
val selectedType = remember(selectedTypeName) {
|
||||
selectedTypeName?.let { runCatching { CloudLibraryItemType.valueOf(it) }.getOrNull() }
|
||||
}
|
||||
|
|
@ -305,6 +304,11 @@ fun LibraryScreen(
|
|||
selectedProviderId = selectedProviderId,
|
||||
selectedType = selectedType,
|
||||
selectedCloudItemKey = selectedCloudItemKey,
|
||||
searchQuery = cloudSearchQuery,
|
||||
onSearchQueryChange = {
|
||||
cloudSearchQuery = it
|
||||
selectedCloudItemKey = null
|
||||
},
|
||||
onProviderSelected = {
|
||||
selectedProviderId = it
|
||||
selectedTypeName = null
|
||||
|
|
@ -335,7 +339,6 @@ fun LibraryScreen(
|
|||
items(3) {
|
||||
HomeSkeletonRow(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
showHeaderAccent = !homeCatalogSettingsUiState.hideCatalogUnderline,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -415,7 +418,6 @@ fun LibraryScreen(
|
|||
LibraryLayoutMode.HORIZONTAL -> librarySections(
|
||||
displaySections = librarySectionsDisplay,
|
||||
watchedKeys = watchedUiState.watchedKeys,
|
||||
showHeaderAccent = !homeCatalogSettingsUiState.hideCatalogUnderline,
|
||||
sortOption = effectiveSortOption,
|
||||
onPosterClick = onPosterClick,
|
||||
onSectionViewAllClick = onSectionViewAllClick,
|
||||
|
|
@ -443,6 +445,8 @@ private fun LazyListScope.cloudLibraryContent(
|
|||
selectedProviderId: String?,
|
||||
selectedType: CloudLibraryItemType?,
|
||||
selectedCloudItemKey: String?,
|
||||
searchQuery: String,
|
||||
onSearchQueryChange: (String) -> Unit,
|
||||
onProviderSelected: (String?) -> Unit,
|
||||
onTypeSelected: (CloudLibraryItemType?) -> Unit,
|
||||
onItemSelected: (CloudLibraryItem) -> Unit,
|
||||
|
|
@ -488,8 +492,19 @@ private fun LazyListScope.cloudLibraryContent(
|
|||
.distinct()
|
||||
.sortedBy { type -> type.ordinal }
|
||||
val effectiveSelectedType = selectedType?.takeIf { type -> type in availableTypes }
|
||||
val filteredItems = providerItems
|
||||
val typeFilteredItems = providerItems
|
||||
.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 }
|
||||
|
||||
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
|
||||
.filter { providerState -> selectedProviderId == null || providerState.providerId == selectedProviderId }
|
||||
.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() {
|
||||
item(key = "cloud-library-skeleton-toolbar") {
|
||||
CloudLibrarySkeletonToolbar(
|
||||
|
|
@ -1113,7 +1168,6 @@ private enum class LibraryViewMode {
|
|||
private fun LazyListScope.librarySections(
|
||||
displaySections: List<LibraryDisplaySection>,
|
||||
watchedKeys: Set<String>,
|
||||
showHeaderAccent: Boolean,
|
||||
sortOption: LibrarySortOption,
|
||||
onPosterClick: ((LibraryItem) -> Unit)?,
|
||||
onSectionViewAllClick: ((LibrarySection, LibrarySortOption) -> Unit)?,
|
||||
|
|
@ -1130,7 +1184,6 @@ private fun LazyListScope.librarySections(
|
|||
modifier = libraryContentTransitionModifier(),
|
||||
headerHorizontalPadding = 16.dp,
|
||||
rowContentPadding = PaddingValues(horizontal = 16.dp),
|
||||
showHeaderAccent = showHeaderAccent,
|
||||
onViewAllClick = section.source
|
||||
?.takeIf { it.items.size > LIBRARY_SECTION_PREVIEW_LIMIT }
|
||||
?.let { source -> onSectionViewAllClick?.let { { it(source, sortOption) } } },
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@ interface PlayerEngineController {
|
|||
fun seekBy(offsetMs: Long)
|
||||
fun retry()
|
||||
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 getAudioTracks(): List<AudioTrack>
|
||||
fun getSubtitleTracks(): List<SubtitleTrack>
|
||||
|
|
|
|||
|
|
@ -134,7 +134,9 @@ internal fun PlayerScreenRuntime.showBrightnessFeedback(level: Float) {
|
|||
}
|
||||
|
||||
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(
|
||||
GestureFeedbackState(
|
||||
messageRes = if (level.isMuted) {
|
||||
|
|
@ -144,7 +146,8 @@ internal fun PlayerScreenRuntime.showVolumeFeedback(level: PlayerAudioLevel) {
|
|||
},
|
||||
messageArgs = if (level.isMuted) emptyList() else listOf("$percentage%"),
|
||||
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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ internal fun PlayerScreenRuntime.RenderPlayerRuntimeUi() {
|
|||
)
|
||||
.playerSurfaceDragGestures(
|
||||
gestureController = gestureController,
|
||||
playerController = playerController,
|
||||
layoutSize = layoutSize,
|
||||
sideGestureSystemEdgeExclusionPx = sideGestureSystemEdgeExclusionPx,
|
||||
playerControlsLockedState = gestureCallbacks.playerControlsLocked,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ internal const val PlayerLockedOverlayDurationMs = 2_000L
|
|||
internal const val PlayerLeftGestureBoundary = 0.4f
|
||||
internal const val PlayerRightGestureBoundary = 0.6f
|
||||
internal const val PlayerVerticalGestureSensitivity = 0.65f
|
||||
internal const val PlayerNormalVolumeCeiling = 1f
|
||||
internal const val PlayerMaxVolumeBoost = 2f
|
||||
internal const val PlayerVerticalGestureTouchSlopMultiplier = 3f
|
||||
internal const val PlayerVerticalGestureMinHeightFraction = 0.06f
|
||||
internal const val PlayerVerticalGestureDominanceRatio = 1.2f
|
||||
|
|
@ -87,3 +89,45 @@ internal data class PendingPlayerP2pSwitch(
|
|||
val episode: MetaVideo?,
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ internal fun Modifier.playerSurfaceTapGestures(
|
|||
|
||||
internal fun Modifier.playerSurfaceDragGestures(
|
||||
gestureController: PlayerGestureController?,
|
||||
playerController: PlayerEngineController?,
|
||||
layoutSize: IntSize,
|
||||
sideGestureSystemEdgeExclusionPx: Float,
|
||||
playerControlsLockedState: State<Boolean>,
|
||||
|
|
@ -55,7 +56,7 @@ internal fun Modifier.playerSurfaceDragGestures(
|
|||
revealLockedOverlayState: State<() -> Unit>,
|
||||
commitHorizontalSeekState: State<(Long) -> Unit>,
|
||||
): Modifier =
|
||||
pointerInput(gestureController, layoutSize, sideGestureSystemEdgeExclusionPx) {
|
||||
pointerInput(gestureController, playerController, layoutSize, sideGestureSystemEdgeExclusionPx) {
|
||||
awaitEachGesture {
|
||||
val down = awaitFirstDown()
|
||||
if (playerControlsLockedState.value) {
|
||||
|
|
@ -91,7 +92,10 @@ internal fun Modifier.playerSurfaceDragGestures(
|
|||
null
|
||||
}
|
||||
val initialVolume = if (region == PlayerSideGesture.Volume) {
|
||||
controller?.currentVolume()
|
||||
currentCombinedVolumeLevel(
|
||||
systemVolume = controller?.currentVolume(),
|
||||
softwareVolume = playerController?.currentPlayerVolume(),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
@ -185,8 +189,13 @@ internal fun Modifier.playerSurfaceDragGestures(
|
|||
val activeTotalDy = totalDy - verticalGestureActivationDy
|
||||
val gestureDeltaFraction =
|
||||
(-activeTotalDy / height) * PlayerVerticalGestureSensitivity
|
||||
controller?.setVolume((initialVolume?.fraction ?: 0f) + gestureDeltaFraction)
|
||||
?.let(showVolumeFeedbackState.value)
|
||||
val target = ((initialVolume?.fraction ?: 0f) + gestureDeltaFraction)
|
||||
.coerceIn(0f, PlayerMaxVolumeBoost)
|
||||
setCombinedVolumeLevel(
|
||||
target = target,
|
||||
systemController = controller,
|
||||
engineController = playerController,
|
||||
)?.let(showVolumeFeedbackState.value)
|
||||
}
|
||||
}
|
||||
change.consume()
|
||||
|
|
|
|||
|
|
@ -318,7 +318,6 @@ fun SearchScreen(
|
|||
items(2) {
|
||||
HomeSkeletonRow(
|
||||
modifier = Modifier.padding(horizontal = homeSectionPadding),
|
||||
showHeaderAccent = !homeCatalogSettingsUiState.hideCatalogUnderline,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -327,7 +326,6 @@ fun SearchScreen(
|
|||
items(2) {
|
||||
HomeSkeletonRow(
|
||||
modifier = Modifier.padding(horizontal = homeSectionPadding),
|
||||
showHeaderAccent = !homeCatalogSettingsUiState.hideCatalogUnderline,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -371,7 +369,6 @@ fun SearchScreen(
|
|||
item(key = "search_loading_more") {
|
||||
HomeSkeletonRow(
|
||||
modifier = Modifier.padding(horizontal = homeSectionPadding),
|
||||
showHeaderAccent = !homeCatalogSettingsUiState.hideCatalogUnderline,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.settings_homescreen_empty_message
|
||||
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_limit_reached
|
||||
import nuvio.composeapp.generated.resources.settings_homescreen_no_sources_selected
|
||||
|
|
@ -70,7 +68,6 @@ internal fun LazyListScope.homescreenSettingsContent(
|
|||
heroEnabled: Boolean,
|
||||
showCatalogType: Boolean,
|
||||
hideUnreleasedContent: Boolean,
|
||||
hideCatalogUnderline: Boolean,
|
||||
items: List<HomeCatalogSettingsItem>,
|
||||
) {
|
||||
val selectedHeroSourceCount = items.count { it.heroSourceEnabled }
|
||||
|
|
@ -112,14 +109,6 @@ internal fun LazyListScope.homescreenSettingsContent(
|
|||
isTablet = isTablet,
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,6 @@ fun HomescreenSettingsScreen(
|
|||
heroEnabled = homescreenSettingsUiState.heroEnabled,
|
||||
showCatalogType = homescreenSettingsUiState.showCatalogType,
|
||||
hideUnreleasedContent = homescreenSettingsUiState.hideUnreleasedContent,
|
||||
hideCatalogUnderline = homescreenSettingsUiState.hideCatalogUnderline,
|
||||
items = homescreenSettingsUiState.items,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -405,7 +405,6 @@ fun SettingsScreen(
|
|||
homescreenHeroEnabled = homescreenSettingsUiState.heroEnabled,
|
||||
homescreenShowCatalogType = homescreenSettingsUiState.showCatalogType,
|
||||
homescreenHideUnreleasedContent = homescreenSettingsUiState.hideUnreleasedContent,
|
||||
homescreenHideCatalogUnderline = homescreenSettingsUiState.hideCatalogUnderline,
|
||||
homescreenItems = homescreenSettingsUiState.items,
|
||||
metaScreenSettingsUiState = metaScreenSettingsUiState,
|
||||
continueWatchingPreferencesUiState = continueWatchingPreferencesUiState,
|
||||
|
|
@ -466,7 +465,6 @@ fun SettingsScreen(
|
|||
homescreenHeroEnabled = homescreenSettingsUiState.heroEnabled,
|
||||
homescreenShowCatalogType = homescreenSettingsUiState.showCatalogType,
|
||||
homescreenHideUnreleasedContent = homescreenSettingsUiState.hideUnreleasedContent,
|
||||
homescreenHideCatalogUnderline = homescreenSettingsUiState.hideCatalogUnderline,
|
||||
homescreenItems = homescreenSettingsUiState.items,
|
||||
metaScreenSettingsUiState = metaScreenSettingsUiState,
|
||||
continueWatchingPreferencesUiState = continueWatchingPreferencesUiState,
|
||||
|
|
@ -537,7 +535,6 @@ private fun MobileSettingsScreen(
|
|||
homescreenHeroEnabled: Boolean,
|
||||
homescreenShowCatalogType: Boolean,
|
||||
homescreenHideUnreleasedContent: Boolean,
|
||||
homescreenHideCatalogUnderline: Boolean,
|
||||
homescreenItems: List<HomeCatalogSettingsItem>,
|
||||
metaScreenSettingsUiState: MetaScreenSettingsUiState,
|
||||
continueWatchingPreferencesUiState: ContinueWatchingPreferencesUiState,
|
||||
|
|
@ -768,7 +765,6 @@ private fun MobileSettingsScreen(
|
|||
heroEnabled = homescreenHeroEnabled,
|
||||
showCatalogType = homescreenShowCatalogType,
|
||||
hideUnreleasedContent = homescreenHideUnreleasedContent,
|
||||
hideCatalogUnderline = homescreenHideCatalogUnderline,
|
||||
items = homescreenItems,
|
||||
)
|
||||
SettingsPage.MetaScreen -> metaScreenSettingsContent(
|
||||
|
|
@ -895,7 +891,6 @@ private fun TabletSettingsScreen(
|
|||
homescreenHeroEnabled: Boolean,
|
||||
homescreenShowCatalogType: Boolean,
|
||||
homescreenHideUnreleasedContent: Boolean,
|
||||
homescreenHideCatalogUnderline: Boolean,
|
||||
homescreenItems: List<HomeCatalogSettingsItem>,
|
||||
metaScreenSettingsUiState: MetaScreenSettingsUiState,
|
||||
continueWatchingPreferencesUiState: ContinueWatchingPreferencesUiState,
|
||||
|
|
@ -1182,7 +1177,6 @@ private fun TabletSettingsScreen(
|
|||
heroEnabled = homescreenHeroEnabled,
|
||||
showCatalogType = homescreenShowCatalogType,
|
||||
hideUnreleasedContent = homescreenHideUnreleasedContent,
|
||||
hideCatalogUnderline = homescreenHideCatalogUnderline,
|
||||
items = homescreenItems,
|
||||
)
|
||||
SettingsPage.MetaScreen -> metaScreenSettingsContent(
|
||||
|
|
|
|||
|
|
@ -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-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-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-catalogs", stringResource(Res.string.settings_homescreen_section_catalogs)),
|
||||
).forEach { row ->
|
||||
|
|
|
|||
|
|
@ -36,13 +36,11 @@ class HomeHeroSectionTest {
|
|||
style = ContinueWatchingSectionStyle.Card,
|
||||
layout = continueWatchingLayout,
|
||||
basePosterWidthDp = 110,
|
||||
showHeaderAccent = true,
|
||||
)
|
||||
val reserveHeight = continueWatchingHeroViewportReserveHeight(
|
||||
style = ContinueWatchingSectionStyle.Card,
|
||||
layout = continueWatchingLayout,
|
||||
basePosterWidthDp = 110,
|
||||
showHeaderAccent = true,
|
||||
)
|
||||
val layout = homeHeroLayout(
|
||||
maxWidthDp = 390f,
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ interface NuvioPlayerBridge {
|
|||
)
|
||||
fun configureAudioOutput(audioOutput: String)
|
||||
fun setPlaybackSpeed(speed: Float)
|
||||
fun getVolume(): Float
|
||||
fun setVolume(volume: Float)
|
||||
fun setMuted(muted: Boolean)
|
||||
fun setResizeMode(mode: Int) // 0=Fit, 1=Fill, 2=Zoom
|
||||
fun syncVideoSurfaceLayout(width: Double, height: Double)
|
||||
|
|
|
|||
|
|
@ -130,6 +130,23 @@ actual fun PlatformPlayerSurface(
|
|||
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) {
|
||||
bridge.setMuted(muted)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,8 @@ final class MPVPlayerBridgeImpl: NSObject, NuvioPlayerBridge {
|
|||
playerVC?.configureAudioOutput(audioOutput: audioOutput)
|
||||
}
|
||||
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 setResizeMode(mode: Int32) { playerVC?.setResize(Int(mode)) }
|
||||
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, "audio-channels", "auto"))
|
||||
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-queue-count", "1"))
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
guard mpv != nil else { return }
|
||||
setFlag("mute", muted)
|
||||
|
|
|
|||
Loading…
Reference in a new issue