mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Fix several UI focus issues (#81)
Remembers the page position & focused item on both Movie & Series detail pages Only loads similar movies & series once so that if you click on one and go back, it doesn't refresh the entire list Remember the tab on the Favorite page (if remember tabs is enabled in settings)
This commit is contained in:
parent
c4df751918
commit
f1f6b729aa
8 changed files with 241 additions and 153 deletions
|
|
@ -27,7 +27,6 @@ import org.jellyfin.sdk.api.okhttp.OkHttpFactory
|
|||
import org.jellyfin.sdk.createJellyfin
|
||||
import org.jellyfin.sdk.model.ClientInfo
|
||||
import org.jellyfin.sdk.model.DeviceInfo
|
||||
import java.util.UUID
|
||||
import javax.inject.Qualifier
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -144,11 +143,11 @@ object AppModule {
|
|||
appPreference: DataStore<AppPreferences>,
|
||||
@IoCoroutineScope scope: CoroutineScope,
|
||||
) = object : RememberTabManager {
|
||||
fun key(itemId: UUID) = "${serverRepository.currentServer?.id}_${serverRepository.currentUser?.id}_$itemId"
|
||||
fun key(itemId: String) = "${serverRepository.currentServer?.id}_${serverRepository.currentUser?.id}_$itemId"
|
||||
|
||||
override fun getRememberedTab(
|
||||
preferences: UserPreferences,
|
||||
itemId: UUID,
|
||||
itemId: String,
|
||||
defaultTab: Int,
|
||||
): Int {
|
||||
if (preferences.appPreferences.interfacePreferences.rememberSelectedTab) {
|
||||
|
|
@ -161,7 +160,7 @@ object AppModule {
|
|||
|
||||
override fun saveRememberedTab(
|
||||
preferences: UserPreferences,
|
||||
itemId: UUID,
|
||||
itemId: String,
|
||||
tabIndex: Int,
|
||||
) {
|
||||
if (preferences.appPreferences.interfacePreferences.rememberSelectedTab) {
|
||||
|
|
|
|||
|
|
@ -321,6 +321,15 @@ fun rememberPosition(initialPosition: RowColumn = RowColumn(-1, -1)) =
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient way to [rememberSaveable] a [RowColumn]
|
||||
*/
|
||||
@Composable
|
||||
fun rememberPosition(
|
||||
row: Int,
|
||||
column: Int,
|
||||
) = rememberPosition(RowColumn(row, column))
|
||||
|
||||
/**
|
||||
* Convenient way to [rememberSaveable] a Int
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ 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.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
|
|
@ -46,29 +46,31 @@ fun FavoritesPage(
|
|||
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
|
||||
) {
|
||||
val uiPrefs = preferences.appPreferences.interfacePreferences
|
||||
val rememberedTabIndex = 0
|
||||
// TODO remember tab
|
||||
// if (uiPrefs.rememberSelectedTab) {
|
||||
// uiPrefs.getRememberedTabsOrDefault(destination.itemId.toString(), 0)
|
||||
// } else {
|
||||
// 0
|
||||
// }
|
||||
val rememberedTabIndex =
|
||||
remember {
|
||||
preferencesViewModel.getRememberedTab(
|
||||
preferences,
|
||||
NavDrawerItem.Favorites.id,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
val tabs = listOf("Movies", "TV Shows", "Episodes")
|
||||
val tabFocusRequesters = remember { List(tabs.size) { FocusRequester() } }
|
||||
var focusTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
val firstTabFocusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() }
|
||||
|
||||
// if (uiPrefs.rememberSelectedTab) {
|
||||
// LaunchedEffect(selectedTabIndex) {
|
||||
// preferencesViewModel.preferenceDataStore.updateData {
|
||||
// preferences.appPreferences.rememberTab(destination.itemId, selectedTabIndex)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
LaunchedEffect(Unit) {
|
||||
tabFocusRequesters.getOrNull(selectedTabIndex)?.tryRequestFocus()
|
||||
}
|
||||
LaunchedEffect(selectedTabIndex) {
|
||||
preferencesViewModel.saveRememberedTab(
|
||||
preferences,
|
||||
NavDrawerItem.Favorites.id,
|
||||
selectedTabIndex,
|
||||
)
|
||||
}
|
||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||
|
||||
val onClickItem = { item: BaseItem ->
|
||||
|
|
@ -89,7 +91,7 @@ fun FavoritesPage(
|
|||
modifier =
|
||||
Modifier
|
||||
.padding(start = 32.dp, top = 16.dp, bottom = 16.dp)
|
||||
.focusRestorer(firstTabFocusRequester)
|
||||
.focusRestorer(tabFocusRequesters[0])
|
||||
.onFocusChanged {
|
||||
if (!it.isFocused) {
|
||||
focusTabIndex = selectedTabIndex
|
||||
|
|
@ -126,10 +128,7 @@ fun FavoritesPage(
|
|||
modifier =
|
||||
Modifier
|
||||
.padding(8.dp)
|
||||
.ifElse(
|
||||
index == selectedTabIndex,
|
||||
Modifier.focusRequester(firstTabFocusRequester),
|
||||
),
|
||||
.focusRequester(tabFocusRequesters[index]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
|||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItems
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.rememberInt
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
|
|
@ -243,6 +244,12 @@ fun MovieDetails(
|
|||
}
|
||||
}
|
||||
|
||||
private const val HEADER_ROW = 0
|
||||
private const val PEOPLE_ROW = HEADER_ROW + 1
|
||||
private const val TRAILER_ROW = PEOPLE_ROW + 1
|
||||
private const val CHAPTER_ROW = TRAILER_ROW + 1
|
||||
private const val SIMILAR_ROW = CHAPTER_ROW + 1
|
||||
|
||||
@Composable
|
||||
fun MovieDetailsContent(
|
||||
preferences: UserPreferences,
|
||||
|
|
@ -262,15 +269,15 @@ fun MovieDetailsContent(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
var position by rememberInt(0)
|
||||
val focusRequesters = remember { List(SIMILAR_ROW + 1) { FocusRequester() } }
|
||||
val dto = movie.data
|
||||
val backdropImageUrl = movie.backdropImageUrl
|
||||
val resumePosition = dto.userData?.playbackPositionTicks?.ticks ?: Duration.ZERO
|
||||
|
||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||
LaunchedEffect(Unit) {
|
||||
// bringIntoViewRequester.bringIntoView()
|
||||
focusRequester.tryRequestFocus()
|
||||
focusRequesters.getOrNull(position)?.tryRequestFocus()
|
||||
}
|
||||
Box(modifier = modifier) {
|
||||
if (backdropImageUrl.isNotNullOrBlank()) {
|
||||
|
|
@ -329,18 +336,22 @@ fun MovieDetailsContent(
|
|||
resumePosition = resumePosition,
|
||||
watched = dto.userData?.played ?: false,
|
||||
favorite = dto.userData?.isFavorite ?: false,
|
||||
playOnClick = playOnClick,
|
||||
playOnClick = {
|
||||
position = HEADER_ROW
|
||||
playOnClick.invoke(it)
|
||||
},
|
||||
moreOnClick = moreOnClick,
|
||||
watchOnClick = watchOnClick,
|
||||
favoriteOnClick = favoriteOnClick,
|
||||
buttonOnFocusChanged = {
|
||||
if (it.isFocused) {
|
||||
position = HEADER_ROW
|
||||
scope.launch(ExceptionHandler()) {
|
||||
bringIntoViewRequester.bringIntoView()
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
modifier = Modifier.focusRequester(focusRequesters[HEADER_ROW]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -348,9 +359,14 @@ fun MovieDetailsContent(
|
|||
item {
|
||||
PersonRow(
|
||||
people = people,
|
||||
onClick = {},
|
||||
onClick = {
|
||||
position = PEOPLE_ROW
|
||||
},
|
||||
onLongClick = {},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequesters[PEOPLE_ROW]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -358,8 +374,14 @@ fun MovieDetailsContent(
|
|||
item {
|
||||
TrailerRow(
|
||||
trailers = trailers,
|
||||
onClickTrailer = trailerOnClick,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClickTrailer = {
|
||||
position = TRAILER_ROW
|
||||
trailerOnClick.invoke(it)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequesters[TRAILER_ROW]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -367,9 +389,15 @@ fun MovieDetailsContent(
|
|||
item {
|
||||
ChapterRow(
|
||||
chapters = chapters,
|
||||
onClick = { playOnClick.invoke(it.position) },
|
||||
onClick = {
|
||||
position = CHAPTER_ROW
|
||||
playOnClick.invoke(it.position)
|
||||
},
|
||||
onLongClick = {},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequesters[CHAPTER_ROW]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -378,7 +406,10 @@ fun MovieDetailsContent(
|
|||
ItemRow(
|
||||
title = stringResource(R.string.more_like_this),
|
||||
items = similar,
|
||||
onClickItem = onClickItem,
|
||||
onClickItem = {
|
||||
position = SIMILAR_ROW
|
||||
onClickItem.invoke(it)
|
||||
},
|
||||
onLongClickItem = {},
|
||||
cardContent = { index, item, mod, onClick, onLongClick ->
|
||||
SeasonCard(
|
||||
|
|
@ -391,7 +422,10 @@ fun MovieDetailsContent(
|
|||
imageWidth = Dp.Unspecified,
|
||||
)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequesters[SIMILAR_ROW]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ import com.github.damontecres.wholphin.ui.letNotEmpty
|
|||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -47,7 +49,7 @@ class MovieViewModel
|
|||
val trailers = MutableLiveData<List<Trailer>>(listOf())
|
||||
val people = MutableLiveData<List<Person>>(listOf())
|
||||
val chapters = MutableLiveData<List<Chapter>>(listOf())
|
||||
val similar = MutableLiveData<List<BaseItem>>(listOf())
|
||||
val similar = MutableLiveData<List<BaseItem>>()
|
||||
val chosenStreams = MutableLiveData<ChosenStreams?>(null)
|
||||
|
||||
override fun init(
|
||||
|
|
@ -55,14 +57,21 @@ class MovieViewModel
|
|||
potential: BaseItem?,
|
||||
): Job? {
|
||||
this.itemId = itemId
|
||||
return viewModelScope.launch(ExceptionHandler()) {
|
||||
super.init(itemId, potential)?.join()
|
||||
return viewModelScope.launch(
|
||||
Dispatchers.IO +
|
||||
LoadingExceptionHandler(
|
||||
loading,
|
||||
"Error fetching movie",
|
||||
),
|
||||
) {
|
||||
fetchAndSetItem(itemId)
|
||||
item.value?.let { item ->
|
||||
val result = itemPlaybackRepository.getSelectedTracks(item.id, item)
|
||||
withContext(Dispatchers.Main) {
|
||||
chosenStreams.value = result
|
||||
loading.value = LoadingState.Success
|
||||
}
|
||||
viewModelScope.launchIO {
|
||||
val result = itemPlaybackRepository.getSelectedTracks(item.id, item)
|
||||
withContext(Dispatchers.Main) {
|
||||
chosenStreams.value = result
|
||||
}
|
||||
val remoteTrailers =
|
||||
item.data.remoteTrailers
|
||||
?.mapNotNull { t ->
|
||||
|
|
@ -98,19 +107,20 @@ class MovieViewModel
|
|||
}.orEmpty()
|
||||
chapters.value = Chapter.Companion.fromDto(item.data, api)
|
||||
}
|
||||
|
||||
val similar =
|
||||
api.libraryApi
|
||||
.getSimilarItems(
|
||||
GetSimilarItemsRequest(
|
||||
userId = serverRepository.currentUser?.id,
|
||||
itemId = itemId,
|
||||
fields = SlimItemFields,
|
||||
limit = 25,
|
||||
),
|
||||
).content.items
|
||||
.map { BaseItem.Companion.from(it, api) }
|
||||
this@MovieViewModel.similar.setValueOnMain(similar)
|
||||
if (!similar.isInitialized) {
|
||||
val similar =
|
||||
api.libraryApi
|
||||
.getSimilarItems(
|
||||
GetSimilarItemsRequest(
|
||||
userId = serverRepository.currentUser?.id,
|
||||
itemId = itemId,
|
||||
fields = SlimItemFields,
|
||||
limit = 25,
|
||||
),
|
||||
).content.items
|
||||
.map { BaseItem.Companion.from(it, api) }
|
||||
this@MovieViewModel.similar.setValueOnMain(similar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,12 +63,10 @@ import com.github.damontecres.wholphin.ui.components.StarRating
|
|||
import com.github.damontecres.wholphin.ui.components.StarRatingPrecision
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.rememberPosition
|
||||
import com.github.damontecres.wholphin.ui.rememberInt
|
||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
|
|
@ -183,6 +181,11 @@ fun SeriesDetails(
|
|||
}
|
||||
}
|
||||
|
||||
private const val HEADER_ROW = 0
|
||||
private const val SEASONS_ROW = HEADER_ROW + 1
|
||||
private const val PEOPLE_ROW = SEASONS_ROW + 1
|
||||
private const val SIMILAR_ROW = PEOPLE_ROW + 1
|
||||
|
||||
@Composable
|
||||
fun SeriesDetailsContent(
|
||||
preferences: UserPreferences,
|
||||
|
|
@ -200,11 +203,14 @@ fun SeriesDetailsContent(
|
|||
favoriteOnClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||
|
||||
var position by rememberPosition()
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||
var position by rememberInt()
|
||||
val focusRequesters = remember { List(SIMILAR_ROW + 1) { FocusRequester() } }
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequesters.getOrNull(position)?.tryRequestFocus()
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier,
|
||||
|
|
@ -256,51 +262,95 @@ fun SeriesDetailsContent(
|
|||
played = played,
|
||||
favorite = favorite,
|
||||
overviewOnClick = overviewOnClick,
|
||||
playOnClick = playOnClick,
|
||||
playOnClick = {
|
||||
position = HEADER_ROW
|
||||
playOnClick.invoke()
|
||||
},
|
||||
watchOnClick = watchOnClick,
|
||||
favoriteOnClick = favoriteOnClick,
|
||||
bringIntoViewRequester = bringIntoViewRequester,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth(.6f)
|
||||
.fillMaxWidth(.7f)
|
||||
.bringIntoViewRequester(bringIntoViewRequester)
|
||||
.padding(bottom = 80.dp)
|
||||
.ifElse(position.row < 0, Modifier.focusRequester(focusRequester)),
|
||||
.padding(bottom = 8.dp),
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(start = 16.dp)
|
||||
.focusRequester(focusRequesters[HEADER_ROW])
|
||||
.padding(bottom = 80.dp),
|
||||
) {
|
||||
ExpandablePlayButton(
|
||||
title = R.string.play,
|
||||
resume = Duration.ZERO,
|
||||
icon = Icons.Default.PlayArrow,
|
||||
onClick = {
|
||||
position = HEADER_ROW
|
||||
playOnClick.invoke()
|
||||
},
|
||||
modifier =
|
||||
Modifier.onFocusChanged {
|
||||
if (it.isFocused) {
|
||||
scope.launch(ExceptionHandler()) {
|
||||
bringIntoViewRequester.bringIntoView()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
ExpandableFaButton(
|
||||
title = if (played) R.string.mark_unwatched else R.string.mark_watched,
|
||||
iconStringRes = if (played) R.string.fa_eye else R.string.fa_eye_slash,
|
||||
onClick = watchOnClick,
|
||||
modifier =
|
||||
Modifier.onFocusChanged {
|
||||
if (it.isFocused) {
|
||||
scope.launch(ExceptionHandler()) {
|
||||
bringIntoViewRequester.bringIntoView()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
ExpandableFaButton(
|
||||
title = if (favorite) R.string.remove_favorite else R.string.add_favorite,
|
||||
iconStringRes = R.string.fa_heart,
|
||||
onClick = favoriteOnClick,
|
||||
iconColor = if (favorite) Color.Red else Color.Unspecified,
|
||||
modifier =
|
||||
Modifier.onFocusChanged {
|
||||
if (it.isFocused) {
|
||||
scope.launch(ExceptionHandler()) {
|
||||
bringIntoViewRequester.bringIntoView()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
ItemRow(
|
||||
title = "Seasons",
|
||||
items = seasons.items,
|
||||
onClickItem = onClickItem,
|
||||
onLongClickItem = onLongClickItem,
|
||||
cardOnFocus = { isFocused, index ->
|
||||
// if (isFocused) {
|
||||
// scope.launch(ExceptionHandler()) {
|
||||
// bringIntoViewRequester.bringIntoView()
|
||||
// }
|
||||
// }
|
||||
onClickItem = {
|
||||
position = SEASONS_ROW
|
||||
onClickItem.invoke(it)
|
||||
},
|
||||
onLongClickItem = onLongClickItem,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth(),
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequesters[SEASONS_ROW]),
|
||||
cardContent = @Composable { index, item, mod, onClick, onLongClick ->
|
||||
SeasonCard(
|
||||
item = item,
|
||||
onClick = {
|
||||
position = RowColumn(0, index)
|
||||
onClick.invoke()
|
||||
},
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
imageHeight = Cards.height2x3,
|
||||
imageWidth = Dp.Unspecified,
|
||||
showImageOverlay = true,
|
||||
modifier =
|
||||
mod
|
||||
.ifElse(
|
||||
position.row == 0 && position.column == index,
|
||||
Modifier.focusRequester(focusRequester),
|
||||
),
|
||||
modifier = mod,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -309,9 +359,14 @@ fun SeriesDetailsContent(
|
|||
item {
|
||||
PersonRow(
|
||||
people = people,
|
||||
onClick = {},
|
||||
onClick = {
|
||||
position = PEOPLE_ROW
|
||||
},
|
||||
onLongClick = {},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequesters[PEOPLE_ROW]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -320,7 +375,10 @@ fun SeriesDetailsContent(
|
|||
ItemRow(
|
||||
title = stringResource(R.string.more_like_this),
|
||||
items = similar,
|
||||
onClickItem = onClickItem,
|
||||
onClickItem = {
|
||||
position = SIMILAR_ROW
|
||||
onClickItem.invoke(it)
|
||||
},
|
||||
onLongClickItem = {},
|
||||
cardContent = { index, item, mod, onClick, onLongClick ->
|
||||
SeasonCard(
|
||||
|
|
@ -333,7 +391,10 @@ fun SeriesDetailsContent(
|
|||
imageWidth = Dp.Unspecified,
|
||||
)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequesters[SIMILAR_ROW]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -409,52 +470,6 @@ fun SeriesDetailsHeader(
|
|||
textBoxHeight = Dp.Unspecified,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
) {
|
||||
ExpandablePlayButton(
|
||||
title = R.string.play,
|
||||
resume = Duration.ZERO,
|
||||
icon = Icons.Default.PlayArrow,
|
||||
onClick = { playOnClick.invoke() },
|
||||
modifier =
|
||||
Modifier.onFocusChanged {
|
||||
if (it.isFocused) {
|
||||
scope.launch(ExceptionHandler()) {
|
||||
bringIntoViewRequester.bringIntoView()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
ExpandableFaButton(
|
||||
title = if (played) R.string.mark_unwatched else R.string.mark_watched,
|
||||
iconStringRes = if (played) R.string.fa_eye else R.string.fa_eye_slash,
|
||||
onClick = watchOnClick,
|
||||
modifier =
|
||||
Modifier.onFocusChanged {
|
||||
if (it.isFocused) {
|
||||
scope.launch(ExceptionHandler()) {
|
||||
bringIntoViewRequester.bringIntoView()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
ExpandableFaButton(
|
||||
title = if (favorite) R.string.remove_favorite else R.string.add_favorite,
|
||||
iconStringRes = R.string.fa_heart,
|
||||
onClick = favoriteOnClick,
|
||||
iconColor = if (favorite) Color.Red else Color.Unspecified,
|
||||
modifier =
|
||||
Modifier.onFocusChanged {
|
||||
if (it.isFocused) {
|
||||
scope.launch(ExceptionHandler()) {
|
||||
bringIntoViewRequester.bringIntoView()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class SeriesViewModel
|
|||
val seasons = MutableLiveData<ItemListAndMapping>(ItemListAndMapping.empty())
|
||||
val episodes = MutableLiveData<EpisodeList>(EpisodeList.Loading)
|
||||
val people = MutableLiveData<List<Person>>(listOf())
|
||||
val similar = MutableLiveData<List<BaseItem>>(listOf())
|
||||
val similar = MutableLiveData<List<BaseItem>>()
|
||||
|
||||
fun init(
|
||||
prefs: UserPreferences,
|
||||
|
|
@ -107,18 +107,20 @@ class SeriesViewModel
|
|||
people.map { Person.fromDto(it, api) }
|
||||
}.orEmpty()
|
||||
}
|
||||
val similar =
|
||||
api.libraryApi
|
||||
.getSimilarItems(
|
||||
GetSimilarItemsRequest(
|
||||
userId = serverRepository.currentUser?.id,
|
||||
itemId = itemId,
|
||||
fields = SlimItemFields,
|
||||
limit = 25,
|
||||
),
|
||||
).content.items
|
||||
.map { BaseItem.from(it, api, true) }
|
||||
this@SeriesViewModel.similar.setValueOnMain(similar)
|
||||
if (!similar.isInitialized) {
|
||||
val similar =
|
||||
api.libraryApi
|
||||
.getSimilarItems(
|
||||
GetSimilarItemsRequest(
|
||||
userId = serverRepository.currentUser?.id,
|
||||
itemId = itemId,
|
||||
fields = SlimItemFields,
|
||||
limit = 25,
|
||||
),
|
||||
).content.items
|
||||
.map { BaseItem.from(it, api, true) }
|
||||
this@SeriesViewModel.similar.setValueOnMain(similar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -320,7 +322,9 @@ class SeriesViewModel
|
|||
).content.items
|
||||
.firstOrNull()
|
||||
if (nextUp != null) {
|
||||
navigateTo(Destination.Playback(nextUp.id, 0L))
|
||||
withContext(Dispatchers.Main) {
|
||||
navigateTo(Destination.Playback(nextUp.id, 0L))
|
||||
}
|
||||
} else {
|
||||
showToast(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,15 @@ interface RememberTabManager {
|
|||
preferences: UserPreferences,
|
||||
itemId: UUID,
|
||||
defaultTab: Int,
|
||||
): Int = getRememberedTab(preferences, itemId.toString(), defaultTab)
|
||||
|
||||
/**
|
||||
* If enabled, get the remembered tab index for the given item
|
||||
*/
|
||||
fun getRememberedTab(
|
||||
preferences: UserPreferences,
|
||||
itemId: String,
|
||||
defaultTab: Int,
|
||||
): Int
|
||||
|
||||
/**
|
||||
|
|
@ -20,5 +29,14 @@ interface RememberTabManager {
|
|||
preferences: UserPreferences,
|
||||
itemId: UUID,
|
||||
tabIndex: Int,
|
||||
) = saveRememberedTab(preferences, itemId.toString(), tabIndex)
|
||||
|
||||
/**
|
||||
* If enabled, save the remembered tab index for the given item
|
||||
*/
|
||||
fun saveRememberedTab(
|
||||
preferences: UserPreferences,
|
||||
itemId: String,
|
||||
tabIndex: Int,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue