Refactor movie page to use use state flow (#1183)

## Description
Refactors the movie page & view model to use a `StateFlow` instead of
many `LiveData` objects. This is in keeping with best practices.

There are no user facing changes.

### Related issues
None

### Testing
Emulator

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Ray 2026-04-03 12:44:59 -04:00 committed by GitHub
parent feb8c31791
commit 44d1ac484f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 316 additions and 307 deletions

View file

@ -14,6 +14,7 @@ import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.ui.detail.DebugViewModel.Companion.sendAppLogs import com.github.damontecres.wholphin.ui.detail.DebugViewModel.Companion.sendAppLogs
import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
@ -98,3 +99,13 @@ fun ErrorMessage(
exception = error.exception, exception = error.exception,
modifier = modifier, modifier = modifier,
) )
@Composable
fun ErrorMessage(
error: DataLoadingState.Error,
modifier: Modifier = Modifier,
) = ErrorMessage(
message = error.message,
exception = error.exception,
modifier = modifier,
)

View file

@ -17,6 +17,7 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
@ -28,10 +29,8 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.LifecycleResumeEffect
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ChosenStreams
import com.github.damontecres.wholphin.data.ExtrasItem import com.github.damontecres.wholphin.data.ExtrasItem
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.Chapter
import com.github.damontecres.wholphin.data.model.DiscoverItem import com.github.damontecres.wholphin.data.model.DiscoverItem
import com.github.damontecres.wholphin.data.model.Person import com.github.damontecres.wholphin.data.model.Person
import com.github.damontecres.wholphin.data.model.Trailer import com.github.damontecres.wholphin.data.model.Trailer
@ -67,11 +66,11 @@ import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForPerson import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForPerson
import com.github.damontecres.wholphin.ui.discover.DiscoverRow import com.github.damontecres.wholphin.ui.discover.DiscoverRow
import com.github.damontecres.wholphin.ui.discover.DiscoverRowData import com.github.damontecres.wholphin.ui.discover.DiscoverRowData
import com.github.damontecres.wholphin.ui.letNotEmpty
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.rememberInt import com.github.damontecres.wholphin.ui.rememberInt
import com.github.damontecres.wholphin.util.DataLoadingState import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.MediaStreamType import org.jellyfin.sdk.model.api.MediaStreamType
@ -98,16 +97,7 @@ fun MovieDetails(
viewModel.init() viewModel.init()
onPauseOrDispose { } onPauseOrDispose { }
} }
val item by viewModel.item.observeAsState() val state by viewModel.state.collectAsState()
val people by viewModel.people.observeAsState(listOf())
val chapters by viewModel.chapters.observeAsState(listOf())
val trailers by viewModel.trailers.observeAsState(listOf())
val extras by viewModel.extras.observeAsState(listOf())
val similar by viewModel.similar.observeAsState(listOf())
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
val chosenStreams by viewModel.chosenStreams.observeAsState(null)
val discovered by viewModel.discovered.collectAsState()
val canDelete by viewModel.canDelete.collectAsState()
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) } var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
var moreDialog by remember { mutableStateOf<DialogParams?>(null) } var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
@ -140,19 +130,21 @@ fun MovieDetails(
onClickDelete = { showDeleteDialog = it }, onClickDelete = { showDeleteDialog = it },
) )
when (val state = loading) { when (val s = state.loading) {
is LoadingState.Error -> { is DataLoadingState.Error -> {
ErrorMessage(state, modifier) ErrorMessage(s, modifier)
} }
LoadingState.Loading, DataLoadingState.Loading,
LoadingState.Pending, DataLoadingState.Pending,
-> { -> {
LoadingPage(modifier) LoadingPage(modifier)
} }
LoadingState.Success -> { is DataLoadingState.Success -> {
item?.let { movie -> val unknownStr = stringResource(R.string.unknown)
val movie by rememberUpdatedState(s.data)
val chosenStreams by rememberUpdatedState(state.chosenStreams)
LifecycleResumeEffect(destination.itemId) { LifecycleResumeEffect(destination.itemId) {
viewModel.maybePlayThemeSong( viewModel.maybePlayThemeSong(
destination.itemId, destination.itemId,
@ -165,12 +157,7 @@ fun MovieDetails(
MovieDetailsContent( MovieDetailsContent(
preferences = preferences, preferences = preferences,
movie = movie, movie = movie,
chosenStreams = chosenStreams, state = state,
people = people,
chapters = chapters,
extras = extras,
trailers = trailers,
similar = similar,
onClickItem = { index, item -> onClickItem = { index, item ->
viewModel.navigateTo(item.destination()) viewModel.navigateTo(item.destination())
}, },
@ -193,7 +180,7 @@ fun MovieDetails(
overviewOnClick = { overviewOnClick = {
overviewDialog = overviewDialog =
ItemDetailsDialogInfo( ItemDetailsDialogInfo(
title = movie.name ?: context.getString(R.string.unknown), title = movie.name ?: unknownStr,
overview = movie.data.overview, overview = movie.data.overview,
genres = movie.data.genres.orEmpty(), genres = movie.data.genres.orEmpty(),
files = movie.data.mediaSources.orEmpty(), files = movie.data.mediaSources.orEmpty(),
@ -212,8 +199,8 @@ fun MovieDetails(
favorite = movie.data.userData?.isFavorite ?: false, favorite = movie.data.userData?.isFavorite ?: false,
seriesId = null, seriesId = null,
sourceId = chosenStreams?.source?.id?.toUUIDOrNull(), sourceId = chosenStreams?.source?.id?.toUUIDOrNull(),
canClearChosenStreams = chosenStreams?.itemPlayback != null || chosenStreams?.plc != null, canClearChosenStreams = chosenStreams.let { it?.itemPlayback != null || it?.plc != null },
canDelete = canDelete, canDelete = state.canDelete,
actions = moreActions, actions = moreActions,
onChooseVersion = { onChooseVersion = {
chooseVersion = chooseVersion =
@ -230,7 +217,6 @@ fun MovieDetails(
moreDialog = null moreDialog = null
}, },
onChooseTracks = { type -> onChooseTracks = { type ->
viewModel.streamChoiceService viewModel.streamChoiceService
.chooseSource( .chooseSource(
movie.data, movie.data,
@ -262,9 +248,7 @@ fun MovieDetails(
onShowOverview = { onShowOverview = {
overviewDialog = overviewDialog =
ItemDetailsDialogInfo( ItemDetailsDialogInfo(
title = title = movie.name ?: unknownStr,
movie.name
?: context.getString(R.string.unknown),
overview = movie.data.overview, overview = movie.data.overview,
genres = movie.data.genres.orEmpty(), genres = movie.data.genres.orEmpty(),
files = movie.data.mediaSources.orEmpty(), files = movie.data.mediaSources.orEmpty(),
@ -321,17 +305,15 @@ fun MovieDetails(
onClickExtra = { index, extra -> onClickExtra = { index, extra ->
viewModel.navigateTo(extra.destination) viewModel.navigateTo(extra.destination)
}, },
discovered = discovered,
onClickDiscover = { index, item -> onClickDiscover = { index, item ->
viewModel.navigateTo(item.destination) viewModel.navigateTo(item.destination)
}, },
canDelete = canDelete, canDelete = state.canDelete,
deleteOnClick = { showDeleteDialog = movie }, deleteOnClick = { showDeleteDialog = state.movie },
modifier = modifier, modifier = modifier,
) )
} }
} }
}
overviewDialog?.let { info -> overviewDialog?.let { info ->
ItemDetailsDialog( ItemDetailsDialog(
info = info, info = info,
@ -403,13 +385,7 @@ private const val DISCOVER_ROW = SIMILAR_ROW + 1
fun MovieDetailsContent( fun MovieDetailsContent(
preferences: UserPreferences, preferences: UserPreferences,
movie: BaseItem, movie: BaseItem,
chosenStreams: ChosenStreams?, state: MovieState,
people: List<Person>,
chapters: List<Chapter>,
trailers: List<Trailer>,
extras: List<ExtrasItem>,
similar: List<BaseItem>,
discovered: List<DiscoverItem>,
playOnClick: (Duration) -> Unit, playOnClick: (Duration) -> Unit,
trailerOnClick: (Trailer) -> Unit, trailerOnClick: (Trailer) -> Unit,
overviewOnClick: () -> Unit, overviewOnClick: () -> Unit,
@ -454,7 +430,7 @@ fun MovieDetailsContent(
MovieDetailsHeader( MovieDetailsHeader(
preferences = preferences, preferences = preferences,
movie = movie, movie = movie,
chosenStreams = chosenStreams, chosenStreams = state.chosenStreams,
bringIntoViewRequester = bringIntoViewRequester, bringIntoViewRequester = bringIntoViewRequester,
overviewOnClick = overviewOnClick, overviewOnClick = overviewOnClick,
modifier = modifier =
@ -481,7 +457,7 @@ fun MovieDetailsContent(
} }
} }
}, },
trailers = trailers, trailers = state.trailers,
trailerOnClick = { trailerOnClick = {
position = TRAILER_ROW position = TRAILER_ROW
trailerOnClick.invoke(it) trailerOnClick.invoke(it)
@ -496,7 +472,7 @@ fun MovieDetailsContent(
) )
} }
} }
if (people.isNotEmpty()) { state.people.letNotEmpty { people ->
item { item {
PersonRow( PersonRow(
people = people, people = people,
@ -515,7 +491,7 @@ fun MovieDetailsContent(
) )
} }
} }
if (chapters.isNotEmpty()) { state.chapters.letNotEmpty { chapters ->
item { item {
ChapterRow( ChapterRow(
chapters = chapters, chapters = chapters,
@ -532,7 +508,7 @@ fun MovieDetailsContent(
) )
} }
} }
if (extras.isNotEmpty()) { state.extras.letNotEmpty { extras ->
item { item {
ExtrasRow( ExtrasRow(
extras = extras, extras = extras,
@ -548,7 +524,7 @@ fun MovieDetailsContent(
) )
} }
} }
if (similar.isNotEmpty()) { state.similar.letNotEmpty { similar ->
item { item {
val imageHeight = val imageHeight =
remember(movie.type) { remember(movie.type) {
@ -587,7 +563,7 @@ fun MovieDetailsContent(
) )
} }
} }
if (discovered.isNotEmpty()) { state.discovered.letNotEmpty { discovered ->
item { item {
DiscoverRow( DiscoverRow(
row = row =

View file

@ -1,9 +1,7 @@
package com.github.damontecres.wholphin.ui.detail.movie package com.github.damontecres.wholphin.ui.detail.movie
import android.content.Context import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.asFlow
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.data.ChosenStreams import com.github.damontecres.wholphin.data.ChosenStreams
import com.github.damontecres.wholphin.data.ExtrasItem import com.github.damontecres.wholphin.data.ExtrasItem
@ -30,36 +28,28 @@ import com.github.damontecres.wholphin.services.TrailerService
import com.github.damontecres.wholphin.services.UserPreferencesService import com.github.damontecres.wholphin.services.UserPreferencesService
import com.github.damontecres.wholphin.services.deleteItem import com.github.damontecres.wholphin.services.deleteItem
import com.github.damontecres.wholphin.ui.SlimItemFields import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.combinePair
import com.github.damontecres.wholphin.ui.launchDefault import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.letNotEmpty import com.github.damontecres.wholphin.ui.letNotEmpty
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.setValueOnMain import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.DataLoadingState
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.libraryApi import org.jellyfin.sdk.api.client.extensions.libraryApi
import org.jellyfin.sdk.api.client.extensions.userLibraryApi import org.jellyfin.sdk.api.client.extensions.userLibraryApi
import org.jellyfin.sdk.model.api.MediaStreamType import org.jellyfin.sdk.model.api.MediaStreamType
import org.jellyfin.sdk.model.api.request.GetSimilarItemsRequest import org.jellyfin.sdk.model.api.request.GetSimilarItemsRequest
import timber.log.Timber
import java.util.UUID import java.util.UUID
@HiltViewModel(assistedFactory = MovieViewModel.Factory::class) @HiltViewModel(assistedFactory = MovieViewModel.Factory::class)
@ -89,94 +79,99 @@ class MovieViewModel
fun create(itemId: UUID): MovieViewModel fun create(itemId: UUID): MovieViewModel
} }
val loading = MutableLiveData<LoadingState>(LoadingState.Pending) private val _state = MutableStateFlow(MovieState())
val item = MutableLiveData<BaseItem?>(null) val state: StateFlow<MovieState> = _state
val trailers = MutableLiveData<List<Trailer>>(listOf())
val people = MutableLiveData<List<Person>>(listOf())
val chapters = MutableLiveData<List<Chapter>>(listOf())
val extras = MutableLiveData<List<ExtrasItem>>(listOf())
val similar = MutableLiveData<List<BaseItem>>()
val chosenStreams = MutableLiveData<ChosenStreams?>(null)
val discovered = MutableStateFlow<List<DiscoverItem>>(listOf())
val canDelete = MutableStateFlow(false)
init { init {
init() init()
viewModelScope.launchDefault { viewModelScope.launchDefault {
item userPreferencesService.flow.collectLatest { preferences ->
.asFlow() _state.update {
.filterNotNull() val canDelete =
.combinePair(userPreferencesService.flow.map { it.appPreferences }) it.movie?.let {
.collectLatest { (item, preferences) -> mediaManagementService.canDelete(
canDelete.update { mediaManagementService.canDelete(item, preferences) } it,
preferences.appPreferences,
)
}
it.copy(
canDelete = canDelete ?: false,
)
}
} }
} }
} }
private fun fetchAndSetItem(): Deferred<BaseItem> = private suspend fun getMovie(): BaseItem {
viewModelScope.async(
Dispatchers.IO +
LoadingExceptionHandler(
loading,
"Error fetching movie",
),
) {
val item = val item =
api.userLibraryApi.getItem(itemId).content.let { api.userLibraryApi.getItem(itemId).content.let {
BaseItem.from(it, api) BaseItem(it)
} }
this@MovieViewModel.item.setValueOnMain(item) return item
item
} }
fun init(): Job = fun init(): Job =
viewModelScope.launch( viewModelScope.launchDefault {
Dispatchers.IO + val movie =
LoadingExceptionHandler( try {
loading, getMovie()
"Error fetching movie", } catch (ex: Exception) {
), Timber.e(ex, "Failed to fetch movie %s", itemId)
) { _state.update { it.copy(loading = DataLoadingState.Error(ex)) }
val item = fetchAndSetItem().await() return@launchDefault
val result = }
val chosenStreams =
itemPlaybackRepository.getSelectedTracks( itemPlaybackRepository.getSelectedTracks(
item.id, itemId,
item, movie,
userPreferencesService.getCurrent(), userPreferencesService.getCurrent(),
) )
val remoteTrailers = trailerService.getRemoteTrailers(item) val remoteTrailers = trailerService.getRemoteTrailers(movie)
withContext(Dispatchers.Main) { val chapters = Chapter.fromDto(movie.data, api)
this@MovieViewModel.item.value = item _state.update {
chosenStreams.value = result it.copy(
this@MovieViewModel.trailers.value = remoteTrailers loading = DataLoadingState.Success(movie),
loading.value = LoadingState.Success chosenStreams = chosenStreams,
backdropService.submit(item) trailers = remoteTrailers,
chapters = chapters,
)
} }
backdropService.submit(movie)
viewModelScope.launchIO { viewModelScope.launchIO {
trailerService.getLocalTrailers(item).letNotEmpty { localTrailers -> trailerService.getLocalTrailers(movie).letNotEmpty { localTrailers ->
withContext(Dispatchers.Main) { _state.update {
this@MovieViewModel.trailers.value = localTrailers + remoteTrailers it.copy(
trailers = localTrailers + remoteTrailers,
)
} }
} }
} }
viewModelScope.launchIO { viewModelScope.launchIO {
val people = peopleFavorites.getPeopleFor(item) val people = peopleFavorites.getPeopleFor(movie)
this@MovieViewModel.people.setValueOnMain(people) _state.update {
it.copy(
people = people,
)
}
} }
viewModelScope.launchIO { viewModelScope.launchIO {
val extras = extrasService.getExtras(item.id) val extras = extrasService.getExtras(itemId)
this@MovieViewModel.extras.setValueOnMain(extras) _state.update {
it.copy(
extras = extras,
)
}
} }
viewModelScope.launchIO { viewModelScope.launchIO {
val results = seerrService.similar(item).orEmpty() val results = seerrService.similar(movie).orEmpty()
discovered.update { results } _state.update {
it.copy(
discovered = results,
)
}
} }
withContext(Dispatchers.Main) { if (state.value.similar.isEmpty()) {
chapters.value = Chapter.fromDto(item.data, api)
}
if (!similar.isInitialized) {
val similar = val similar =
api.libraryApi api.libraryApi
.getSimilarItems( .getSimilarItems(
@ -187,31 +182,48 @@ class MovieViewModel
limit = 25, limit = 25,
), ),
).content.items ).content.items
.map { BaseItem.Companion.from(it, api) } .map { BaseItem(it) }
this@MovieViewModel.similar.setValueOnMain(similar)
_state.update { it.copy(similar = similar) }
} }
} }
fun setWatched( fun setWatched(
itemId: UUID, itemId: UUID,
played: Boolean, played: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { ) = viewModelScope.launchDefault {
try {
favoriteWatchManager.setWatched(itemId, played) favoriteWatchManager.setWatched(itemId, played)
fetchAndSetItem() getMovie().let { movie ->
_state.update {
it.copy(loading = DataLoadingState.Success(movie))
}
}
} catch (ex: Exception) {
Timber.e(ex, "Error updating watch status for movie %s", itemId)
showToast(context, "Something went wrong...")
}
} }
fun setFavorite( fun setFavorite(
itemId: UUID, itemId: UUID,
favorite: Boolean, favorite: Boolean,
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) { ) = viewModelScope.launchDefault {
try {
favoriteWatchManager.setFavorite(itemId, favorite) favoriteWatchManager.setFavorite(itemId, favorite)
val item = item.value val movie = getMovie()
fetchAndSetItem() _state.update {
if (item != null && itemId != item.id) { it.copy(loading = DataLoadingState.Success(movie))
viewModelScope.launchIO {
val people = peopleFavorites.getPeopleFor(item)
this@MovieViewModel.people.setValueOnMain(people)
} }
if (itemId != movie.id) {
viewModelScope.launchIO {
val people = peopleFavorites.getPeopleFor(movie)
_state.update { it.copy(people = people) }
}
}
} catch (ex: Exception) {
Timber.e(ex, "Error updating favorite %s", itemId)
showToast(context, "Something went wrong...")
} }
} }
@ -227,9 +239,7 @@ class MovieViewModel
result?.let { result?.let {
itemPlaybackRepository.getChosenItemFromPlayback(item, result, plc, prefs) itemPlaybackRepository.getChosenItemFromPlayback(item, result, plc, prefs)
} }
withContext(Dispatchers.Main) { _state.update { it.copy(chosenStreams = chosen) }
chosenStreams.value = chosen
}
} }
} }
@ -253,9 +263,7 @@ class MovieViewModel
result?.let { result?.let {
itemPlaybackRepository.getChosenItemFromPlayback(item, result, plc, prefs) itemPlaybackRepository.getChosenItemFromPlayback(item, result, plc, prefs)
} }
withContext(Dispatchers.Main) { _state.update { it.copy(chosenStreams = chosen) }
chosenStreams.value = chosen
}
} }
} }
@ -283,14 +291,14 @@ class MovieViewModel
fun clearChosenStreams(chosenStreams: ChosenStreams?) { fun clearChosenStreams(chosenStreams: ChosenStreams?) {
viewModelScope.launchIO { viewModelScope.launchIO {
itemPlaybackRepository.deleteChosenStreams(chosenStreams) itemPlaybackRepository.deleteChosenStreams(chosenStreams)
item.value?.let { item -> state.value.movie?.let { item ->
val result = val result =
itemPlaybackRepository.getSelectedTracks( itemPlaybackRepository.getSelectedTracks(
itemId, itemId,
item, item,
userPreferencesService.getCurrent(), userPreferencesService.getCurrent(),
) )
this@MovieViewModel.chosenStreams.setValueOnMain(result) _state.update { it.copy(chosenStreams = result) }
} }
} }
} }
@ -301,3 +309,17 @@ class MovieViewModel
} }
} }
} }
data class MovieState(
val loading: DataLoadingState<BaseItem> = DataLoadingState.Pending,
val trailers: List<Trailer> = emptyList(),
val people: List<Person> = emptyList(),
val chapters: List<Chapter> = emptyList(),
val extras: List<ExtrasItem> = emptyList(),
val similar: List<BaseItem> = emptyList(),
val discovered: List<DiscoverItem> = emptyList(),
val chosenStreams: ChosenStreams? = null,
val canDelete: Boolean = false,
) {
val movie: BaseItem? = (loading as? DataLoadingState.Success<BaseItem>)?.data
}