Merge branch 'cmp-rewrite' of https://github.com/NuvioMedia/NuvioMobile into cmp-rewrite

This commit is contained in:
tapframe 2026-05-13 14:12:34 +05:30
commit 37203d1fc1
6 changed files with 1333 additions and 22 deletions

View file

@ -1,13 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android"> <locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="cs"/>
<locale android:name="en"/> <locale android:name="en"/>
<locale android:name="fr"/> <locale android:name="fr"/>
<locale android:name="es"/>
<locale android:name="pt"/>
<locale android:name="tr"/>
<locale android:name="it"/>
<locale android:name="el"/>
<locale android:name="pl"/>
<locale android:name="de"/> <locale android:name="de"/>
<locale android:name="cs"/> <locale android:name="el"/>
<locale android:name="id"/>
<locale android:name="it"/>
<locale android:name="pl"/>
<locale android:name="pt"/>
<locale android:name="es"/>
<locale android:name="tr"/>
</locale-config> </locale-config>

View file

@ -720,7 +720,7 @@
<string name="settings_playback_threshold_mode_percentage">Pourcentage</string> <string name="settings_playback_threshold_mode_percentage">Pourcentage</string>
<string name="settings_playback_threshold_percentage">Pourcentage de seuil</string> <string name="settings_playback_threshold_percentage">Pourcentage de seuil</string>
<string name="settings_playback_threshold_percentage_description">Afficher la carte de l'épisode suivant lorsque la lecture atteint ce pourcentage.</string> <string name="settings_playback_threshold_percentage_description">Afficher la carte de l'épisode suivant lorsque la lecture atteint ce pourcentage.</string>
<string name="settings_playback_threshold_percentage_value">%1$s%</string> <string name="settings_playback_threshold_percentage_value">%1$s %</string>
<string name="settings_playback_timeout_instant">Instantané</string> <string name="settings_playback_timeout_instant">Instantané</string>
<string name="settings_playback_timeout_seconds">%1$ss</string> <string name="settings_playback_timeout_seconds">%1$ss</string>
<string name="settings_playback_timeout_unlimited">Illimité</string> <string name="settings_playback_timeout_unlimited">Illimité</string>
@ -1017,7 +1017,7 @@
<string name="streams_no_direct_link">Aucun lien direct du stream disponible</string> <string name="streams_no_direct_link">Aucun lien direct du stream disponible</string>
<string name="streams_no_metadata">Aucune métadonnée disponible</string> <string name="streams_no_metadata">Aucune métadonnée disponible</string>
<string name="streams_refresh">Actualiser les streams</string> <string name="streams_refresh">Actualiser les streams</string>
<string name="streams_resume_from_percent">Reprendre depuis %1$d%</string> <string name="streams_resume_from_percent">Reprendre depuis %1$d %</string>
<string name="streams_resume_from_time">Reprendre depuis %1$s</string> <string name="streams_resume_from_time">Reprendre depuis %1$s</string>
<string name="streams_size">TAILLE %1$s</string> <string name="streams_size">TAILLE %1$s</string>
<string name="trailer_close">Fermer la bande-annonce</string> <string name="trailer_close">Fermer la bande-annonce</string>
@ -1027,7 +1027,7 @@
<string name="updates_asset_line">%1$s • %2$s</string> <string name="updates_asset_line">%1$s • %2$s</string>
<string name="updates_check_failed">Échec de la vérification des mises à jour</string> <string name="updates_check_failed">Échec de la vérification des mises à jour</string>
<string name="updates_download_failed">Échec du téléchargement</string> <string name="updates_download_failed">Échec du téléchargement</string>
<string name="updates_downloading_progress">Téléchargement %1$d%%</string> <string name="updates_downloading_progress">Téléchargement %1$d %</string>
<string name="updates_install_failed">Impossible de démarrer l'installation</string> <string name="updates_install_failed">Impossible de démarrer l'installation</string>
<string name="updates_latest_version">Vous utilisez la version la plus récente.</string> <string name="updates_latest_version">Vous utilisez la version la plus récente.</string>
<string name="updates_message_allow_installs">Activez l'installation d'applications pour Nuvio puis revenez pour continuer.</string> <string name="updates_message_allow_installs">Activez l'installation d'applications pour Nuvio puis revenez pour continuer.</string>

File diff suppressed because it is too large Load diff

View file

@ -531,6 +531,7 @@ private fun MainAppContent(
val hapticFeedback = LocalHapticFeedback.current val hapticFeedback = LocalHapticFeedback.current
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
var selectedTab by rememberSaveable { mutableStateOf(AppScreenTab.Home) } var selectedTab by rememberSaveable { mutableStateOf(AppScreenTab.Home) }
var searchFocusRequestCount by remember { mutableStateOf(0) }
val currentBackStackEntry by navController.currentBackStackEntryAsState() val currentBackStackEntry by navController.currentBackStackEntryAsState()
val nativeRequestedTab by remember { NativeTabBridge.requestedTab }.collectAsStateWithLifecycle() val nativeRequestedTab by remember { NativeTabBridge.requestedTab }.collectAsStateWithLifecycle()
val liquidGlassNativeTabBarEnabled by remember { val liquidGlassNativeTabBarEnabled by remember {
@ -597,6 +598,9 @@ private fun MainAppContent(
LaunchedEffect(selectedTab) { LaunchedEffect(selectedTab) {
NativeTabBridge.publishSelectedTab(selectedTab.toNativeNavigationTab()) NativeTabBridge.publishSelectedTab(selectedTab.toNativeNavigationTab())
if (selectedTab != AppScreenTab.Search) {
searchFocusRequestCount = 0
}
} }
DisposableEffect( DisposableEffect(
@ -1049,7 +1053,13 @@ private fun MainAppContent(
) )
NavItem( NavItem(
selected = selectedTab == AppScreenTab.Search, selected = selectedTab == AppScreenTab.Search,
onClick = { selectedTab = AppScreenTab.Search }, onClick = {
if (selectedTab == AppScreenTab.Search) {
searchFocusRequestCount++
} else {
selectedTab = AppScreenTab.Search
}
},
icon = Res.drawable.sidebar_search, icon = Res.drawable.sidebar_search,
contentDescription = stringResource(Res.string.compose_nav_search), contentDescription = stringResource(Res.string.compose_nav_search),
) )
@ -1083,6 +1093,7 @@ private fun MainAppContent(
.fillMaxSize() .fillMaxSize()
.padding(innerPadding), .padding(innerPadding),
selectedTab = selectedTab, selectedTab = selectedTab,
searchFocusRequestCount = searchFocusRequestCount,
animateHomeCollectionGifs = tabsRouteActive, animateHomeCollectionGifs = tabsRouteActive,
onCatalogClick = onCatalogClick, onCatalogClick = onCatalogClick,
onPosterClick = { meta -> onPosterClick = { meta ->
@ -1137,7 +1148,13 @@ private fun MainAppContent(
if (isTabletLayout && !useNativeBottomTabs) { if (isTabletLayout && !useNativeBottomTabs) {
TabletFloatingTopBar( TabletFloatingTopBar(
selectedTab = selectedTab, selectedTab = selectedTab,
onTabSelected = { selectedTab = it }, onTabSelected = { tab ->
if (tab == AppScreenTab.Search && selectedTab == AppScreenTab.Search) {
searchFocusRequestCount++
} else {
selectedTab = tab
}
},
onProfileSelected = onProfileSelected, onProfileSelected = onProfileSelected,
onAddProfileRequested = onSwitchProfile, onAddProfileRequested = onSwitchProfile,
) )
@ -2085,6 +2102,7 @@ private fun rememberGuardedPopBackStack(
private fun AppTabHost( private fun AppTabHost(
selectedTab: AppScreenTab, selectedTab: AppScreenTab,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
searchFocusRequestCount: Int = 0,
animateHomeCollectionGifs: Boolean = true, animateHomeCollectionGifs: Boolean = true,
onCatalogClick: ((HomeCatalogSection) -> Unit)? = null, onCatalogClick: ((HomeCatalogSection) -> Unit)? = null,
onPosterClick: ((MetaPreview) -> Unit)? = null, onPosterClick: ((MetaPreview) -> Unit)? = null,
@ -2132,6 +2150,7 @@ private fun AppTabHost(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
onPosterClick = onPosterClick, onPosterClick = onPosterClick,
onPosterLongClick = onPosterLongClick, onPosterLongClick = onPosterLongClick,
searchFocusRequestCount = searchFocusRequestCount,
) )
} }

View file

@ -33,6 +33,8 @@ import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@ -80,7 +82,16 @@ fun SearchScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onPosterClick: ((MetaPreview) -> Unit)? = null, onPosterClick: ((MetaPreview) -> Unit)? = null,
onPosterLongClick: ((MetaPreview) -> Unit)? = null, onPosterLongClick: ((MetaPreview) -> Unit)? = null,
searchFocusRequestCount: Int = 0,
) { ) {
val focusRequester = remember { FocusRequester() }
LaunchedEffect(searchFocusRequestCount) {
if (searchFocusRequestCount > 0) {
focusRequester.requestFocus()
}
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
AddonRepository.initialize() AddonRepository.initialize()
WatchedRepository.ensureLoaded() WatchedRepository.ensureLoaded()
@ -240,6 +251,7 @@ fun SearchScreen(
value = query, value = query,
onValueChange = { query = it }, onValueChange = { query = it },
placeholder = stringResource(Res.string.compose_search_placeholder), placeholder = stringResource(Res.string.compose_search_placeholder),
modifier = Modifier.focusRequester(focusRequester),
trailingContent = if (query.isNotBlank()) { trailingContent = if (query.isNotBlank()) {
{ {
IconButton(onClick = { query = "" }) { IconButton(onClick = { query = "" }) {

View file

@ -1,32 +1,34 @@
package com.nuvio.app.features.settings package com.nuvio.app.features.settings
import nuvio.composeapp.generated.resources.Res import nuvio.composeapp.generated.resources.Res
import nuvio.composeapp.generated.resources.lang_czech
import nuvio.composeapp.generated.resources.lang_english import nuvio.composeapp.generated.resources.lang_english
import nuvio.composeapp.generated.resources.lang_french import nuvio.composeapp.generated.resources.lang_french
import nuvio.composeapp.generated.resources.lang_german import nuvio.composeapp.generated.resources.lang_german
import nuvio.composeapp.generated.resources.lang_spanish
import nuvio.composeapp.generated.resources.lang_portuguese_portugal
import nuvio.composeapp.generated.resources.lang_turkish
import nuvio.composeapp.generated.resources.lang_italian
import nuvio.composeapp.generated.resources.lang_greek import nuvio.composeapp.generated.resources.lang_greek
import nuvio.composeapp.generated.resources.lang_indonesian
import nuvio.composeapp.generated.resources.lang_italian
import nuvio.composeapp.generated.resources.lang_polish import nuvio.composeapp.generated.resources.lang_polish
import nuvio.composeapp.generated.resources.lang_czech import nuvio.composeapp.generated.resources.lang_portuguese_portugal
import nuvio.composeapp.generated.resources.lang_spanish
import nuvio.composeapp.generated.resources.lang_turkish
import org.jetbrains.compose.resources.StringResource import org.jetbrains.compose.resources.StringResource
enum class AppLanguage( enum class AppLanguage(
val code: String, val code: String,
val labelRes: StringResource, val labelRes: StringResource,
) { ) {
CZECH("cs", Res.string.lang_czech),
ENGLISH("en", Res.string.lang_english), ENGLISH("en", Res.string.lang_english),
FRENCH("fr", Res.string.lang_french), FRENCH("fr", Res.string.lang_french),
GERMAN("de", Res.string.lang_german), GERMAN("de", Res.string.lang_german),
SPANISH("es", Res.string.lang_spanish),
PORTUGUESE("pt", Res.string.lang_portuguese_portugal),
TURKISH("tr", Res.string.lang_turkish),
ITALIAN("it", Res.string.lang_italian),
GREEK("el", Res.string.lang_greek), GREEK("el", Res.string.lang_greek),
INDONESIAN("id", Res.string.lang_indonesian),
ITALIAN("it", Res.string.lang_italian),
POLISH("pl", Res.string.lang_polish), POLISH("pl", Res.string.lang_polish),
CZECH("cs", Res.string.lang_czech), PORTUGUESE("pt", Res.string.lang_portuguese_portugal),
SPANISH("es", Res.string.lang_spanish),
TURKISH("tr", Res.string.lang_turkish),
; ;
companion object { companion object {