mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-16 23:12:12 +00:00
Update App.kt
This commit is contained in:
parent
52ffd0c553
commit
8187f404c4
1 changed files with 477 additions and 460 deletions
|
|
@ -92,6 +92,7 @@ import com.nuvio.app.core.ui.NuvioToastController
|
||||||
import com.nuvio.app.core.ui.NuvioFloatingPrompt
|
import com.nuvio.app.core.ui.NuvioFloatingPrompt
|
||||||
import com.nuvio.app.core.ui.TraktListPickerDialog
|
import com.nuvio.app.core.ui.TraktListPickerDialog
|
||||||
import com.nuvio.app.core.ui.NuvioTheme
|
import com.nuvio.app.core.ui.NuvioTheme
|
||||||
|
import com.nuvio.app.core.ui.localizedContinueWatchingSubtitle
|
||||||
import com.nuvio.app.features.auth.AuthScreen
|
import com.nuvio.app.features.auth.AuthScreen
|
||||||
import com.nuvio.app.features.addons.AddonRepository
|
import com.nuvio.app.features.addons.AddonRepository
|
||||||
import com.nuvio.app.features.catalog.CatalogRepository
|
import com.nuvio.app.features.catalog.CatalogRepository
|
||||||
|
|
@ -168,12 +169,20 @@ import com.nuvio.app.features.watching.application.WatchingState
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import nuvio.composeapp.generated.resources.Res
|
import nuvio.composeapp.generated.resources.*
|
||||||
import nuvio.composeapp.generated.resources.app_logo_wordmark
|
import nuvio.composeapp.generated.resources.app_logo_wordmark
|
||||||
|
import nuvio.composeapp.generated.resources.compose_catalog_subtitle_library
|
||||||
|
import nuvio.composeapp.generated.resources.compose_catalog_subtitle_trakt_library
|
||||||
|
import nuvio.composeapp.generated.resources.compose_nav_home
|
||||||
|
import nuvio.composeapp.generated.resources.compose_nav_library
|
||||||
|
import nuvio.composeapp.generated.resources.compose_nav_profile
|
||||||
|
import nuvio.composeapp.generated.resources.compose_nav_search
|
||||||
import nuvio.composeapp.generated.resources.sidebar_library
|
import nuvio.composeapp.generated.resources.sidebar_library
|
||||||
import nuvio.composeapp.generated.resources.sidebar_search
|
import nuvio.composeapp.generated.resources.sidebar_search
|
||||||
import org.jetbrains.compose.resources.DrawableResource
|
import org.jetbrains.compose.resources.DrawableResource
|
||||||
|
import org.jetbrains.compose.resources.getString
|
||||||
import org.jetbrains.compose.resources.painterResource
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
object TabsRoute
|
object TabsRoute
|
||||||
|
|
@ -279,7 +288,6 @@ fun App() {
|
||||||
ThemeSettingsRepository.selectedTheme
|
ThemeSettingsRepository.selectedTheme
|
||||||
}.collectAsStateWithLifecycle()
|
}.collectAsStateWithLifecycle()
|
||||||
val amoledEnabled by remember { ThemeSettingsRepository.amoledEnabled }.collectAsStateWithLifecycle()
|
val amoledEnabled by remember { ThemeSettingsRepository.amoledEnabled }.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
NuvioTheme(appTheme = selectedTheme, amoled = amoledEnabled) {
|
NuvioTheme(appTheme = selectedTheme, amoled = amoledEnabled) {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
AuthRepository.initialize()
|
AuthRepository.initialize()
|
||||||
|
|
@ -500,6 +508,7 @@ private fun MainAppContent(
|
||||||
val networkStatusUiState by remember {
|
val networkStatusUiState by remember {
|
||||||
NetworkStatusRepository.uiState
|
NetworkStatusRepository.uiState
|
||||||
}.collectAsStateWithLifecycle()
|
}.collectAsStateWithLifecycle()
|
||||||
|
val downloadedProviderLabel = stringResource(Res.string.provider_downloaded)
|
||||||
val isTraktConnected = traktAuthUiState.mode == TraktConnectionMode.CONNECTED
|
val isTraktConnected = traktAuthUiState.mode == TraktConnectionMode.CONNECTED
|
||||||
var initialHomeReady by rememberSaveable { mutableStateOf(false) }
|
var initialHomeReady by rememberSaveable { mutableStateOf(false) }
|
||||||
var offlineLaunchRouteHandled by rememberSaveable { mutableStateOf(false) }
|
var offlineLaunchRouteHandled by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
@ -543,11 +552,11 @@ private fun MainAppContent(
|
||||||
|
|
||||||
when (condition) {
|
when (condition) {
|
||||||
NetworkCondition.NoInternet -> {
|
NetworkCondition.NoInternet -> {
|
||||||
NuvioToastController.show("No internet connection")
|
NuvioToastController.show(getString(Res.string.network_no_internet_connection))
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkCondition.ServersUnreachable -> {
|
NetworkCondition.ServersUnreachable -> {
|
||||||
NuvioToastController.show("Cannot reach servers")
|
NuvioToastController.show(getString(Res.string.network_cannot_reach_servers))
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkCondition.Online -> {
|
NetworkCondition.Online -> {
|
||||||
|
|
@ -555,7 +564,7 @@ private fun MainAppContent(
|
||||||
previousConditionName == NetworkCondition.NoInternet.name ||
|
previousConditionName == NetworkCondition.NoInternet.name ||
|
||||||
previousConditionName == NetworkCondition.ServersUnreachable.name
|
previousConditionName == NetworkCondition.ServersUnreachable.name
|
||||||
) {
|
) {
|
||||||
NuvioToastController.show("Back online")
|
NuvioToastController.show(getString(Res.string.network_back_online))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -699,7 +708,7 @@ private fun MainAppContent(
|
||||||
streamTitle = downloadedItem.streamTitle.ifBlank { title },
|
streamTitle = downloadedItem.streamTitle.ifBlank { title },
|
||||||
streamSubtitle = downloadedItem.streamSubtitle,
|
streamSubtitle = downloadedItem.streamSubtitle,
|
||||||
pauseDescription = pauseDescription,
|
pauseDescription = pauseDescription,
|
||||||
providerName = downloadedItem.providerName.ifBlank { "Downloaded" },
|
providerName = downloadedItem.providerName.ifBlank { downloadedProviderLabel },
|
||||||
providerAddonId = downloadedItem.providerAddonId,
|
providerAddonId = downloadedItem.providerAddonId,
|
||||||
contentType = type,
|
contentType = type,
|
||||||
videoId = videoId,
|
videoId = videoId,
|
||||||
|
|
@ -799,15 +808,17 @@ private fun MainAppContent(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val librarySectionSubtitle = if (libraryUiState.sourceMode == LibrarySourceMode.TRAKT) {
|
||||||
|
stringResource(Res.string.compose_catalog_subtitle_trakt_library)
|
||||||
|
} else {
|
||||||
|
stringResource(Res.string.compose_catalog_subtitle_library)
|
||||||
|
}
|
||||||
|
|
||||||
val onLibrarySectionViewAllClick: (LibrarySection) -> Unit = { section ->
|
val onLibrarySectionViewAllClick: (LibrarySection) -> Unit = { section ->
|
||||||
navController.navigate(
|
navController.navigate(
|
||||||
CatalogRoute(
|
CatalogRoute(
|
||||||
title = section.displayTitle,
|
title = section.displayTitle,
|
||||||
subtitle = if (libraryUiState.sourceMode == LibrarySourceMode.TRAKT) {
|
subtitle = librarySectionSubtitle,
|
||||||
"Trakt Library"
|
|
||||||
} else {
|
|
||||||
"Library"
|
|
||||||
},
|
|
||||||
manifestUrl = INTERNAL_LIBRARY_MANIFEST_URL,
|
manifestUrl = INTERNAL_LIBRARY_MANIFEST_URL,
|
||||||
type = section.items.firstOrNull()?.type ?: "movie",
|
type = section.items.firstOrNull()?.type ?: "movie",
|
||||||
catalogId = section.type,
|
catalogId = section.type,
|
||||||
|
|
@ -900,19 +911,19 @@ private fun MainAppContent(
|
||||||
selected = selectedTab == AppScreenTab.Home,
|
selected = selectedTab == AppScreenTab.Home,
|
||||||
onClick = { selectedTab = AppScreenTab.Home },
|
onClick = { selectedTab = AppScreenTab.Home },
|
||||||
icon = Icons.Filled.Home,
|
icon = Icons.Filled.Home,
|
||||||
contentDescription = "Home",
|
contentDescription = stringResource(Res.string.compose_nav_home),
|
||||||
)
|
)
|
||||||
NavItem(
|
NavItem(
|
||||||
selected = selectedTab == AppScreenTab.Search,
|
selected = selectedTab == AppScreenTab.Search,
|
||||||
onClick = { selectedTab = AppScreenTab.Search },
|
onClick = { selectedTab = AppScreenTab.Search },
|
||||||
icon = Res.drawable.sidebar_search,
|
icon = Res.drawable.sidebar_search,
|
||||||
contentDescription = "Search",
|
contentDescription = stringResource(Res.string.compose_nav_search),
|
||||||
)
|
)
|
||||||
NavItem(
|
NavItem(
|
||||||
selected = selectedTab == AppScreenTab.Library,
|
selected = selectedTab == AppScreenTab.Library,
|
||||||
onClick = { selectedTab = AppScreenTab.Library },
|
onClick = { selectedTab = AppScreenTab.Library },
|
||||||
icon = Res.drawable.sidebar_library,
|
icon = Res.drawable.sidebar_library,
|
||||||
contentDescription = "Library",
|
contentDescription = stringResource(Res.string.compose_nav_library),
|
||||||
)
|
)
|
||||||
NavItem(
|
NavItem(
|
||||||
selected = selectedTab == AppScreenTab.Settings,
|
selected = selectedTab == AppScreenTab.Settings,
|
||||||
|
|
@ -948,7 +959,7 @@ private fun MainAppContent(
|
||||||
},
|
},
|
||||||
onLibraryPosterLongClick = { item ->
|
onLibraryPosterLongClick = { item ->
|
||||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
selectedPosterForActions = item.toMetaPreview() // reuse existing sheet
|
selectedPosterForActions = item.toMetaPreview()
|
||||||
},
|
},
|
||||||
onLibrarySectionViewAllClick = onLibrarySectionViewAllClick,
|
onLibrarySectionViewAllClick = onLibrarySectionViewAllClick,
|
||||||
onContinueWatchingClick = onContinueWatchingClick,
|
onContinueWatchingClick = onContinueWatchingClick,
|
||||||
|
|
@ -999,6 +1010,9 @@ private fun MainAppContent(
|
||||||
}
|
}
|
||||||
composable<DetailRoute> { backStackEntry ->
|
composable<DetailRoute> { backStackEntry ->
|
||||||
val route = backStackEntry.toRoute<DetailRoute>()
|
val route = backStackEntry.toRoute<DetailRoute>()
|
||||||
|
val directorRole = stringResource(Res.string.person_role_director)
|
||||||
|
val writerRole = stringResource(Res.string.person_role_writer)
|
||||||
|
val creatorRole = stringResource(Res.string.person_role_creator)
|
||||||
MetaDetailsScreen(
|
MetaDetailsScreen(
|
||||||
type = route.type,
|
type = route.type,
|
||||||
id = route.id,
|
id = route.id,
|
||||||
|
|
@ -1039,8 +1053,11 @@ private fun MainAppContent(
|
||||||
castAvatarTransitionKey = avatarTransitionKey,
|
castAvatarTransitionKey = avatarTransitionKey,
|
||||||
preferCrew = person.role?.let {
|
preferCrew = person.role?.let {
|
||||||
it.equals("Director", ignoreCase = true) ||
|
it.equals("Director", ignoreCase = true) ||
|
||||||
|
it.equals(directorRole, ignoreCase = true) ||
|
||||||
it.equals("Writer", ignoreCase = true) ||
|
it.equals("Writer", ignoreCase = true) ||
|
||||||
|
it.equals(writerRole, ignoreCase = true) ||
|
||||||
it.equals("Creator", ignoreCase = true)
|
it.equals("Creator", ignoreCase = true)
|
||||||
|
|| it.equals(creatorRole, ignoreCase = true)
|
||||||
} ?: false,
|
} ?: false,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -1667,7 +1684,7 @@ private fun MainAppContent(
|
||||||
tab.key to (snapshot[tab.key] == true)
|
tab.key to (snapshot[tab.key] == true)
|
||||||
}
|
}
|
||||||
}.onFailure { error ->
|
}.onFailure { error ->
|
||||||
pickerError = error.message ?: "Failed to load Trakt lists"
|
pickerError = error.message ?: getString(Res.string.trakt_lists_load_failed)
|
||||||
}
|
}
|
||||||
pickerPending = false
|
pickerPending = false
|
||||||
}
|
}
|
||||||
|
|
@ -1753,7 +1770,7 @@ private fun MainAppContent(
|
||||||
pickerItem = null
|
pickerItem = null
|
||||||
pickerError = null
|
pickerError = null
|
||||||
}.onFailure { error ->
|
}.onFailure { error ->
|
||||||
pickerError = error.message ?: "Failed to update Trakt lists"
|
pickerError = error.message ?: getString(Res.string.trakt_lists_update_failed)
|
||||||
}
|
}
|
||||||
pickerPending = false
|
pickerPending = false
|
||||||
}
|
}
|
||||||
|
|
@ -1761,11 +1778,11 @@ private fun MainAppContent(
|
||||||
)
|
)
|
||||||
|
|
||||||
NuvioStatusModal(
|
NuvioStatusModal(
|
||||||
title = "Exit app",
|
title = stringResource(Res.string.app_exit_title),
|
||||||
message = "Do you want to exit the app?",
|
message = stringResource(Res.string.app_exit_message),
|
||||||
isVisible = showExitConfirmation,
|
isVisible = showExitConfirmation,
|
||||||
confirmText = "Yes",
|
confirmText = stringResource(Res.string.action_yes),
|
||||||
dismissText = "No",
|
dismissText = stringResource(Res.string.action_no),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
showExitConfirmation = false
|
showExitConfirmation = false
|
||||||
platformExitApp()
|
platformExitApp()
|
||||||
|
|
@ -1796,9 +1813,9 @@ private fun MainAppContent(
|
||||||
visible = resumePromptItem != null,
|
visible = resumePromptItem != null,
|
||||||
imageUrl = resumePromptItem?.poster ?: resumePromptItem?.imageUrl,
|
imageUrl = resumePromptItem?.poster ?: resumePromptItem?.imageUrl,
|
||||||
title = resumePromptItem?.title.orEmpty(),
|
title = resumePromptItem?.title.orEmpty(),
|
||||||
subtitle = resumePromptItem?.subtitle.orEmpty(),
|
subtitle = resumePromptItem?.let { localizedContinueWatchingSubtitle(it) }.orEmpty(),
|
||||||
progressFraction = resumePromptItem?.progressFraction ?: 0f,
|
progressFraction = resumePromptItem?.progressFraction ?: 0f,
|
||||||
actionLabel = "Resume",
|
actionLabel = stringResource(Res.string.resume_prompt_action),
|
||||||
onAction = {
|
onAction = {
|
||||||
val item = resumePromptItem ?: return@NuvioFloatingPrompt
|
val item = resumePromptItem ?: return@NuvioFloatingPrompt
|
||||||
resumePromptItem = null
|
resumePromptItem = null
|
||||||
|
|
@ -1853,6 +1870,7 @@ private fun AppTabHost(
|
||||||
onPosterClick: ((MetaPreview) -> Unit)? = null,
|
onPosterClick: ((MetaPreview) -> Unit)? = null,
|
||||||
onPosterLongClick: ((MetaPreview) -> Unit)? = null,
|
onPosterLongClick: ((MetaPreview) -> Unit)? = null,
|
||||||
onLibraryPosterClick: ((LibraryItem) -> Unit)? = null,
|
onLibraryPosterClick: ((LibraryItem) -> Unit)? = null,
|
||||||
|
onLibraryPosterLongClick: ((LibraryItem) -> Unit)? = null,
|
||||||
onLibrarySectionViewAllClick: ((LibrarySection) -> Unit)? = null,
|
onLibrarySectionViewAllClick: ((LibrarySection) -> Unit)? = null,
|
||||||
onContinueWatchingClick: ((ContinueWatchingItem) -> Unit)? = null,
|
onContinueWatchingClick: ((ContinueWatchingItem) -> Unit)? = null,
|
||||||
onContinueWatchingLongPress: ((ContinueWatchingItem) -> Unit)? = null,
|
onContinueWatchingLongPress: ((ContinueWatchingItem) -> Unit)? = null,
|
||||||
|
|
@ -1869,7 +1887,6 @@ private fun AppTabHost(
|
||||||
onCollectionsSettingsClick: () -> Unit = {},
|
onCollectionsSettingsClick: () -> Unit = {},
|
||||||
onFolderClick: ((collectionId: String, folderId: String) -> Unit)? = null,
|
onFolderClick: ((collectionId: String, folderId: String) -> Unit)? = null,
|
||||||
onInitialHomeContentRendered: () -> Unit = {},
|
onInitialHomeContentRendered: () -> Unit = {},
|
||||||
onLibraryPosterLongClick: ((LibraryItem) -> Unit)? = null,
|
|
||||||
) {
|
) {
|
||||||
val tabStateHolder = rememberSaveableStateHolder()
|
val tabStateHolder = rememberSaveableStateHolder()
|
||||||
|
|
||||||
|
|
@ -1955,13 +1972,13 @@ private fun TabletFloatingTopBar(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
TabletTopPillItem(
|
TabletTopPillItem(
|
||||||
label = "Home",
|
label = stringResource(Res.string.compose_nav_home),
|
||||||
selected = selectedTab == AppScreenTab.Home,
|
selected = selectedTab == AppScreenTab.Home,
|
||||||
onClick = { onTabSelected(AppScreenTab.Home) },
|
onClick = { onTabSelected(AppScreenTab.Home) },
|
||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.Home,
|
imageVector = Icons.Filled.Home,
|
||||||
contentDescription = "Home",
|
contentDescription = stringResource(Res.string.compose_nav_home),
|
||||||
modifier = Modifier.size(18.dp),
|
modifier = Modifier.size(18.dp),
|
||||||
tint = if (selectedTab == AppScreenTab.Home) {
|
tint = if (selectedTab == AppScreenTab.Home) {
|
||||||
MaterialTheme.colorScheme.onPrimaryContainer
|
MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
|
@ -1972,13 +1989,13 @@ private fun TabletFloatingTopBar(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
TabletTopPillItem(
|
TabletTopPillItem(
|
||||||
label = "Search",
|
label = stringResource(Res.string.compose_nav_search),
|
||||||
selected = selectedTab == AppScreenTab.Search,
|
selected = selectedTab == AppScreenTab.Search,
|
||||||
onClick = { onTabSelected(AppScreenTab.Search) },
|
onClick = { onTabSelected(AppScreenTab.Search) },
|
||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(Res.drawable.sidebar_search),
|
painter = painterResource(Res.drawable.sidebar_search),
|
||||||
contentDescription = "Search",
|
contentDescription = stringResource(Res.string.compose_nav_search),
|
||||||
modifier = Modifier.size(18.dp),
|
modifier = Modifier.size(18.dp),
|
||||||
tint = if (selectedTab == AppScreenTab.Search) {
|
tint = if (selectedTab == AppScreenTab.Search) {
|
||||||
MaterialTheme.colorScheme.onPrimaryContainer
|
MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
|
@ -1989,13 +2006,13 @@ private fun TabletFloatingTopBar(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
TabletTopPillItem(
|
TabletTopPillItem(
|
||||||
label = "Library",
|
label = stringResource(Res.string.compose_nav_library),
|
||||||
selected = selectedTab == AppScreenTab.Library,
|
selected = selectedTab == AppScreenTab.Library,
|
||||||
onClick = { onTabSelected(AppScreenTab.Library) },
|
onClick = { onTabSelected(AppScreenTab.Library) },
|
||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(Res.drawable.sidebar_library),
|
painter = painterResource(Res.drawable.sidebar_library),
|
||||||
contentDescription = "Library",
|
contentDescription = stringResource(Res.string.compose_nav_library),
|
||||||
modifier = Modifier.size(18.dp),
|
modifier = Modifier.size(18.dp),
|
||||||
tint = if (selectedTab == AppScreenTab.Library) {
|
tint = if (selectedTab == AppScreenTab.Library) {
|
||||||
MaterialTheme.colorScheme.onPrimaryContainer
|
MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
|
|
@ -2025,7 +2042,7 @@ private fun TabletFloatingTopBar(
|
||||||
onAddProfileRequested = onAddProfileRequested,
|
onAddProfileRequested = onAddProfileRequested,
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Profile",
|
text = stringResource(Res.string.compose_nav_profile),
|
||||||
modifier = Modifier.clickable { onTabSelected(AppScreenTab.Settings) },
|
modifier = Modifier.clickable { onTabSelected(AppScreenTab.Settings) },
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
color = if (selectedTab == AppScreenTab.Settings) {
|
color = if (selectedTab == AppScreenTab.Settings) {
|
||||||
|
|
@ -2088,7 +2105,7 @@ private fun AppLaunchOverlay(
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(Res.drawable.app_logo_wordmark),
|
painter = painterResource(Res.drawable.app_logo_wordmark),
|
||||||
contentDescription = "Nuvio",
|
contentDescription = stringResource(Res.string.app_brand_name),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(0.48f)
|
.fillMaxWidth(0.48f)
|
||||||
.height(44.dp),
|
.height(44.dp),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue