fix: multiple ios crashes

This commit is contained in:
tapframe 2026-06-21 16:45:56 +05:30
parent f2ef1e1383
commit 3e49c78de7
2 changed files with 80 additions and 80 deletions

View file

@ -47,12 +47,14 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@ -111,7 +113,6 @@ import com.nuvio.app.features.addons.AddonRepository
import com.nuvio.app.features.catalog.CatalogRepository
import com.nuvio.app.features.catalog.CatalogScreen
import com.nuvio.app.features.catalog.CatalogTarget
import com.nuvio.app.features.catalog.CatalogTargetKind
import com.nuvio.app.features.cloud.CloudLibraryContentType
import com.nuvio.app.features.cloud.CloudLibraryFile
import com.nuvio.app.features.cloud.CloudLibraryItem
@ -303,65 +304,30 @@ data class StreamRoute(
@Serializable
data class CatalogRoute(
val launchId: Long,
)
private data class CatalogLaunch(
val title: String,
val subtitle: String,
val targetKind: String,
val contentType: String,
val supportsPagination: Boolean = false,
val manifestUrl: String? = null,
val addonCatalogId: String? = null,
val genre: String? = null,
val librarySectionType: String? = null,
val collectionId: String? = null,
val folderId: String? = null,
val sourceKey: String? = null,
) {
constructor(
title: String,
subtitle: String,
target: CatalogTarget,
) : this(
title = title,
subtitle = subtitle,
targetKind = when (target) {
is CatalogTarget.Addon -> CatalogTargetKind.ADDON
is CatalogTarget.Library -> CatalogTargetKind.LIBRARY
is CatalogTarget.CollectionSource -> CatalogTargetKind.COLLECTION_SOURCE
}.name,
contentType = target.contentType,
supportsPagination = target.supportsPagination,
manifestUrl = (target as? CatalogTarget.Addon)?.manifestUrl,
addonCatalogId = (target as? CatalogTarget.Addon)?.catalogId,
genre = (target as? CatalogTarget.Addon)?.genre,
librarySectionType = (target as? CatalogTarget.Library)?.sectionType,
collectionId = (target as? CatalogTarget.CollectionSource)?.collectionId,
folderId = (target as? CatalogTarget.CollectionSource)?.folderId,
sourceKey = (target as? CatalogTarget.CollectionSource)?.sourceKey,
)
val target: CatalogTarget,
)
fun toCatalogTarget(): CatalogTarget =
when (CatalogTargetKind.valueOf(targetKind)) {
CatalogTargetKind.ADDON -> CatalogTarget.Addon(
manifestUrl = requireNotNull(manifestUrl),
contentType = contentType,
catalogId = requireNotNull(addonCatalogId),
genre = genre,
supportsPagination = supportsPagination,
)
private object CatalogLaunchStore {
private var nextLaunchId = 1L
private val launches = mutableMapOf<Long, CatalogLaunch>()
CatalogTargetKind.LIBRARY -> CatalogTarget.Library(
contentType = contentType,
sectionType = requireNotNull(librarySectionType),
)
fun put(launch: CatalogLaunch): Long {
val launchId = nextLaunchId++
launches[launchId] = launch
return launchId
}
CatalogTargetKind.COLLECTION_SOURCE -> CatalogTarget.CollectionSource(
collectionId = requireNotNull(collectionId),
folderId = requireNotNull(folderId),
sourceKey = requireNotNull(sourceKey),
contentType = contentType,
supportsPagination = supportsPagination,
)
}
fun get(launchId: Long): CatalogLaunch? = launches[launchId]
fun remove(launchId: Long) {
launches.remove(launchId)
}
}
private data class PosterActionTarget(
@ -726,6 +692,7 @@ private fun MainAppContent(
ProfileSettingsSync.startObserving()
}
val hapticFeedback = LocalHapticFeedback.current
val focusManager = LocalFocusManager.current
val coroutineScope = rememberCoroutineScope()
var selectedTab by rememberSaveable { mutableStateOf(AppScreenTab.Home) }
var searchFocusRequestCount by remember { mutableStateOf(0) }
@ -758,6 +725,14 @@ private fun MainAppContent(
LibraryRepository.uiState
}.collectAsStateWithLifecycle()
val authState by AuthRepository.state.collectAsStateWithLifecycle()
val openPosterActions: (PosterActionTarget) -> Unit = { target ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
focusManager.clearFocus(force = true)
coroutineScope.launch {
withFrameNanos { }
selectedPosterActionTarget = target
}
}
val profileState by ProfileRepository.state.collectAsStateWithLifecycle()
val playerSettingsUiState by remember {
PlayerSettingsRepository.ensureLoaded()
@ -1297,13 +1272,18 @@ private fun MainAppContent(
}
val onCatalogClick: (HomeCatalogSection) -> Unit = { section ->
navController.navigate(
CatalogRoute(
val launchId = CatalogLaunchStore.put(
CatalogLaunch(
title = section.title,
subtitle = section.subtitle,
target = section.target,
),
)
navController.navigate(
CatalogRoute(
launchId = launchId,
),
)
}
val librarySectionSubtitle = if (libraryUiState.sourceMode == LibrarySourceMode.TRAKT) {
@ -1313,8 +1293,8 @@ private fun MainAppContent(
}
val onLibrarySectionViewAllClick: (LibrarySection) -> Unit = { section ->
navController.navigate(
CatalogRoute(
val launchId = CatalogLaunchStore.put(
CatalogLaunch(
title = section.displayTitle,
subtitle = librarySectionSubtitle,
target = CatalogTarget.Library(
@ -1323,6 +1303,11 @@ private fun MainAppContent(
),
),
)
navController.navigate(
CatalogRoute(
launchId = launchId,
),
)
}
val openContinueWatching: (ContinueWatchingItem, Boolean, Boolean) -> Unit = { item, manualSelection, startFromBeginning ->
@ -1505,18 +1490,18 @@ private fun MainAppContent(
navController.navigate(DetailRoute(type = meta.type, id = meta.id))
},
onPosterLongClick = { meta ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
selectedPosterActionTarget = PosterActionTarget(preview = meta)
openPosterActions(PosterActionTarget(preview = meta))
},
onLibraryPosterClick = { item ->
navController.navigate(DetailRoute(type = item.type, id = item.id))
},
onLibraryPosterLongClick = { item, section ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
selectedPosterActionTarget = PosterActionTarget(
preview = item.toMetaPreview(),
libraryItem = item,
libraryListKey = section.type,
openPosterActions(
PosterActionTarget(
preview = item.toMetaPreview(),
libraryItem = item,
libraryListKey = section.type,
),
)
},
onLibrarySectionViewAllClick = onLibrarySectionViewAllClick,
@ -2487,29 +2472,38 @@ private fun MainAppContent(
}
composable<CatalogRoute> { backStackEntry ->
val route = backStackEntry.toRoute<CatalogRoute>()
val target = route.toCatalogTarget()
val launch = remember(route.launchId) { CatalogLaunchStore.get(route.launchId) }
if (launch == null) {
LaunchedEffect(route.launchId) {
navController.popBackStack()
}
return@composable
}
val target = launch.target
CatalogScreen(
title = route.title,
subtitle = route.subtitle,
title = launch.title,
subtitle = launch.subtitle,
target = target,
onBack = {
CatalogRepository.clear()
CatalogLaunchStore.remove(route.launchId)
navController.popBackStack()
},
onPosterClick = { meta ->
navController.navigate(DetailRoute(type = meta.type, id = meta.id))
},
onPosterLongClick = { meta ->
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
selectedPosterActionTarget = if (target is CatalogTarget.Library) {
PosterActionTarget(
preview = meta,
libraryItem = meta.toLibraryItem(savedAtEpochMs = 0L),
libraryListKey = target.sectionType,
)
} else {
PosterActionTarget(preview = meta)
}
openPosterActions(
if (target is CatalogTarget.Library) {
PosterActionTarget(
preview = meta,
libraryItem = meta.toLibraryItem(savedAtEpochMs = 0L),
libraryListKey = target.sectionType,
)
} else {
PosterActionTarget(preview = meta)
},
)
},
modifier = Modifier.fillMaxSize(),
)

View file

@ -10,9 +10,11 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
@ -86,7 +88,10 @@ fun SubmitIntroDialog(
BasicAlertDialog(onDismissRequest = onDismiss) {
Surface(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 24.dp),
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 24.dp)
.widthIn(max = 420.dp)
.heightIn(max = 560.dp),
shape = RoundedCornerShape(24.dp),
color = MaterialTheme.colorScheme.surface,
tonalElevation = 8.dp,
@ -94,6 +99,7 @@ fun SubmitIntroDialog(
Column(
modifier = Modifier
.padding(24.dp)
.heightIn(max = 512.dp)
.verticalScroll(scrollState),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {