mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11:20 +02:00
Variety of UI changes for grids, home page order, & tv guide (#272)
## Details This PR ended up being larger than I meant. It contains some UI changes and code organization. ## TV & DVR - Uses the "Recordings" library position to order "Recently added in Recordings" instead of Live TV position - The times at the top of the TV guide are more distinguishable from each other - Removes the duplicate title on the Live TV->Recordings tab ## Other libraries - Bare-bone support for "Music Video" libraries. Full support for "Music Video" is work-in-progress in #267, but the libraries should be browseable now. - Bare-bone support for "Other" (aka Mixed Movies & TV Shows) libraries. Note: this type of library is considered [broken and deprecated](https://jellyfin.org/docs/general/server/media/mixed-movies-and-shows), so this won't be expanded any further ## Grids Updates various grid types to use poster vs wide cards. For example, Favorite TV Episodes will use episode images in a wide card now. ## Issues Closes #249 Closes #271
This commit is contained in:
parent
39ad7e564f
commit
45567bf4eb
13 changed files with 287 additions and 161 deletions
|
|
@ -4,11 +4,13 @@ import android.content.Context
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
|
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
|
||||||
import com.github.damontecres.wholphin.data.model.NavPinType
|
import com.github.damontecres.wholphin.data.model.NavPinType
|
||||||
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
||||||
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
|
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
|
||||||
import com.github.damontecres.wholphin.util.supportedCollectionTypes
|
import com.github.damontecres.wholphin.util.supportedCollectionTypes
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
|
import org.jellyfin.sdk.api.client.extensions.liveTvApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.userViewsApi
|
import org.jellyfin.sdk.api.client.extensions.userViewsApi
|
||||||
import org.jellyfin.sdk.model.api.CollectionType
|
import org.jellyfin.sdk.model.api.CollectionType
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -24,21 +26,33 @@ class NavDrawerItemRepository
|
||||||
private val serverPreferencesDao: ServerPreferencesDao,
|
private val serverPreferencesDao: ServerPreferencesDao,
|
||||||
) {
|
) {
|
||||||
suspend fun getNavDrawerItems(): List<NavDrawerItem> {
|
suspend fun getNavDrawerItems(): List<NavDrawerItem> {
|
||||||
val user = serverRepository.currentUser
|
val user = serverRepository.currentUser.value
|
||||||
val userViews =
|
val userViews =
|
||||||
api.userViewsApi
|
api.userViewsApi
|
||||||
.getUserViews(userId = user.value?.id)
|
.getUserViews(userId = user?.id)
|
||||||
.content.items
|
.content.items
|
||||||
|
val recordingFolders =
|
||||||
|
api.liveTvApi
|
||||||
|
.getRecordingFolders(userId = user?.id)
|
||||||
|
.content.items
|
||||||
|
.map { it.id }
|
||||||
|
.toSet()
|
||||||
|
|
||||||
val builtins = listOf(NavDrawerItem.Favorites)
|
val builtins = listOf(NavDrawerItem.Favorites)
|
||||||
val libraries =
|
val libraries =
|
||||||
userViews
|
userViews
|
||||||
.filter { it.collectionType in supportedCollectionTypes }
|
.filter { it.collectionType in supportedCollectionTypes || it.id in recordingFolders }
|
||||||
.map {
|
.map {
|
||||||
|
val destination =
|
||||||
|
if (it.id in recordingFolders) {
|
||||||
|
Destination.Recordings(it.id)
|
||||||
|
} else {
|
||||||
|
BaseItem.from(it, api).destination()
|
||||||
|
}
|
||||||
ServerNavDrawerItem(
|
ServerNavDrawerItem(
|
||||||
itemId = it.id,
|
itemId = it.id,
|
||||||
name = it.name ?: it.id.toString(),
|
name = it.name ?: it.id.toString(),
|
||||||
destination = BaseItem.from(it, api).destination(),
|
destination = destination,
|
||||||
type = it.collectionType ?: CollectionType.UNKNOWN,
|
type = it.collectionType ?: CollectionType.UNKNOWN,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||||
import com.github.damontecres.wholphin.services.NavigationManager
|
import com.github.damontecres.wholphin.services.NavigationManager
|
||||||
|
import com.github.damontecres.wholphin.ui.AspectRatios
|
||||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||||
import com.github.damontecres.wholphin.ui.cards.GridCard
|
import com.github.damontecres.wholphin.ui.cards.GridCard
|
||||||
|
|
@ -96,11 +97,14 @@ class CollectionFolderViewModel
|
||||||
val sortAndDirection = MutableLiveData<SortAndDirection>()
|
val sortAndDirection = MutableLiveData<SortAndDirection>()
|
||||||
val filter = MutableLiveData<GetItemsFilter>(GetItemsFilter())
|
val filter = MutableLiveData<GetItemsFilter>(GetItemsFilter())
|
||||||
|
|
||||||
|
private var useSeriesForPrimary: Boolean = true
|
||||||
|
|
||||||
fun init(
|
fun init(
|
||||||
itemId: String,
|
itemId: String,
|
||||||
initialSortAndDirection: SortAndDirection?,
|
initialSortAndDirection: SortAndDirection?,
|
||||||
recursive: Boolean,
|
recursive: Boolean,
|
||||||
filter: GetItemsFilter,
|
filter: GetItemsFilter,
|
||||||
|
useSeriesForPrimary: Boolean,
|
||||||
): Job =
|
): Job =
|
||||||
viewModelScope.launch(
|
viewModelScope.launch(
|
||||||
LoadingExceptionHandler(
|
LoadingExceptionHandler(
|
||||||
|
|
@ -108,6 +112,7 @@ class CollectionFolderViewModel
|
||||||
context.getString(R.string.error_loading_collection, itemId),
|
context.getString(R.string.error_loading_collection, itemId),
|
||||||
) + Dispatchers.IO,
|
) + Dispatchers.IO,
|
||||||
) {
|
) {
|
||||||
|
this@CollectionFolderViewModel.useSeriesForPrimary = useSeriesForPrimary
|
||||||
this@CollectionFolderViewModel.itemId = itemId
|
this@CollectionFolderViewModel.itemId = itemId
|
||||||
itemId?.toUUIDOrNull()?.let {
|
itemId?.toUUIDOrNull()?.let {
|
||||||
fetchItem(it)
|
fetchItem(it)
|
||||||
|
|
@ -121,7 +126,7 @@ class CollectionFolderViewModel
|
||||||
} ?: SortAndDirection.DEFAULT
|
} ?: SortAndDirection.DEFAULT
|
||||||
)
|
)
|
||||||
|
|
||||||
loadResults(sortAndDirection, recursive, filter)
|
loadResults(sortAndDirection, recursive, filter, useSeriesForPrimary)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSortChange(
|
fun onSortChange(
|
||||||
|
|
@ -141,13 +146,14 @@ class CollectionFolderViewModel
|
||||||
libraryDisplayInfoDao.saveItem(libraryDisplayInfo)
|
libraryDisplayInfoDao.saveItem(libraryDisplayInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadResults(sortAndDirection, recursive, filter)
|
loadResults(sortAndDirection, recursive, filter, useSeriesForPrimary)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadResults(
|
private fun loadResults(
|
||||||
sortAndDirection: SortAndDirection,
|
sortAndDirection: SortAndDirection,
|
||||||
recursive: Boolean,
|
recursive: Boolean,
|
||||||
filter: GetItemsFilter,
|
filter: GetItemsFilter,
|
||||||
|
useSeriesForPrimary: Boolean,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
|
|
@ -156,7 +162,7 @@ class CollectionFolderViewModel
|
||||||
this@CollectionFolderViewModel.sortAndDirection.value = sortAndDirection
|
this@CollectionFolderViewModel.sortAndDirection.value = sortAndDirection
|
||||||
this@CollectionFolderViewModel.filter.value = filter
|
this@CollectionFolderViewModel.filter.value = filter
|
||||||
}
|
}
|
||||||
val newPager = createPager(sortAndDirection, recursive, filter)
|
val newPager = createPager(sortAndDirection, recursive, filter, useSeriesForPrimary)
|
||||||
newPager.init()
|
newPager.init()
|
||||||
if (newPager.isNotEmpty()) newPager.getBlocking(0)
|
if (newPager.isNotEmpty()) newPager.getBlocking(0)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
|
|
@ -170,6 +176,7 @@ class CollectionFolderViewModel
|
||||||
sortAndDirection: SortAndDirection,
|
sortAndDirection: SortAndDirection,
|
||||||
recursive: Boolean,
|
recursive: Boolean,
|
||||||
filter: GetItemsFilter,
|
filter: GetItemsFilter,
|
||||||
|
useSeriesForPrimary: Boolean,
|
||||||
): ApiRequestPager<out Any> {
|
): ApiRequestPager<out Any> {
|
||||||
val item = item.value
|
val item = item.value
|
||||||
return when (filter.override) {
|
return when (filter.override) {
|
||||||
|
|
@ -228,7 +235,7 @@ class CollectionFolderViewModel
|
||||||
request,
|
request,
|
||||||
GetItemsRequestHandler,
|
GetItemsRequestHandler,
|
||||||
viewModelScope,
|
viewModelScope,
|
||||||
useSeriesForPrimary = true,
|
useSeriesForPrimary = useSeriesForPrimary,
|
||||||
)
|
)
|
||||||
newPager
|
newPager
|
||||||
}
|
}
|
||||||
|
|
@ -246,7 +253,7 @@ class CollectionFolderViewModel
|
||||||
request,
|
request,
|
||||||
GetPersonsHandler,
|
GetPersonsHandler,
|
||||||
viewModelScope,
|
viewModelScope,
|
||||||
useSeriesForPrimary = true,
|
useSeriesForPrimary = useSeriesForPrimary,
|
||||||
)
|
)
|
||||||
newPager
|
newPager
|
||||||
}
|
}
|
||||||
|
|
@ -344,10 +351,17 @@ fun CollectionFolderGrid(
|
||||||
showTitle: Boolean = true,
|
showTitle: Boolean = true,
|
||||||
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
|
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
|
||||||
params: CollectionFolderGridParameters = CollectionFolderGridParameters(),
|
params: CollectionFolderGridParameters = CollectionFolderGridParameters(),
|
||||||
|
useSeriesForPrimary: Boolean = true,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
OneTimeLaunchedEffect {
|
OneTimeLaunchedEffect {
|
||||||
viewModel.init(itemId, initialSortAndDirection, recursive, initialFilter)
|
viewModel.init(
|
||||||
|
itemId,
|
||||||
|
initialSortAndDirection,
|
||||||
|
recursive,
|
||||||
|
initialFilter,
|
||||||
|
useSeriesForPrimary,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val sortAndDirection by viewModel.sortAndDirection.observeAsState()
|
val sortAndDirection by viewModel.sortAndDirection.observeAsState()
|
||||||
val filter by viewModel.filter.observeAsState(initialFilter)
|
val filter by viewModel.filter.observeAsState(initialFilter)
|
||||||
|
|
@ -538,4 +552,52 @@ data class CollectionFolderGridParameters(
|
||||||
modifier = mod,
|
modifier = mod,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val POSTER =
|
||||||
|
CollectionFolderGridParameters(
|
||||||
|
columns = 6,
|
||||||
|
spacing = 16.dp,
|
||||||
|
cardContent = { item, onClick, onLongClick, mod ->
|
||||||
|
GridCard(
|
||||||
|
item = item,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
imageContentScale = ContentScale.FillBounds,
|
||||||
|
imageAspectRatio = AspectRatios.TALL,
|
||||||
|
modifier = mod,
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val WIDE =
|
||||||
|
CollectionFolderGridParameters(
|
||||||
|
columns = 4,
|
||||||
|
spacing = 24.dp,
|
||||||
|
cardContent = { item, onClick, onLongClick, mod ->
|
||||||
|
GridCard(
|
||||||
|
item = item,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
imageContentScale = ContentScale.Crop,
|
||||||
|
imageAspectRatio = AspectRatios.WIDE,
|
||||||
|
modifier = mod,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val SQUARE =
|
||||||
|
CollectionFolderGridParameters(
|
||||||
|
columns = 6,
|
||||||
|
spacing = 16.dp,
|
||||||
|
cardContent = { item, onClick, onLongClick, mod ->
|
||||||
|
GridCard(
|
||||||
|
item = item,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
imageContentScale = ContentScale.FillBounds,
|
||||||
|
imageAspectRatio = AspectRatios.SQUARE,
|
||||||
|
modifier = mod,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,11 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.ui.AspectRatios
|
|
||||||
import com.github.damontecres.wholphin.ui.cards.GridCard
|
|
||||||
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
|
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
|
||||||
import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters
|
import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters
|
||||||
import com.github.damontecres.wholphin.ui.data.MovieSortOptions
|
import com.github.damontecres.wholphin.ui.data.MovieSortOptions
|
||||||
|
|
@ -32,21 +29,6 @@ fun CollectionFolderBoxSet(
|
||||||
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
|
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
var showHeader by remember { mutableStateOf(true) }
|
var showHeader by remember { mutableStateOf(true) }
|
||||||
val params =
|
|
||||||
CollectionFolderGridParameters(
|
|
||||||
columns = 6,
|
|
||||||
spacing = 16.dp,
|
|
||||||
cardContent = { item, onClick, onLongClick, mod ->
|
|
||||||
GridCard(
|
|
||||||
item = item,
|
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = onLongClick,
|
|
||||||
imageContentScale = ContentScale.FillBounds,
|
|
||||||
imageAspectRatio = AspectRatios.TALL,
|
|
||||||
modifier = mod,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
CollectionFolderGrid(
|
CollectionFolderGrid(
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) },
|
onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) },
|
||||||
|
|
@ -61,6 +43,6 @@ fun CollectionFolderBoxSet(
|
||||||
positionCallback = { columns, position ->
|
positionCallback = { columns, position ->
|
||||||
showHeader = position < columns
|
showHeader = position < columns
|
||||||
},
|
},
|
||||||
params = params,
|
params = CollectionFolderGridParameters.POSTER,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,10 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
|
||||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.ui.AspectRatios
|
|
||||||
import com.github.damontecres.wholphin.ui.cards.GridCard
|
|
||||||
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
|
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
|
||||||
import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters
|
import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters
|
||||||
import com.github.damontecres.wholphin.ui.data.VideoSortOptions
|
import com.github.damontecres.wholphin.ui.data.VideoSortOptions
|
||||||
|
|
@ -25,7 +21,7 @@ import java.util.UUID
|
||||||
fun CollectionFolderGeneric(
|
fun CollectionFolderGeneric(
|
||||||
preferences: UserPreferences,
|
preferences: UserPreferences,
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
item: BaseItem?,
|
usePosters: Boolean,
|
||||||
recursive: Boolean,
|
recursive: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
filter: GetItemsFilter = GetItemsFilter(),
|
filter: GetItemsFilter = GetItemsFilter(),
|
||||||
|
|
@ -33,20 +29,13 @@ fun CollectionFolderGeneric(
|
||||||
) {
|
) {
|
||||||
var showHeader by remember { mutableStateOf(true) }
|
var showHeader by remember { mutableStateOf(true) }
|
||||||
val params =
|
val params =
|
||||||
CollectionFolderGridParameters(
|
remember(usePosters) {
|
||||||
columns = 4,
|
if (usePosters) {
|
||||||
spacing = 24.dp,
|
CollectionFolderGridParameters.POSTER
|
||||||
cardContent = { item, onClick, onLongClick, mod ->
|
} else {
|
||||||
GridCard(
|
CollectionFolderGridParameters.WIDE
|
||||||
item = item,
|
}
|
||||||
onClick = onClick,
|
}
|
||||||
onLongClick = onLongClick,
|
|
||||||
imageContentScale = ContentScale.Crop,
|
|
||||||
imageAspectRatio = AspectRatios.WIDE,
|
|
||||||
modifier = mod,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
CollectionFolderGrid(
|
CollectionFolderGrid(
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) },
|
onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) },
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ fun CollectionFolderLiveTv(
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
itemId = folders[folderIndex].id,
|
itemId = folders[folderIndex].id,
|
||||||
initialFilter = GetItemsFilter(),
|
initialFilter = GetItemsFilter(),
|
||||||
showTitle = showHeader,
|
showTitle = false,
|
||||||
recursive = false,
|
recursive = false,
|
||||||
sortOptions = VideoSortOptions,
|
sortOptions = VideoSortOptions,
|
||||||
modifier =
|
modifier =
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,11 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.ui.AspectRatios
|
|
||||||
import com.github.damontecres.wholphin.ui.cards.GridCard
|
|
||||||
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
|
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
|
||||||
import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters
|
import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters
|
||||||
import com.github.damontecres.wholphin.ui.data.PlaylistSortOptions
|
import com.github.damontecres.wholphin.ui.data.PlaylistSortOptions
|
||||||
|
|
@ -32,21 +29,6 @@ fun CollectionFolderPlaylist(
|
||||||
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
|
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
var showHeader by remember { mutableStateOf(true) }
|
var showHeader by remember { mutableStateOf(true) }
|
||||||
val params =
|
|
||||||
CollectionFolderGridParameters(
|
|
||||||
columns = 6,
|
|
||||||
spacing = 16.dp,
|
|
||||||
cardContent = { item, onClick, onLongClick, mod ->
|
|
||||||
GridCard(
|
|
||||||
item = item,
|
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = onLongClick,
|
|
||||||
imageContentScale = ContentScale.FillBounds,
|
|
||||||
imageAspectRatio = AspectRatios.SQUARE,
|
|
||||||
modifier = mod,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
CollectionFolderGrid(
|
CollectionFolderGrid(
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) },
|
onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) },
|
||||||
|
|
@ -61,6 +43,6 @@ fun CollectionFolderPlaylist(
|
||||||
positionCallback = { columns, position ->
|
positionCallback = { columns, position ->
|
||||||
showHeader = position < columns
|
showHeader = position < columns
|
||||||
},
|
},
|
||||||
params = params,
|
params = CollectionFolderGridParameters.SQUARE,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.github.damontecres.wholphin.ui.detail
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||||
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
|
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
|
||||||
|
import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters
|
||||||
|
import com.github.damontecres.wholphin.ui.data.MovieSortOptions
|
||||||
|
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CollectionFolderRecordings(
|
||||||
|
preferences: UserPreferences,
|
||||||
|
itemId: UUID,
|
||||||
|
recursive: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
filter: GetItemsFilter = GetItemsFilter(),
|
||||||
|
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
|
||||||
|
) {
|
||||||
|
var showHeader by remember { mutableStateOf(true) }
|
||||||
|
CollectionFolderGrid(
|
||||||
|
preferences = preferences,
|
||||||
|
onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) },
|
||||||
|
itemId = itemId,
|
||||||
|
initialFilter = filter,
|
||||||
|
showTitle = showHeader,
|
||||||
|
recursive = recursive,
|
||||||
|
sortOptions = MovieSortOptions,
|
||||||
|
modifier =
|
||||||
|
modifier
|
||||||
|
.padding(start = 16.dp),
|
||||||
|
positionCallback = { columns, position ->
|
||||||
|
showHeader = position < columns
|
||||||
|
},
|
||||||
|
params = CollectionFolderGridParameters.POSTER,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -28,6 +28,7 @@ import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||||
import com.github.damontecres.wholphin.data.model.GetItemsFilterOverride
|
import com.github.damontecres.wholphin.data.model.GetItemsFilterOverride
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
|
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
|
||||||
|
import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.TabRow
|
import com.github.damontecres.wholphin.ui.components.TabRow
|
||||||
import com.github.damontecres.wholphin.ui.data.EpisodeSortOptions
|
import com.github.damontecres.wholphin.ui.data.EpisodeSortOptions
|
||||||
|
|
@ -163,6 +164,8 @@ fun FavoritesPage(
|
||||||
showTitle = false,
|
showTitle = false,
|
||||||
recursive = true,
|
recursive = true,
|
||||||
sortOptions = EpisodeSortOptions,
|
sortOptions = EpisodeSortOptions,
|
||||||
|
params = CollectionFolderGridParameters.WIDE,
|
||||||
|
useSeriesForPrimary = false,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
|
|
@ -186,6 +189,7 @@ fun FavoritesPage(
|
||||||
showTitle = false,
|
showTitle = false,
|
||||||
recursive = true,
|
recursive = true,
|
||||||
sortOptions = VideoSortOptions,
|
sortOptions = VideoSortOptions,
|
||||||
|
params = CollectionFolderGridParameters.WIDE,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
|
|
@ -209,6 +213,7 @@ fun FavoritesPage(
|
||||||
showTitle = false,
|
showTitle = false,
|
||||||
recursive = true,
|
recursive = true,
|
||||||
sortOptions = VideoSortOptions,
|
sortOptions = VideoSortOptions,
|
||||||
|
params = CollectionFolderGridParameters.SQUARE,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -453,8 +454,16 @@ fun TvGuideGridContent(
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
// Intentionally set background twice
|
||||||
|
// The second is padded so there are gaps between times
|
||||||
|
// The first covers those gaps
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
|
.padding(horizontal = 2.dp)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colorScheme.surface),
|
.background(
|
||||||
|
MaterialTheme.colorScheme.surfaceVariant,
|
||||||
|
shape = RoundedCornerShape(4.dp),
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
val differentDay =
|
val differentDay =
|
||||||
start.toLocalDate() !=
|
start.toLocalDate() !=
|
||||||
|
|
@ -472,7 +481,8 @@ fun TvGuideGridContent(
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = time.toString(),
|
text = time.toString(),
|
||||||
modifier = Modifier.background(MaterialTheme.colorScheme.background),
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
modifier = Modifier.padding(2.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -217,14 +217,12 @@ class HomeViewModel
|
||||||
views.items
|
views.items
|
||||||
.filter {
|
.filter {
|
||||||
it.id in includedIds && it.id !in excluded &&
|
it.id in includedIds && it.id !in excluded &&
|
||||||
it.collectionType in supportedLatestCollectionTypes
|
// Exclude Live TV because a recording folder view will be used instead
|
||||||
|
it.collectionType != CollectionType.LIVETV
|
||||||
}.mapNotNull { view ->
|
}.mapNotNull { view ->
|
||||||
val title =
|
val title =
|
||||||
if (view.collectionType == CollectionType.LIVETV) {
|
|
||||||
context.getString(R.string.recently_recorded)
|
|
||||||
} else {
|
|
||||||
view.name?.let { context.getString(R.string.recently_added_in, it) }
|
view.name?.let { context.getString(R.string.recently_added_in, it) }
|
||||||
} ?: context.getString(R.string.recently_added)
|
?: context.getString(R.string.recently_added)
|
||||||
val viewId =
|
val viewId =
|
||||||
if (view.collectionType == CollectionType.LIVETV) {
|
if (view.collectionType == CollectionType.LIVETV) {
|
||||||
api.liveTvApi
|
api.liveTvApi
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,11 @@ sealed class Destination(
|
||||||
"MediaItem(itemId=$itemId, type=$type, seasonEpisode=$seasonEpisode, collectionType=${item?.data?.collectionType})"
|
"MediaItem(itemId=$itemId, type=$type, seasonEpisode=$seasonEpisode, collectionType=${item?.data?.collectionType})"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Recordings(
|
||||||
|
val itemId: UUID,
|
||||||
|
) : Destination()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Playback(
|
data class Playback(
|
||||||
val itemId: UUID,
|
val itemId: UUID,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import com.github.damontecres.wholphin.ui.detail.CollectionFolderGeneric
|
||||||
import com.github.damontecres.wholphin.ui.detail.CollectionFolderLiveTv
|
import com.github.damontecres.wholphin.ui.detail.CollectionFolderLiveTv
|
||||||
import com.github.damontecres.wholphin.ui.detail.CollectionFolderMovie
|
import com.github.damontecres.wholphin.ui.detail.CollectionFolderMovie
|
||||||
import com.github.damontecres.wholphin.ui.detail.CollectionFolderPlaylist
|
import com.github.damontecres.wholphin.ui.detail.CollectionFolderPlaylist
|
||||||
|
import com.github.damontecres.wholphin.ui.detail.CollectionFolderRecordings
|
||||||
import com.github.damontecres.wholphin.ui.detail.CollectionFolderTv
|
import com.github.damontecres.wholphin.ui.detail.CollectionFolderTv
|
||||||
import com.github.damontecres.wholphin.ui.detail.DebugPage
|
import com.github.damontecres.wholphin.ui.detail.DebugPage
|
||||||
import com.github.damontecres.wholphin.ui.detail.FavoritesPage
|
import com.github.damontecres.wholphin.ui.detail.FavoritesPage
|
||||||
|
|
@ -107,97 +108,40 @@ fun DestinationContent(
|
||||||
modifier,
|
modifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
BaseItemKind.COLLECTION_FOLDER -> {
|
|
||||||
when (destination.item?.data?.collectionType) {
|
|
||||||
CollectionType.TVSHOWS ->
|
|
||||||
CollectionFolderTv(
|
|
||||||
preferences,
|
|
||||||
destination,
|
|
||||||
modifier,
|
|
||||||
)
|
|
||||||
|
|
||||||
CollectionType.MOVIES ->
|
|
||||||
CollectionFolderMovie(
|
|
||||||
preferences,
|
|
||||||
destination,
|
|
||||||
modifier,
|
|
||||||
)
|
|
||||||
|
|
||||||
CollectionType.BOXSETS ->
|
|
||||||
CollectionFolderBoxSet(
|
|
||||||
preferences,
|
|
||||||
destination.itemId,
|
|
||||||
destination.item,
|
|
||||||
false,
|
|
||||||
modifier,
|
|
||||||
)
|
|
||||||
|
|
||||||
else ->
|
|
||||||
CollectionFolderGeneric(
|
|
||||||
preferences,
|
|
||||||
destination.itemId,
|
|
||||||
destination.item,
|
|
||||||
false,
|
|
||||||
modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseItemKind.PLAYLIST ->
|
BaseItemKind.PLAYLIST ->
|
||||||
PlaylistDetails(
|
PlaylistDetails(
|
||||||
destination = destination,
|
destination = destination,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
BaseItemKind.USER_VIEW ->
|
BaseItemKind.COLLECTION_FOLDER ->
|
||||||
when (destination.item?.data?.collectionType) {
|
CollectionFolder(
|
||||||
CollectionType.TVSHOWS ->
|
preferences = preferences,
|
||||||
CollectionFolderTv(
|
destination = destination,
|
||||||
preferences,
|
collectionType = destination.item?.data?.collectionType,
|
||||||
destination,
|
usePostersOverride = null,
|
||||||
modifier,
|
recursiveOverride = null,
|
||||||
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
CollectionType.MOVIES ->
|
|
||||||
CollectionFolderMovie(
|
|
||||||
preferences,
|
|
||||||
destination,
|
|
||||||
modifier,
|
|
||||||
)
|
|
||||||
|
|
||||||
CollectionType.LIVETV ->
|
|
||||||
CollectionFolderLiveTv(
|
|
||||||
preferences,
|
|
||||||
destination,
|
|
||||||
modifier,
|
|
||||||
)
|
|
||||||
|
|
||||||
CollectionType.PLAYLISTS ->
|
|
||||||
CollectionFolderPlaylist(
|
|
||||||
preferences,
|
|
||||||
destination.itemId,
|
|
||||||
destination.item,
|
|
||||||
true,
|
|
||||||
modifier,
|
|
||||||
)
|
|
||||||
|
|
||||||
else ->
|
|
||||||
CollectionFolderGeneric(
|
|
||||||
preferences,
|
|
||||||
destination.itemId,
|
|
||||||
destination.item,
|
|
||||||
true,
|
|
||||||
modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseItemKind.FOLDER ->
|
BaseItemKind.FOLDER ->
|
||||||
CollectionFolderGeneric(
|
CollectionFolder(
|
||||||
preferences,
|
preferences = preferences,
|
||||||
destination.itemId,
|
destination = destination,
|
||||||
destination.item,
|
collectionType = destination.item?.data?.collectionType,
|
||||||
false,
|
usePostersOverride = true,
|
||||||
modifier,
|
recursiveOverride = null,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
|
||||||
|
BaseItemKind.USER_VIEW ->
|
||||||
|
CollectionFolder(
|
||||||
|
preferences = preferences,
|
||||||
|
destination = destination,
|
||||||
|
collectionType = destination.item?.data?.collectionType,
|
||||||
|
usePostersOverride = null,
|
||||||
|
recursiveOverride = true,
|
||||||
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
BaseItemKind.PERSON ->
|
BaseItemKind.PERSON ->
|
||||||
|
|
@ -217,12 +161,20 @@ fun DestinationContent(
|
||||||
CollectionFolderGeneric(
|
CollectionFolderGeneric(
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
itemId = destination.itemId,
|
itemId = destination.itemId,
|
||||||
item = null,
|
|
||||||
filter = destination.filter,
|
filter = destination.filter,
|
||||||
recursive = destination.recursive,
|
recursive = destination.recursive,
|
||||||
|
usePosters = true,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
is Destination.Recordings ->
|
||||||
|
CollectionFolderRecordings(
|
||||||
|
preferences,
|
||||||
|
destination.itemId,
|
||||||
|
false,
|
||||||
|
modifier,
|
||||||
|
)
|
||||||
|
|
||||||
is Destination.ItemGrid ->
|
is Destination.ItemGrid ->
|
||||||
ItemGrid(
|
ItemGrid(
|
||||||
destination,
|
destination,
|
||||||
|
|
@ -248,3 +200,81 @@ fun DestinationContent(
|
||||||
Destination.Debug -> DebugPage(preferences, modifier)
|
Destination.Debug -> DebugPage(preferences, modifier)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CollectionFolder(
|
||||||
|
preferences: UserPreferences,
|
||||||
|
destination: Destination.MediaItem,
|
||||||
|
collectionType: CollectionType?,
|
||||||
|
usePostersOverride: Boolean?,
|
||||||
|
recursiveOverride: Boolean?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
when (collectionType) {
|
||||||
|
CollectionType.TVSHOWS ->
|
||||||
|
CollectionFolderTv(
|
||||||
|
preferences,
|
||||||
|
destination,
|
||||||
|
modifier,
|
||||||
|
)
|
||||||
|
|
||||||
|
CollectionType.MOVIES ->
|
||||||
|
CollectionFolderMovie(
|
||||||
|
preferences,
|
||||||
|
destination,
|
||||||
|
modifier,
|
||||||
|
)
|
||||||
|
|
||||||
|
CollectionType.BOXSETS ->
|
||||||
|
CollectionFolderBoxSet(
|
||||||
|
preferences,
|
||||||
|
destination.itemId,
|
||||||
|
destination.item,
|
||||||
|
false,
|
||||||
|
modifier,
|
||||||
|
)
|
||||||
|
|
||||||
|
CollectionType.PLAYLISTS ->
|
||||||
|
CollectionFolderPlaylist(
|
||||||
|
preferences,
|
||||||
|
destination.itemId,
|
||||||
|
destination.item,
|
||||||
|
true,
|
||||||
|
modifier,
|
||||||
|
)
|
||||||
|
|
||||||
|
CollectionType.LIVETV ->
|
||||||
|
CollectionFolderLiveTv(
|
||||||
|
preferences = preferences,
|
||||||
|
destination = destination,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
|
||||||
|
CollectionType.HOMEVIDEOS,
|
||||||
|
CollectionType.MUSICVIDEOS,
|
||||||
|
CollectionType.MUSIC,
|
||||||
|
CollectionType.BOOKS,
|
||||||
|
CollectionType.PHOTOS,
|
||||||
|
->
|
||||||
|
CollectionFolderGeneric(
|
||||||
|
preferences,
|
||||||
|
destination.itemId,
|
||||||
|
usePosters = usePostersOverride ?: false,
|
||||||
|
recursive = recursiveOverride ?: false,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
|
||||||
|
CollectionType.FOLDERS,
|
||||||
|
CollectionType.TRAILERS,
|
||||||
|
CollectionType.UNKNOWN,
|
||||||
|
null,
|
||||||
|
->
|
||||||
|
CollectionFolderGeneric(
|
||||||
|
preferences,
|
||||||
|
destination.itemId,
|
||||||
|
usePosters = usePostersOverride ?: false,
|
||||||
|
recursive = recursiveOverride ?: false,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ val supportedCollectionTypes =
|
||||||
CollectionType.PLAYLISTS,
|
CollectionType.PLAYLISTS,
|
||||||
CollectionType.BOXSETS,
|
CollectionType.BOXSETS,
|
||||||
CollectionType.LIVETV,
|
CollectionType.LIVETV,
|
||||||
|
CollectionType.MUSICVIDEOS,
|
||||||
|
CollectionType.FOLDERS,
|
||||||
|
null, // Mixed
|
||||||
)
|
)
|
||||||
|
|
||||||
val supportedPlayableTypes =
|
val supportedPlayableTypes =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue