mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Barebones sorting
This commit is contained in:
parent
b0b791f325
commit
25859a05f7
3 changed files with 253 additions and 91 deletions
|
|
@ -0,0 +1,117 @@
|
||||||
|
package com.github.damontecres.dolphin.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
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.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.tv.material3.Button
|
||||||
|
import androidx.tv.material3.MaterialTheme
|
||||||
|
import androidx.tv.material3.Text
|
||||||
|
import com.github.damontecres.dolphin.R
|
||||||
|
import com.github.damontecres.dolphin.ui.FontAwesome
|
||||||
|
import com.github.damontecres.dolphin.ui.data.SortAndDirection
|
||||||
|
import com.github.damontecres.dolphin.ui.data.flip
|
||||||
|
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||||
|
import org.jellyfin.sdk.model.api.SortOrder
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SortByButton(
|
||||||
|
sortOptions: List<ItemSortBy>,
|
||||||
|
current: SortAndDirection,
|
||||||
|
onSortChange: (SortAndDirection) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val currentSort = current.sort
|
||||||
|
val currentDirection = current.direction
|
||||||
|
var sortByDropDown by remember { mutableStateOf(false) }
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Box(modifier = modifier) {
|
||||||
|
Button(
|
||||||
|
onClick = { sortByDropDown = true },
|
||||||
|
onLongClick = {
|
||||||
|
onSortChange.invoke(current.flip())
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text =
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(SpanStyle(fontFamily = FontAwesome)) {
|
||||||
|
append(
|
||||||
|
stringResource(
|
||||||
|
if (currentDirection == SortOrder.ASCENDING) {
|
||||||
|
R.string.fa_caret_up
|
||||||
|
} else {
|
||||||
|
R.string.fa_caret_down
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
append(" ")
|
||||||
|
append(currentSort.name) // TODO names
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO switch to dialog?
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = sortByDropDown,
|
||||||
|
onDismissRequest = { sortByDropDown = false },
|
||||||
|
) {
|
||||||
|
sortOptions
|
||||||
|
.sortedBy { it.name }
|
||||||
|
.forEach { sortOption ->
|
||||||
|
DropdownMenuItem(
|
||||||
|
leadingIcon = {
|
||||||
|
if (sortOption == currentSort) {
|
||||||
|
if (currentDirection == SortOrder.ASCENDING) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.fa_caret_up),
|
||||||
|
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
fontFamily = FontAwesome,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.fa_caret_down),
|
||||||
|
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
fontFamily = FontAwesome,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = sortOption.name,
|
||||||
|
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
sortByDropDown = false
|
||||||
|
val newDirection =
|
||||||
|
if (currentSort == sortOption) {
|
||||||
|
currentDirection.flip()
|
||||||
|
} else {
|
||||||
|
currentDirection
|
||||||
|
}
|
||||||
|
onSortChange.invoke(
|
||||||
|
SortAndDirection(
|
||||||
|
sortOption,
|
||||||
|
newDirection,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.github.damontecres.dolphin.ui.data
|
||||||
|
|
||||||
|
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||||
|
import org.jellyfin.sdk.model.api.SortOrder
|
||||||
|
|
||||||
|
data class SortAndDirection(
|
||||||
|
val sort: ItemSortBy,
|
||||||
|
val direction: SortOrder,
|
||||||
|
) {
|
||||||
|
fun flip() = copy(direction = direction.flip())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun SortOrder.flip() = if (this == SortOrder.ASCENDING) SortOrder.DESCENDING else SortOrder.ASCENDING
|
||||||
|
|
||||||
|
val MovieSortOptions =
|
||||||
|
listOf(
|
||||||
|
ItemSortBy.SORT_NAME,
|
||||||
|
ItemSortBy.PRODUCTION_YEAR,
|
||||||
|
ItemSortBy.COMMUNITY_RATING,
|
||||||
|
ItemSortBy.DATE_CREATED,
|
||||||
|
ItemSortBy.DATE_PLAYED,
|
||||||
|
ItemSortBy.PLAY_COUNT,
|
||||||
|
ItemSortBy.STUDIO,
|
||||||
|
ItemSortBy.OFFICIAL_RATING,
|
||||||
|
ItemSortBy.RANDOM,
|
||||||
|
)
|
||||||
|
|
||||||
|
val SeriesSortOptions =
|
||||||
|
listOf(
|
||||||
|
ItemSortBy.SORT_NAME,
|
||||||
|
ItemSortBy.PREMIERE_DATE,
|
||||||
|
ItemSortBy.COMMUNITY_RATING,
|
||||||
|
ItemSortBy.DATE_CREATED,
|
||||||
|
ItemSortBy.DATE_LAST_CONTENT_ADDED,
|
||||||
|
ItemSortBy.DATE_PLAYED,
|
||||||
|
ItemSortBy.STUDIO,
|
||||||
|
ItemSortBy.OFFICIAL_RATING,
|
||||||
|
ItemSortBy.RANDOM,
|
||||||
|
)
|
||||||
|
|
||||||
|
val VideoSortOptions =
|
||||||
|
listOf(
|
||||||
|
ItemSortBy.SORT_NAME,
|
||||||
|
ItemSortBy.DATE_CREATED,
|
||||||
|
ItemSortBy.DATE_PLAYED,
|
||||||
|
ItemSortBy.RANDOM,
|
||||||
|
)
|
||||||
|
|
@ -21,9 +21,15 @@ import androidx.tv.material3.Text
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
import com.github.damontecres.dolphin.data.model.Library
|
import com.github.damontecres.dolphin.data.model.Library
|
||||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
|
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
||||||
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
|
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
|
||||||
import com.github.damontecres.dolphin.ui.components.ErrorMessage
|
import com.github.damontecres.dolphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.dolphin.ui.components.LoadingPage
|
import com.github.damontecres.dolphin.ui.components.LoadingPage
|
||||||
|
import com.github.damontecres.dolphin.ui.components.SortByButton
|
||||||
|
import com.github.damontecres.dolphin.ui.data.MovieSortOptions
|
||||||
|
import com.github.damontecres.dolphin.ui.data.SeriesSortOptions
|
||||||
|
import com.github.damontecres.dolphin.ui.data.SortAndDirection
|
||||||
|
import com.github.damontecres.dolphin.ui.data.VideoSortOptions
|
||||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||||
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
||||||
|
|
@ -40,7 +46,6 @@ import org.jellyfin.sdk.api.client.ApiClient
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.CollectionType
|
import org.jellyfin.sdk.model.api.CollectionType
|
||||||
import org.jellyfin.sdk.model.api.ImageType
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
import org.jellyfin.sdk.model.api.ItemFields
|
|
||||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||||
import org.jellyfin.sdk.model.api.SortOrder
|
import org.jellyfin.sdk.model.api.SortOrder
|
||||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||||
|
|
@ -54,11 +59,13 @@ class CollectionFolderViewModel
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
) : ItemViewModel<Library>(api) {
|
) : ItemViewModel<Library>(api) {
|
||||||
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||||
val pager = MutableLiveData<ApiRequestPager<GetItemsRequest>?>()
|
val pager = MutableLiveData<List<BaseItem?>>(listOf())
|
||||||
|
val sortAndDirection = MutableLiveData<SortAndDirection>()
|
||||||
|
|
||||||
fun init(
|
fun init(
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
potential: BaseItem?,
|
potential: BaseItem?,
|
||||||
|
sortAndDirection: SortAndDirection,
|
||||||
): Job =
|
): Job =
|
||||||
viewModelScope.launch(
|
viewModelScope.launch(
|
||||||
LoadingExceptionHandler(
|
LoadingExceptionHandler(
|
||||||
|
|
@ -67,13 +74,17 @@ class CollectionFolderViewModel
|
||||||
) + Dispatchers.IO,
|
) + Dispatchers.IO,
|
||||||
) {
|
) {
|
||||||
fetchItem(itemId, potential)
|
fetchItem(itemId, potential)
|
||||||
setup()
|
loadResults(sortAndDirection)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun setup() =
|
fun loadResults(sortAndDirection: SortAndDirection) {
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
if (!pager.isInitialized) {
|
|
||||||
item.value?.let { item ->
|
item.value?.let { item ->
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
pager.value = listOf()
|
||||||
|
loading.value = LoadingState.Loading
|
||||||
|
this@CollectionFolderViewModel.sortAndDirection.value = sortAndDirection
|
||||||
|
}
|
||||||
val includeItemTypes =
|
val includeItemTypes =
|
||||||
when (item.data.collectionType) {
|
when (item.data.collectionType) {
|
||||||
CollectionType.UNKNOWN -> TODO()
|
CollectionType.UNKNOWN -> TODO()
|
||||||
|
|
@ -89,7 +100,7 @@ class CollectionFolderViewModel
|
||||||
CollectionType.LIVETV -> TODO()
|
CollectionType.LIVETV -> TODO()
|
||||||
CollectionType.PLAYLISTS -> TODO()
|
CollectionType.PLAYLISTS -> TODO()
|
||||||
CollectionType.FOLDERS -> TODO()
|
CollectionType.FOLDERS -> TODO()
|
||||||
null -> TODO()
|
null -> listOf()
|
||||||
}
|
}
|
||||||
val request =
|
val request =
|
||||||
GetItemsRequest(
|
GetItemsRequest(
|
||||||
|
|
@ -99,9 +110,9 @@ class CollectionFolderViewModel
|
||||||
// recursive = true,
|
// recursive = true,
|
||||||
enableImageTypes = listOf(ImageType.PRIMARY, ImageType.THUMB),
|
enableImageTypes = listOf(ImageType.PRIMARY, ImageType.THUMB),
|
||||||
includeItemTypes = includeItemTypes,
|
includeItemTypes = includeItemTypes,
|
||||||
sortBy = listOf(ItemSortBy.SORT_NAME),
|
sortBy = listOf(sortAndDirection.sort),
|
||||||
sortOrder = listOf(SortOrder.ASCENDING),
|
sortOrder = listOf(sortAndDirection.direction),
|
||||||
fields = listOf(ItemFields.PRIMARY_IMAGE_ASPECT_RATIO),
|
fields = DefaultItemFields,
|
||||||
)
|
)
|
||||||
val newPager =
|
val newPager =
|
||||||
ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope)
|
ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope)
|
||||||
|
|
@ -123,10 +134,16 @@ fun CollectionFolderDetails(
|
||||||
destination: Destination.MediaItem,
|
destination: Destination.MediaItem,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: CollectionFolderViewModel = hiltViewModel(),
|
viewModel: CollectionFolderViewModel = hiltViewModel(),
|
||||||
|
initialSortAndDirection: SortAndDirection =
|
||||||
|
SortAndDirection(
|
||||||
|
ItemSortBy.SORT_NAME,
|
||||||
|
SortOrder.ASCENDING,
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
OneTimeLaunchedEffect {
|
OneTimeLaunchedEffect {
|
||||||
viewModel.init(destination.itemId, destination.item)
|
viewModel.init(destination.itemId, destination.item, initialSortAndDirection)
|
||||||
}
|
}
|
||||||
|
val sortAndDirection by viewModel.sortAndDirection.observeAsState(initialSortAndDirection)
|
||||||
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
||||||
val item by viewModel.item.observeAsState()
|
val item by viewModel.item.observeAsState()
|
||||||
val library by viewModel.model.observeAsState()
|
val library by viewModel.model.observeAsState()
|
||||||
|
|
@ -137,67 +154,42 @@ fun CollectionFolderDetails(
|
||||||
LoadingState.Loading -> LoadingPage()
|
LoadingState.Loading -> LoadingPage()
|
||||||
LoadingState.Success -> {
|
LoadingState.Success -> {
|
||||||
pager?.let { pager ->
|
pager?.let { pager ->
|
||||||
when (library!!.collectionType) {
|
CollectionDetails(
|
||||||
CollectionType.UNKNOWN -> TODO()
|
|
||||||
|
|
||||||
// TODO?
|
|
||||||
CollectionType.MOVIES ->
|
|
||||||
TVShowCollectionDetails(
|
|
||||||
preferences,
|
preferences,
|
||||||
navigationManager,
|
navigationManager,
|
||||||
library!!,
|
library!!,
|
||||||
item!!,
|
item!!,
|
||||||
pager,
|
pager,
|
||||||
modifier,
|
sortAndDirection = sortAndDirection,
|
||||||
|
modifier = modifier,
|
||||||
|
onSortChange = {
|
||||||
|
viewModel.loadResults(it)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
CollectionType.TVSHOWS -> {
|
|
||||||
TVShowCollectionDetails(
|
|
||||||
preferences,
|
|
||||||
navigationManager,
|
|
||||||
library!!,
|
|
||||||
item!!,
|
|
||||||
pager,
|
|
||||||
modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO?
|
|
||||||
CollectionType.HOMEVIDEOS ->
|
|
||||||
TVShowCollectionDetails(
|
|
||||||
preferences,
|
|
||||||
navigationManager,
|
|
||||||
library!!,
|
|
||||||
item!!,
|
|
||||||
pager,
|
|
||||||
modifier,
|
|
||||||
)
|
|
||||||
|
|
||||||
CollectionType.MUSIC -> TODO()
|
|
||||||
CollectionType.MUSICVIDEOS -> TODO()
|
|
||||||
CollectionType.TRAILERS -> TODO()
|
|
||||||
CollectionType.BOXSETS -> TODO()
|
|
||||||
CollectionType.BOOKS -> TODO()
|
|
||||||
CollectionType.PHOTOS -> TODO()
|
|
||||||
CollectionType.LIVETV -> TODO()
|
|
||||||
CollectionType.PLAYLISTS -> TODO()
|
|
||||||
CollectionType.FOLDERS -> TODO()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TVShowCollectionDetails(
|
fun CollectionDetails(
|
||||||
preferences: UserPreferences,
|
preferences: UserPreferences,
|
||||||
navigationManager: NavigationManager,
|
navigationManager: NavigationManager,
|
||||||
library: Library,
|
library: Library,
|
||||||
item: BaseItem,
|
item: BaseItem,
|
||||||
pager: List<BaseItem?>,
|
pager: List<BaseItem?>,
|
||||||
|
sortAndDirection: SortAndDirection,
|
||||||
|
onSortChange: (SortAndDirection) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val title = library.name ?: item.data.name ?: item.data.collectionType?.name ?: "Collection"
|
val title = library.name ?: item.data.name ?: item.data.collectionType?.name ?: "Collection"
|
||||||
|
val sortOptions =
|
||||||
|
when (item.data.collectionType) {
|
||||||
|
CollectionType.MOVIES -> MovieSortOptions
|
||||||
|
CollectionType.TVSHOWS -> SeriesSortOptions
|
||||||
|
CollectionType.HOMEVIDEOS -> VideoSortOptions
|
||||||
|
else -> listOf(ItemSortBy.SORT_NAME, ItemSortBy.DATE_CREATED, ItemSortBy.RANDOM)
|
||||||
|
}
|
||||||
|
|
||||||
val gridFocusRequester = remember { FocusRequester() }
|
val gridFocusRequester = remember { FocusRequester() }
|
||||||
LaunchedEffect(Unit) { gridFocusRequester.tryRequestFocus() }
|
LaunchedEffect(Unit) { gridFocusRequester.tryRequestFocus() }
|
||||||
|
|
@ -212,6 +204,12 @@ fun TVShowCollectionDetails(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
SortByButton(
|
||||||
|
sortOptions = sortOptions,
|
||||||
|
current = sortAndDirection,
|
||||||
|
onSortChange = onSortChange,
|
||||||
|
modifier = Modifier,
|
||||||
|
)
|
||||||
CardGrid(
|
CardGrid(
|
||||||
pager = pager,
|
pager = pager,
|
||||||
itemOnClick = {
|
itemOnClick = {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue