diff --git a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt index f127944e..c7b12bfc 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt @@ -265,8 +265,7 @@ class MainActivity : AppCompatActivity() { server = current.server, startDestination = requestedDestination - // TODO Undo - ?: Destination.HomeSettings, + ?: Destination.Home(), navigationManager = navigationManager, preferences = preferences, modifier = Modifier.fillMaxSize(), diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt index 16dc7db8..d9c184a3 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt @@ -309,16 +309,15 @@ class HomeSettingsService userDto: UserDto, libraries: List, limit: Int = prefs.maxItemsPerRow, - ): List = + ): HomeRowLoadingState = when (row) { is HomeRowConfig.ContinueWatching -> { val resume = latestNextUpService.getResume(userDto.id, limit, true) - listOf( - HomeRowLoadingState.Success( - title = context.getString(R.string.continue_watching), - items = resume, - viewOptions = row.viewOptions, - ), + + HomeRowLoadingState.Success( + title = context.getString(R.string.continue_watching), + items = resume, + viewOptions = row.viewOptions, ) } @@ -330,12 +329,11 @@ class HomeSettingsService prefs.enableRewatchingNextUp, false, ) - listOf( - HomeRowLoadingState.Success( - title = context.getString(R.string.next_up), - items = nextUp, - viewOptions = row.viewOptions, - ), + + HomeRowLoadingState.Success( + title = context.getString(R.string.next_up), + items = nextUp, + viewOptions = row.viewOptions, ) } @@ -350,16 +348,14 @@ class HomeSettingsService false, ) - listOf( - HomeRowLoadingState.Success( - title = context.getString(R.string.continue_watching), - items = - latestNextUpService.buildCombined( - resume, - nextUp, - ), - viewOptions = row.viewOptions, - ), + HomeRowLoadingState.Success( + title = context.getString(R.string.continue_watching), + items = + latestNextUpService.buildCombined( + resume, + nextUp, + ), + viewOptions = row.viewOptions, ) } @@ -397,12 +393,11 @@ class HomeSettingsService val title = name?.let { context.getString(R.string.genres_in, it) } ?: context.getString(R.string.genres) - listOf( - HomeRowLoadingState.Success( - title, - genres, - viewOptions = row.viewOptions, - ), + + HomeRowLoadingState.Success( + title, + genres, + viewOptions = row.viewOptions, ) } @@ -435,7 +430,7 @@ class HomeSettingsService row.viewOptions, ) } - listOf(latest) + latest } is HomeRowConfig.RecentlyReleased -> { @@ -461,12 +456,10 @@ class HomeSettingsService .content.items .map { BaseItem.Companion.from(it, api, true) } .let { - listOf( - HomeRowLoadingState.Success( - title, - it, - row.viewOptions, - ), + HomeRowLoadingState.Success( + title, + it, + row.viewOptions, ) } } @@ -491,12 +484,10 @@ class HomeSettingsService .content.items .map { BaseItem(it, true) } .let { - listOf( - HomeRowLoadingState.Success( - name ?: context.getString(R.string.collection), - it, - row.viewOptions, - ), + HomeRowLoadingState.Success( + name ?: context.getString(R.string.collection), + it, + row.viewOptions, ) } } @@ -529,12 +520,10 @@ class HomeSettingsService .content.items .map { BaseItem.Companion.from(it, api, true) } .let { - listOf( - HomeRowLoadingState.Success( - name ?: context.getString(R.string.collection), - it, - row.viewOptions, - ), + HomeRowLoadingState.Success( + name ?: context.getString(R.string.collection), + it, + row.viewOptions, ) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt index 3bcea534..a218f985 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomePage.kt @@ -21,6 +21,7 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf @@ -90,12 +91,10 @@ fun HomePage( LaunchedEffect(Unit) { viewModel.init() } - val loading by viewModel.loadingState.observeAsState(LoadingState.Loading) - val refreshing by viewModel.refreshState.observeAsState(LoadingState.Loading) - val watchingRows by viewModel.watchingRows.observeAsState(listOf()) - val latestRows by viewModel.latestRows.observeAsState(listOf()) - - val homeRows = remember(watchingRows, latestRows) { watchingRows + latestRows } + val state by viewModel.state.collectAsState() + val loading = state.loadingState + val refreshing = state.refreshState + val homeRows = state.homeRows when (val state = loading) { is LoadingState.Error -> { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt index 1911e34d..877ab014 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt @@ -8,14 +8,16 @@ import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.NavDrawerItemRepository import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.BaseItem -import com.github.damontecres.wholphin.preferences.UserPreferences +import com.github.damontecres.wholphin.data.model.HomePageResolvedSettings import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.DatePlayedService import com.github.damontecres.wholphin.services.FavoriteWatchManager +import com.github.damontecres.wholphin.services.HomeSettingsService import com.github.damontecres.wholphin.services.LatestNextUpService import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.UserPreferencesService import com.github.damontecres.wholphin.ui.launchIO +import com.github.damontecres.wholphin.ui.main.settings.Library import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem import com.github.damontecres.wholphin.ui.setValueOnMain import com.github.damontecres.wholphin.ui.showToast @@ -26,7 +28,15 @@ import com.github.damontecres.wholphin.util.LoadingState import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.withContext import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.model.api.CollectionType @@ -44,6 +54,7 @@ class HomeViewModel val navigationManager: NavigationManager, val serverRepository: ServerRepository, val navDrawerItemRepository: NavDrawerItemRepository, + private val homeSettingsService: HomeSettingsService, private val favoriteWatchManager: FavoriteWatchManager, private val datePlayedService: DatePlayedService, private val latestNextUpService: LatestNextUpService, @@ -55,7 +66,8 @@ class HomeViewModel val watchingRows = MutableLiveData>(listOf()) val latestRows = MutableLiveData>(listOf()) - private lateinit var preferences: UserPreferences + private val _state = MutableStateFlow(HomeState.EMPTY) + val state: StateFlow = _state init { datePlayedService.invalidateAll() @@ -76,12 +88,86 @@ class HomeViewModel loadingState.setValueOnMain(LoadingState.Loading) } refreshState.setValueOnMain(LoadingState.Loading) - this@HomeViewModel.preferences = userPreferencesService.getCurrent() + val preferences = userPreferencesService.getCurrent() val prefs = preferences.appPreferences.homePagePreferences val limit = prefs.maxItemsPerRow if (reload) { backdropService.clearBackdrop() } + + val navDrawerItems = + navDrawerItemRepository + .getNavDrawerItems() + val libraries = + navDrawerItems + .filter { it is ServerNavDrawerItem } + .map { + it as ServerNavDrawerItem + Library(it.itemId, it.name, it.type) + } + serverRepository.currentUserDto.value?.let { userDto -> + val settings = + homeSettingsService.currentSettings.first { it != HomePageResolvedSettings.EMPTY } + val state = state.value + + // Refreshing if a load has already occurred and the rows haven't significantly changed + val refresh = + state.loadingState == LoadingState.Success && state.settings == settings + + val semaphore = Semaphore(4) + val loadingRows = + if (refresh) { + state.homeRows + } else { + mutableListOf() + } + val deferred = + settings.rows.mapIndexed { index, row -> + if (refresh) { + (loadingRows as MutableList).add(HomeRowLoadingState.Loading(row.title)) + } + + viewModelScope.async(Dispatchers.IO) { + semaphore.withPermit { + Timber.v("Fetching row: %s", row) + try { + homeSettingsService.fetchDataForRow( + row = row.config, + scope = viewModelScope, + prefs = prefs, + userDto = userDto, + libraries = libraries, + limit = prefs.maxItemsPerRow, + ) + } catch (ex: Exception) { + Timber.e(ex, "Error on row %s", row) + HomeRowLoadingState.Error(row.title, exception = ex) + } + } + } + } + if (refresh) { + deferred.firstOrNull()?.await()?.let { + (loadingRows as MutableList)[0] = it + } + _state.update { + it.copy( + loadingState = LoadingState.Success, + homeRows = loadingRows, + ) + } + } + val rows = deferred.awaitAll() + Timber.v("Got all rows") + _state.update { + it.copy( + loadingState = LoadingState.Success, + refreshState = LoadingState.Success, + homeRows = rows, + ) + } + } + try { serverRepository.currentUserDto.value?.let { userDto -> val includedIds = @@ -192,3 +278,20 @@ data class LatestData( val title: String, val request: GetLatestMediaRequest, ) + +data class HomeState( + val loadingState: LoadingState, + val refreshState: LoadingState, + val homeRows: List, + val settings: HomePageResolvedSettings, +) { + companion object { + val EMPTY = + HomeState( + LoadingState.Pending, + LoadingState.Pending, + listOf(), + HomePageResolvedSettings.EMPTY, + ) + } +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt index 98074c09..4b88834e 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt @@ -96,7 +96,7 @@ class HomeSettingsViewModel libraries = state.libraries, limit = limit, ) - }.flatten() + } } } rows?.let { rows ->