mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Merge branch 'main' into fea/live-tv
This commit is contained in:
commit
8558644c7c
10 changed files with 99 additions and 50 deletions
|
|
@ -173,8 +173,7 @@ sealed interface AppPreference<T> {
|
|||
setter = { prefs, value ->
|
||||
prefs.updateHomePagePreferences { combineContinueNext = value }
|
||||
},
|
||||
summaryOn = R.string.enabled,
|
||||
summaryOff = R.string.disabled,
|
||||
summary = R.string.combine_continue_next_summary,
|
||||
)
|
||||
|
||||
val RewatchNextUp =
|
||||
|
|
|
|||
|
|
@ -21,12 +21,14 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.Card
|
||||
import androidx.tv.material3.CardDefaults
|
||||
import androidx.tv.material3.Icon
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
|
|
@ -58,18 +60,22 @@ fun BannerCard(
|
|||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
interactionSource = interactionSource,
|
||||
colors =
|
||||
CardDefaults.colors(
|
||||
containerColor = Color.Transparent,
|
||||
),
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
.fillMaxSize(),
|
||||
// .background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
) {
|
||||
if (!imageError && imageUrl.isNotNullOrBlank()) {
|
||||
AsyncImage(
|
||||
model = imageUrl,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Fit,
|
||||
contentScale = ContentScale.FillBounds,
|
||||
onError = { imageError = true },
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ fun CollectionFolderMovie(
|
|||
val rememberedTabIndex =
|
||||
remember { preferencesViewModel.getRememberedTab(preferences, destination.itemId, 0) }
|
||||
|
||||
val tabs = listOf("Recommended", "Library", "Genres")
|
||||
val tabs = listOf("Recommended", "Library", "Collections", "Genres")
|
||||
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
|
@ -170,6 +170,29 @@ fun CollectionFolderMovie(
|
|||
)
|
||||
}
|
||||
2 -> {
|
||||
CollectionFolderGrid(
|
||||
preferences = preferences,
|
||||
onClickItem = onClickItem,
|
||||
itemId = destination.itemId,
|
||||
item = destination.item,
|
||||
initialFilter =
|
||||
GetItemsFilter(
|
||||
includeItemTypes = listOf(BaseItemKind.BOX_SET),
|
||||
),
|
||||
showTitle = false,
|
||||
recursive = true,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(start = 16.dp)
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
3 -> {
|
||||
GenreCardGrid(
|
||||
itemId = destination.itemId,
|
||||
modifier =
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
|
|
@ -219,7 +218,7 @@ class SeriesViewModel
|
|||
|
||||
fun loadEpisodes(season: Int) {
|
||||
this@SeriesViewModel.episodes.value = EpisodeList.Loading
|
||||
viewModelScope.async(ExceptionHandler(true)) {
|
||||
viewModelScope.launch(ExceptionHandler(true)) {
|
||||
val episodes =
|
||||
try {
|
||||
loadEpisodesInternal(season)
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ fun SeriesOverview(
|
|||
val firstItemFocusRequester = remember { FocusRequester() }
|
||||
val episodeRowFocusRequester = remember { FocusRequester() }
|
||||
|
||||
var initialLoadDone by rememberSaveable { mutableStateOf(false) }
|
||||
OneTimeLaunchedEffect {
|
||||
Timber.v("SeriesDetailParent: itemId=${destination.itemId}, initialSeasonEpisode=$initialSeasonEpisode")
|
||||
viewModel.init(
|
||||
|
|
@ -81,7 +82,9 @@ fun SeriesOverview(
|
|||
initialSeasonEpisode?.season,
|
||||
initialSeasonEpisode?.episode,
|
||||
)
|
||||
initialLoadDone = true
|
||||
}
|
||||
|
||||
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
||||
|
||||
val series by viewModel.item.observeAsState(null)
|
||||
|
|
@ -108,6 +111,13 @@ fun SeriesOverview(
|
|||
),
|
||||
)
|
||||
}
|
||||
if (initialLoadDone) {
|
||||
LaunchedEffect(Unit) {
|
||||
seasons.indexToNumber[position.seasonTabIndex]?.let {
|
||||
viewModel.loadEpisodes(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
|
||||
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||
|
|
|
|||
|
|
@ -225,21 +225,21 @@ fun HomePageContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(8.dp)
|
||||
.size(24.dp)
|
||||
.align(Alignment.BottomEnd),
|
||||
) {
|
||||
when (loadingState) {
|
||||
LoadingState.Pending,
|
||||
LoadingState.Loading,
|
||||
->
|
||||
when (loadingState) {
|
||||
LoadingState.Pending,
|
||||
LoadingState.Loading,
|
||||
->
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(16.dp)
|
||||
.size(40.dp)
|
||||
.align(Alignment.TopEnd),
|
||||
) {
|
||||
CircularProgress(Modifier.fillMaxSize())
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import org.jellyfin.sdk.api.client.extensions.liveTvApi
|
|||
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||
import org.jellyfin.sdk.api.client.extensions.userViewsApi
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
import org.jellyfin.sdk.model.api.UserDto
|
||||
import org.jellyfin.sdk.model.api.request.GetLatestMediaRequest
|
||||
|
|
@ -65,32 +66,28 @@ class HomeViewModel
|
|||
.filter { it is ServerNavDrawerItem }
|
||||
.map { (it as ServerNavDrawerItem).itemId }
|
||||
// TODO data is fetched all together which may be slow for large servers
|
||||
val resume = getResume(userDto.id, limit)
|
||||
val nextUp = getNextUp(userDto.id, limit, prefs.enableRewatchingNextUp)
|
||||
val resume = getResume(userDto.id, limit, !prefs.combineContinueNext)
|
||||
val nextUp =
|
||||
getNextUp(
|
||||
userDto.id,
|
||||
limit,
|
||||
prefs.enableRewatchingNextUp,
|
||||
prefs.combineContinueNext,
|
||||
)
|
||||
val latest = getLatest(userDto, limit, includedIds)
|
||||
|
||||
val homeRows =
|
||||
if (prefs.combineContinueNext) {
|
||||
listOf(
|
||||
HomeRow(
|
||||
section = HomeSection.NEXT_UP,
|
||||
items = resume + nextUp,
|
||||
),
|
||||
*latest.toTypedArray(),
|
||||
)
|
||||
} else {
|
||||
listOf(
|
||||
HomeRow(
|
||||
section = HomeSection.RESUME,
|
||||
items = resume,
|
||||
),
|
||||
HomeRow(
|
||||
section = HomeSection.NEXT_UP,
|
||||
items = nextUp,
|
||||
),
|
||||
*latest.toTypedArray(),
|
||||
)
|
||||
}
|
||||
listOf(
|
||||
HomeRow(
|
||||
section = HomeSection.RESUME,
|
||||
items = resume,
|
||||
),
|
||||
HomeRow(
|
||||
section = HomeSection.NEXT_UP,
|
||||
items = nextUp,
|
||||
),
|
||||
*latest.toTypedArray(),
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
this@HomeViewModel.homeRows.value = homeRows
|
||||
loadingState.value = LoadingState.Success
|
||||
|
|
@ -102,13 +99,23 @@ class HomeViewModel
|
|||
private suspend fun getResume(
|
||||
userId: UUID,
|
||||
limit: Int,
|
||||
includeEpisodes: Boolean,
|
||||
): List<BaseItem> {
|
||||
val request =
|
||||
GetResumeItemsRequest(
|
||||
userId = userId,
|
||||
fields = SlimItemFields,
|
||||
limit = limit,
|
||||
includeItemTypes = supportItemKinds,
|
||||
includeItemTypes =
|
||||
if (includeEpisodes) {
|
||||
supportItemKinds
|
||||
} else {
|
||||
supportItemKinds
|
||||
.toMutableSet()
|
||||
.apply {
|
||||
remove(BaseItemKind.EPISODE)
|
||||
}
|
||||
},
|
||||
)
|
||||
val items =
|
||||
api.itemsApi
|
||||
|
|
@ -123,6 +130,7 @@ class HomeViewModel
|
|||
userId: UUID,
|
||||
limit: Int,
|
||||
enableRewatching: Boolean,
|
||||
enableResumable: Boolean,
|
||||
): List<BaseItem> {
|
||||
val request =
|
||||
GetNextUpRequest(
|
||||
|
|
@ -131,7 +139,7 @@ class HomeViewModel
|
|||
imageTypeLimit = 1,
|
||||
parentId = null,
|
||||
limit = limit,
|
||||
enableResumable = false,
|
||||
enableResumable = enableResumable,
|
||||
enableUserData = true,
|
||||
enableRewatching = enableRewatching,
|
||||
)
|
||||
|
|
@ -140,7 +148,7 @@ class HomeViewModel
|
|||
.getNextUp(request)
|
||||
.content
|
||||
.items
|
||||
.map { BaseItem.Companion.from(it, api, true) }
|
||||
.map { BaseItem.from(it, api, true) }
|
||||
return nextUp
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -288,7 +288,10 @@ fun NavDrawer(
|
|||
library = it,
|
||||
selected = selectedIndex == index,
|
||||
moreExpanded = showMore,
|
||||
onClick = { onClick.invoke(index, it) },
|
||||
onClick = {
|
||||
onClick.invoke(index, it)
|
||||
if (it !is NavDrawerItem.More) setShowMore(false)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.ifElse(
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ fun PlaybackPage(
|
|||
is LoadingState.Error -> ErrorMessage(st, modifier)
|
||||
LoadingState.Pending,
|
||||
LoadingState.Loading,
|
||||
-> LoadingPage(modifier)
|
||||
-> LoadingPage(modifier.background(Color.Black))
|
||||
|
||||
LoadingState.Success -> {
|
||||
val prefs = preferences.appPreferences.playbackPreferences
|
||||
|
|
@ -201,7 +201,7 @@ fun PlaybackPage(
|
|||
|
||||
val showSegment =
|
||||
!segmentCancelled && currentSegment != null &&
|
||||
!controllerViewState.controlsVisible && skipIndicatorDuration == 0L
|
||||
nextUp == null && !controllerViewState.controlsVisible && skipIndicatorDuration == 0L
|
||||
BackHandler(showSegment) {
|
||||
segmentCancelled = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,5 +91,6 @@
|
|||
<string name="nav_drawer_pins">Customize Navigation Drawer Items</string>
|
||||
<string name="profile_specific_settings">User Profile Settings</string>
|
||||
<string name="nav_drawer_pins_summary">Choose the default items to show, others will be hidden</string>
|
||||
<string name="combine_continue_next_summary">Applies to TV Series only</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue