mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Various UI/UX tweaks
This commit is contained in:
parent
005d6b11ce
commit
2212e32c53
22 changed files with 237 additions and 149 deletions
|
|
@ -9,8 +9,10 @@ class DolphinApplication : Application() {
|
||||||
init {
|
init {
|
||||||
instance = this
|
instance = this
|
||||||
|
|
||||||
// TODO only plant in debug builds
|
if (BuildConfig.DEBUG) {
|
||||||
Timber.plant(Timber.DebugTree())
|
// TODO minimal logging for release builds?
|
||||||
|
Timber.plant(Timber.DebugTree())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,6 @@ fun SortByButton(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO switch to dialog?
|
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = sortByDropDown,
|
expanded = sortByDropDown,
|
||||||
onDismissRequest = { sortByDropDown = false },
|
onDismissRequest = { sortByDropDown = false },
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package com.github.damontecres.dolphin.ui.components
|
package com.github.damontecres.dolphin.ui.components
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.LocalIndication
|
import androidx.compose.foundation.LocalIndication
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
|
@ -51,11 +50,6 @@ fun SwitchWithLabel(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
onStateChange(!checked)
|
onStateChange(!checked)
|
||||||
} else {
|
|
||||||
// TODO there are other uses, so shouldn't hardcode this toast
|
|
||||||
Toast
|
|
||||||
.makeText(context, "Item has no children", Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
).padding(8.dp),
|
).padding(8.dp),
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusProperties
|
import androidx.compose.ui.focus.focusProperties
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRestorer
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.input.key.Key
|
||||||
import androidx.compose.ui.input.key.KeyEventType
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
|
|
@ -253,6 +254,7 @@ fun CardGrid(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.focusGroup()
|
.focusGroup()
|
||||||
|
.focusRestorer(firstFocus)
|
||||||
.focusRequester(gridFocusRequester)
|
.focusRequester(gridFocusRequester)
|
||||||
.focusProperties {
|
.focusProperties {
|
||||||
onExit = {
|
onExit = {
|
||||||
|
|
@ -264,6 +266,7 @@ fun CardGrid(
|
||||||
onEnter = {
|
onEnter = {
|
||||||
focusedIndexOnExit = -1
|
focusedIndexOnExit = -1
|
||||||
if (focusedIndex < 0 && gridState.firstVisibleItemIndex <= startPosition) {
|
if (focusedIndex < 0 && gridState.firstVisibleItemIndex <= startPosition) {
|
||||||
|
Timber.d("onEnter: focusedIndex=$focusedIndex, savedFocusedIndex=$savedFocusedIndex")
|
||||||
focusedIndex = startPosition
|
focusedIndex = startPosition
|
||||||
firstFocus.tryRequestFocus()
|
firstFocus.tryRequestFocus()
|
||||||
}
|
}
|
||||||
|
|
@ -317,7 +320,12 @@ fun CardGrid(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
item = item,
|
item = item,
|
||||||
onClick = { if (item != null) itemOnClick.invoke(item) },
|
onClick = {
|
||||||
|
if (item != null) {
|
||||||
|
itemOnClick.invoke(item)
|
||||||
|
savedFocusedIndex = index
|
||||||
|
}
|
||||||
|
},
|
||||||
onLongClick = { if (item != null) longClicker.invoke(item) },
|
onLongClick = { if (item != null) longClicker.invoke(item) },
|
||||||
cardHeight = null,
|
cardHeight = null,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
package com.github.damontecres.dolphin.ui.detail
|
package com.github.damontecres.dolphin.ui.detail
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
|
@ -8,7 +13,10 @@ 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
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
|
@ -136,6 +144,7 @@ fun CollectionFolderDetails(
|
||||||
SortOrder.ASCENDING,
|
SortOrder.ASCENDING,
|
||||||
),
|
),
|
||||||
showTitle: Boolean = true,
|
showTitle: Boolean = true,
|
||||||
|
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
OneTimeLaunchedEffect {
|
OneTimeLaunchedEffect {
|
||||||
viewModel.init(destination.itemId, destination.item, initialSortAndDirection)
|
viewModel.init(destination.itemId, destination.item, initialSortAndDirection)
|
||||||
|
|
@ -163,6 +172,7 @@ fun CollectionFolderDetails(
|
||||||
viewModel.loadResults(it)
|
viewModel.loadResults(it)
|
||||||
},
|
},
|
||||||
showTitle = showTitle,
|
showTitle = showTitle,
|
||||||
|
positionCallback = positionCallback,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -180,6 +190,7 @@ fun CollectionDetails(
|
||||||
onSortChange: (SortAndDirection) -> Unit,
|
onSortChange: (SortAndDirection) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
showTitle: Boolean = true,
|
showTitle: Boolean = true,
|
||||||
|
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
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 =
|
val sortOptions =
|
||||||
|
|
@ -190,27 +201,35 @@ fun CollectionDetails(
|
||||||
else -> listOf(ItemSortBy.SORT_NAME, ItemSortBy.DATE_CREATED, ItemSortBy.RANDOM)
|
else -> listOf(ItemSortBy.SORT_NAME, ItemSortBy.DATE_CREATED, ItemSortBy.RANDOM)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||||
|
|
||||||
val gridFocusRequester = remember { FocusRequester() }
|
val gridFocusRequester = remember { FocusRequester() }
|
||||||
LaunchedEffect(Unit) { gridFocusRequester.tryRequestFocus() }
|
LaunchedEffect(Unit) { gridFocusRequester.tryRequestFocus() }
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(0.dp),
|
verticalArrangement = Arrangement.spacedBy(0.dp),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
if (showTitle) {
|
AnimatedVisibility(
|
||||||
Text(
|
showHeader,
|
||||||
text = title,
|
enter = slideInVertically() + fadeIn(),
|
||||||
style = MaterialTheme.typography.displayMedium,
|
exit = slideOutVertically() + fadeOut(),
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
) {
|
||||||
textAlign = TextAlign.Center,
|
if (showTitle) {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.displayMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
SortByButton(
|
||||||
|
sortOptions = sortOptions,
|
||||||
|
current = sortAndDirection,
|
||||||
|
onSortChange = onSortChange,
|
||||||
|
modifier = Modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SortByButton(
|
|
||||||
sortOptions = sortOptions,
|
|
||||||
current = sortAndDirection,
|
|
||||||
onSortChange = onSortChange,
|
|
||||||
modifier = Modifier,
|
|
||||||
)
|
|
||||||
CardGrid(
|
CardGrid(
|
||||||
pager = pager,
|
pager = pager,
|
||||||
itemOnClick = {
|
itemOnClick = {
|
||||||
|
|
@ -229,7 +248,10 @@ fun CollectionDetails(
|
||||||
showJumpButtons = false, // TODO add preference
|
showJumpButtons = false, // TODO add preference
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
initialPosition = 0,
|
initialPosition = 0,
|
||||||
positionCallback = { _, _ -> },
|
positionCallback = { columns, position ->
|
||||||
|
showHeader = position < columns
|
||||||
|
positionCallback?.invoke(columns, position)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
package com.github.damontecres.dolphin.ui.detail
|
package com.github.damontecres.dolphin.ui.detail
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
|
@ -7,6 +12,7 @@ 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
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
|
@ -64,62 +70,69 @@ fun CollectionFolderMovie(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
TabRow(
|
AnimatedVisibility(
|
||||||
selectedTabIndex = focusTabIndex,
|
showHeader,
|
||||||
modifier =
|
enter = slideInVertically() + fadeIn(),
|
||||||
Modifier
|
exit = slideOutVertically() + fadeOut(),
|
||||||
.padding(start = 32.dp, top = 16.dp, bottom = 16.dp)
|
) {
|
||||||
.focusRestorer(firstTabFocusRequester)
|
TabRow(
|
||||||
.onFocusChanged {
|
selectedTabIndex = focusTabIndex,
|
||||||
if (!it.isFocused) {
|
modifier =
|
||||||
focusTabIndex = selectedTabIndex
|
Modifier
|
||||||
}
|
.padding(start = 32.dp, top = 16.dp, bottom = 16.dp)
|
||||||
},
|
.focusRestorer(firstTabFocusRequester)
|
||||||
indicator =
|
.onFocusChanged {
|
||||||
@Composable { tabPositions, doesTabRowHaveFocus ->
|
if (!it.isFocused) {
|
||||||
tabPositions.getOrNull(focusTabIndex)?.let { currentTabPosition ->
|
focusTabIndex = selectedTabIndex
|
||||||
|
}
|
||||||
|
},
|
||||||
|
indicator =
|
||||||
|
@Composable { tabPositions, doesTabRowHaveFocus ->
|
||||||
|
tabPositions.getOrNull(focusTabIndex)?.let { currentTabPosition ->
|
||||||
// TabRowDefaults.PillIndicator(
|
// TabRowDefaults.PillIndicator(
|
||||||
// currentTabPosition = currentTabPosition,
|
// currentTabPosition = currentTabPosition,
|
||||||
// doesTabRowHaveFocus = doesTabRowHaveFocus,
|
// doesTabRowHaveFocus = doesTabRowHaveFocus,
|
||||||
// )
|
// )
|
||||||
TabRowDefaults.UnderlinedIndicator(
|
TabRowDefaults.UnderlinedIndicator(
|
||||||
currentTabPosition = currentTabPosition,
|
currentTabPosition = currentTabPosition,
|
||||||
doesTabRowHaveFocus = doesTabRowHaveFocus,
|
doesTabRowHaveFocus = doesTabRowHaveFocus,
|
||||||
activeColor = MaterialTheme.colorScheme.border,
|
activeColor = MaterialTheme.colorScheme.border,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tabs = {
|
||||||
|
tabs.forEachIndexed { index, title ->
|
||||||
|
Tab(
|
||||||
|
selected = focusTabIndex == index,
|
||||||
|
onClick = { selectedTabIndex = index },
|
||||||
|
onFocus = { focusTabIndex = index },
|
||||||
|
colors =
|
||||||
|
TabDefaults.pillIndicatorTabColors(
|
||||||
|
focusedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.ifElse(
|
||||||
|
index == selectedTabIndex,
|
||||||
|
Modifier.focusRequester(firstTabFocusRequester),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tabs = {
|
)
|
||||||
tabs.forEachIndexed { index, title ->
|
}
|
||||||
Tab(
|
|
||||||
selected = focusTabIndex == index,
|
|
||||||
onClick = { selectedTabIndex = index },
|
|
||||||
onFocus = { focusTabIndex = index },
|
|
||||||
colors =
|
|
||||||
TabDefaults.pillIndicatorTabColors(
|
|
||||||
focusedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.ifElse(
|
|
||||||
index == selectedTabIndex,
|
|
||||||
Modifier.focusRequester(firstTabFocusRequester),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
when (selectedTabIndex) {
|
when (selectedTabIndex) {
|
||||||
0 -> {
|
0 -> {
|
||||||
RecommendedMovie(
|
RecommendedMovie(
|
||||||
|
|
@ -144,6 +157,9 @@ fun CollectionFolderMovie(
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.focusRequester(focusRequester),
|
.focusRequester(focusRequester),
|
||||||
|
positionCallback = { columns, position ->
|
||||||
|
showHeader = position < columns
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> ErrorMessage("Invalid tab index $selectedTabIndex", null)
|
else -> ErrorMessage("Invalid tab index $selectedTabIndex", null)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
package com.github.damontecres.dolphin.ui.detail
|
package com.github.damontecres.dolphin.ui.detail
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
|
@ -7,6 +12,7 @@ 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
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
|
@ -65,61 +71,69 @@ fun CollectionFolderTv(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
TabRow(
|
AnimatedVisibility(
|
||||||
selectedTabIndex = focusTabIndex,
|
showHeader,
|
||||||
modifier =
|
enter = slideInVertically() + fadeIn(),
|
||||||
Modifier
|
exit = slideOutVertically() + fadeOut(),
|
||||||
.padding(start = 32.dp, top = 16.dp, bottom = 16.dp)
|
) {
|
||||||
.focusRestorer(firstTabFocusRequester)
|
TabRow(
|
||||||
.onFocusChanged {
|
selectedTabIndex = focusTabIndex,
|
||||||
if (!it.isFocused) {
|
modifier =
|
||||||
focusTabIndex = selectedTabIndex
|
Modifier
|
||||||
}
|
.padding(start = 32.dp, top = 16.dp, bottom = 16.dp)
|
||||||
},
|
.focusRestorer(firstTabFocusRequester)
|
||||||
indicator =
|
.onFocusChanged {
|
||||||
@Composable { tabPositions, doesTabRowHaveFocus ->
|
if (!it.isFocused) {
|
||||||
tabPositions.getOrNull(focusTabIndex)?.let { currentTabPosition ->
|
focusTabIndex = selectedTabIndex
|
||||||
|
}
|
||||||
|
},
|
||||||
|
indicator =
|
||||||
|
@Composable { tabPositions, doesTabRowHaveFocus ->
|
||||||
|
tabPositions.getOrNull(focusTabIndex)?.let { currentTabPosition ->
|
||||||
// TabRowDefaults.PillIndicator(
|
// TabRowDefaults.PillIndicator(
|
||||||
// currentTabPosition = currentTabPosition,
|
// currentTabPosition = currentTabPosition,
|
||||||
// doesTabRowHaveFocus = doesTabRowHaveFocus,
|
// doesTabRowHaveFocus = doesTabRowHaveFocus,
|
||||||
// )
|
// )
|
||||||
TabRowDefaults.UnderlinedIndicator(
|
TabRowDefaults.UnderlinedIndicator(
|
||||||
currentTabPosition = currentTabPosition,
|
currentTabPosition = currentTabPosition,
|
||||||
doesTabRowHaveFocus = doesTabRowHaveFocus,
|
doesTabRowHaveFocus = doesTabRowHaveFocus,
|
||||||
activeColor = MaterialTheme.colorScheme.border,
|
activeColor = MaterialTheme.colorScheme.border,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tabs = {
|
||||||
|
tabs.forEachIndexed { index, title ->
|
||||||
|
Tab(
|
||||||
|
selected = focusTabIndex == index,
|
||||||
|
onClick = { selectedTabIndex = index },
|
||||||
|
onFocus = { focusTabIndex = index },
|
||||||
|
colors =
|
||||||
|
TabDefaults.pillIndicatorTabColors(
|
||||||
|
focusedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.ifElse(
|
||||||
|
index == selectedTabIndex,
|
||||||
|
Modifier.focusRequester(firstTabFocusRequester),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tabs = {
|
)
|
||||||
tabs.forEachIndexed { index, title ->
|
}
|
||||||
Tab(
|
|
||||||
selected = focusTabIndex == index,
|
|
||||||
onClick = { selectedTabIndex = index },
|
|
||||||
onFocus = { focusTabIndex = index },
|
|
||||||
colors =
|
|
||||||
TabDefaults.pillIndicatorTabColors(
|
|
||||||
focusedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.padding(8.dp)
|
|
||||||
.ifElse(
|
|
||||||
index == selectedTabIndex,
|
|
||||||
Modifier.focusRequester(firstTabFocusRequester),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
when (selectedTabIndex) {
|
when (selectedTabIndex) {
|
||||||
0 -> {
|
0 -> {
|
||||||
RecommendedTvShow(
|
RecommendedTvShow(
|
||||||
|
|
@ -144,6 +158,9 @@ fun CollectionFolderTv(
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.focusRequester(focusRequester),
|
.focusRequester(focusRequester),
|
||||||
|
positionCallback = { columns, position ->
|
||||||
|
showHeader = position < columns
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> ErrorMessage("Invalid tab index $selectedTabIndex", null)
|
else -> ErrorMessage("Invalid tab index $selectedTabIndex", null)
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@ class SeriesViewModel
|
||||||
try {
|
try {
|
||||||
loadEpisodesInternal(season)
|
loadEpisodesInternal(season)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.Forest.e(e, "Error loading episodes for $seriesId for season $season")
|
Timber.e(e, "Error loading episodes for $seriesId for season $season")
|
||||||
// TODO show error in UI?
|
// TODO show error in UI?
|
||||||
ItemListAndMapping.empty()
|
ItemListAndMapping.empty()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,9 +90,10 @@ class MovieViewModel
|
||||||
super.init(itemId, potential)?.join()
|
super.init(itemId, potential)?.join()
|
||||||
item.value?.let { item ->
|
item.value?.let { item ->
|
||||||
people.value =
|
people.value =
|
||||||
item.data.people?.letNotEmpty { people ->
|
item.data.people
|
||||||
people.map { Person.fromDto(it, api) }
|
?.letNotEmpty { people ->
|
||||||
}
|
people.map { Person.fromDto(it, api) }
|
||||||
|
}.orEmpty()
|
||||||
chapters.value = Chapter.fromDto(item.data, api)
|
chapters.value = Chapter.fromDto(item.data, api)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ import com.github.damontecres.dolphin.ui.playSoundOnFocus
|
||||||
import com.github.damontecres.dolphin.ui.roundMinutes
|
import com.github.damontecres.dolphin.ui.roundMinutes
|
||||||
import com.github.damontecres.dolphin.ui.timeRemaining
|
import com.github.damontecres.dolphin.ui.timeRemaining
|
||||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||||
|
import org.jellyfin.sdk.model.api.PersonKind
|
||||||
import org.jellyfin.sdk.model.extensions.ticks
|
import org.jellyfin.sdk.model.extensions.ticks
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -93,13 +94,13 @@ fun MovieDetailsHeader(
|
||||||
add(it)
|
add(it)
|
||||||
}
|
}
|
||||||
dto.timeRemaining?.roundMinutes?.let { add("$it left") }
|
dto.timeRemaining?.roundMinutes?.let { add("$it left") }
|
||||||
|
dto.officialRating?.let(::add)
|
||||||
}
|
}
|
||||||
DotSeparatedRow(
|
DotSeparatedRow(
|
||||||
texts = details,
|
texts = details,
|
||||||
textStyle = MaterialTheme.typography.titleLarge,
|
textStyle = MaterialTheme.typography.titleLarge,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// TODO ratings?
|
|
||||||
dto.communityRating?.let {
|
dto.communityRating?.let {
|
||||||
if (it > 0f) {
|
if (it > 0f) {
|
||||||
StarRating(
|
StarRating(
|
||||||
|
|
@ -196,15 +197,21 @@ fun MovieDetailsHeader(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO director
|
// TODO add writers, studio, etc to overview dialog
|
||||||
// TODO writers
|
dto.people?.firstOrNull { it.type == PersonKind.DIRECTOR }?.name?.let {
|
||||||
dto.studios?.letNotEmpty {
|
|
||||||
TitleValueText(
|
TitleValueText(
|
||||||
stringResource(R.string.studios),
|
stringResource(R.string.director),
|
||||||
it.joinToString(", ") { s -> s.name ?: "" },
|
it,
|
||||||
modifier = Modifier.widthIn(max = 80.dp),
|
modifier = Modifier.widthIn(max = 80.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
// dto.studios?.letNotEmpty {
|
||||||
|
// TitleValueText(
|
||||||
|
// stringResource(R.string.studios),
|
||||||
|
// it.joinToString(", ") { s -> s.name ?: "" },
|
||||||
|
// modifier = Modifier.widthIn(max = 80.dp),
|
||||||
|
// )
|
||||||
|
// }
|
||||||
dto.genres?.letNotEmpty {
|
dto.genres?.letNotEmpty {
|
||||||
TitleValueText(
|
TitleValueText(
|
||||||
stringResource(R.string.genres),
|
stringResource(R.string.genres),
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ fun FocusedEpisodeHeader(
|
||||||
add(it)
|
add(it)
|
||||||
}
|
}
|
||||||
dto.timeRemaining?.roundMinutes?.let { add("$it left") }
|
dto.timeRemaining?.roundMinutes?.let { add("$it left") }
|
||||||
|
dto.officialRating?.let(::add)
|
||||||
}
|
}
|
||||||
DotSeparatedRow(
|
DotSeparatedRow(
|
||||||
texts = details,
|
texts = details,
|
||||||
|
|
@ -77,7 +78,6 @@ fun FocusedEpisodeHeader(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
// TODO ratings?
|
|
||||||
dto.communityRating?.let {
|
dto.communityRating?.let {
|
||||||
if (it > 0f) {
|
if (it > 0f) {
|
||||||
StarRating(
|
StarRating(
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,6 @@ fun SeriesOverview(
|
||||||
val seasons by viewModel.seasons.observeAsState(ItemListAndMapping.empty())
|
val seasons by viewModel.seasons.observeAsState(ItemListAndMapping.empty())
|
||||||
val episodes by viewModel.episodes.observeAsState(ItemListAndMapping.empty())
|
val episodes by viewModel.episodes.observeAsState(ItemListAndMapping.empty())
|
||||||
|
|
||||||
// TODO need to translate season/episode into tab/row index in case of missing seasons/episodes
|
|
||||||
var position by rememberSaveable(
|
var position by rememberSaveable(
|
||||||
destination,
|
destination,
|
||||||
loading,
|
loading,
|
||||||
|
|
@ -103,7 +102,7 @@ fun SeriesOverview(
|
||||||
|
|
||||||
LaunchedEffect(episodes.items) {
|
LaunchedEffect(episodes.items) {
|
||||||
if (episodes.items.isNotEmpty()) {
|
if (episodes.items.isNotEmpty()) {
|
||||||
// TODO focus on first episode when changing seasons
|
// TODO focus on first episode when changing seasons?
|
||||||
// firstItemFocusRequester.requestFocus()
|
// firstItemFocusRequester.requestFocus()
|
||||||
episodes.items.getOrNull(position.episodeRowIndex)?.let {
|
episodes.items.getOrNull(position.episodeRowIndex)?.let {
|
||||||
viewModel.refreshEpisode(it.id, position.episodeRowIndex)
|
viewModel.refreshEpisode(it.id, position.episodeRowIndex)
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import androidx.compose.ui.draw.drawWithContent
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.focusRestorer
|
import androidx.compose.ui.focus.focusRestorer
|
||||||
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
|
@ -38,6 +39,7 @@ import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Tab
|
import androidx.tv.material3.Tab
|
||||||
import androidx.tv.material3.TabDefaults
|
import androidx.tv.material3.TabDefaults
|
||||||
import androidx.tv.material3.TabRow
|
import androidx.tv.material3.TabRow
|
||||||
|
import androidx.tv.material3.TabRowDefaults
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
|
|
@ -67,7 +69,7 @@ fun SeriesOverviewContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||||
// TODO need to map between season index and tab index in case of missing seasons
|
var focusTabIndex by rememberSaveable(position) { mutableIntStateOf(position.seasonTabIndex) }
|
||||||
var selectedTabIndex by rememberSaveable(position) { mutableIntStateOf(position.seasonTabIndex) }
|
var selectedTabIndex by rememberSaveable(position) { mutableIntStateOf(position.seasonTabIndex) }
|
||||||
val focusRequesters = remember(seasons.size) { List(seasons.size) { FocusRequester() } }
|
val focusRequesters = remember(seasons.size) { List(seasons.size) { FocusRequester() } }
|
||||||
var resolvedTabIndex by remember { mutableIntStateOf(selectedTabIndex) }
|
var resolvedTabIndex by remember { mutableIntStateOf(selectedTabIndex) }
|
||||||
|
|
@ -121,7 +123,7 @@ fun SeriesOverviewContent(
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
TabRow(
|
TabRow(
|
||||||
selectedTabIndex = selectedTabIndex,
|
selectedTabIndex = focusTabIndex,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.ifElse(
|
.ifElse(
|
||||||
|
|
@ -129,20 +131,39 @@ fun SeriesOverviewContent(
|
||||||
{ Modifier.focusRestorer(focusRequesters[selectedTabIndex]) },
|
{ Modifier.focusRestorer(focusRequesters[selectedTabIndex]) },
|
||||||
).focusRequester(tabRowFocusRequester)
|
).focusRequester(tabRowFocusRequester)
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
|
.onFocusChanged {
|
||||||
|
if (!it.isFocused) {
|
||||||
|
focusTabIndex = selectedTabIndex
|
||||||
|
}
|
||||||
|
},
|
||||||
|
indicator =
|
||||||
|
@Composable { tabPositions, doesTabRowHaveFocus ->
|
||||||
|
tabPositions.getOrNull(focusTabIndex)?.let { currentTabPosition ->
|
||||||
|
// TabRowDefaults.PillIndicator(
|
||||||
|
// currentTabPosition = currentTabPosition,
|
||||||
|
// doesTabRowHaveFocus = doesTabRowHaveFocus,
|
||||||
|
// )
|
||||||
|
TabRowDefaults.UnderlinedIndicator(
|
||||||
|
currentTabPosition = currentTabPosition,
|
||||||
|
doesTabRowHaveFocus = doesTabRowHaveFocus,
|
||||||
|
activeColor = MaterialTheme.colorScheme.border,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
seasons.forEachIndexed { index, season ->
|
seasons.forEachIndexed { index, season ->
|
||||||
season?.let { season ->
|
season?.let { season ->
|
||||||
Tab(
|
Tab(
|
||||||
selected = index == selectedTabIndex,
|
selected = focusTabIndex == index,
|
||||||
onFocus = {},
|
onFocus = { focusTabIndex = index },
|
||||||
onClick = {
|
onClick = {
|
||||||
selectedTabIndex = index
|
selectedTabIndex = index
|
||||||
onFocus.invoke(SeriesOverviewPosition(index, 0))
|
onFocus.invoke(SeriesOverviewPosition(index, 0))
|
||||||
},
|
},
|
||||||
colors =
|
colors =
|
||||||
TabDefaults.pillIndicatorTabColors(
|
TabDefaults.pillIndicatorTabColors(
|
||||||
// TODO
|
focusedContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ fun HomePageContent(
|
||||||
var position by rememberSaveable(stateSaver = RowColumnSaver) {
|
var position by rememberSaveable(stateSaver = RowColumnSaver) {
|
||||||
mutableStateOf(RowColumn(0, 0))
|
mutableStateOf(RowColumn(0, 0))
|
||||||
}
|
}
|
||||||
// TODO use position instead?
|
|
||||||
var focusedItem by remember {
|
var focusedItem by remember {
|
||||||
mutableStateOf<BaseItem?>(
|
mutableStateOf<BaseItem?>(
|
||||||
homeRows.getOrNull(0)?.items?.getOrNull(
|
homeRows.getOrNull(0)?.items?.getOrNull(
|
||||||
|
|
@ -178,7 +178,7 @@ fun HomePageContent(
|
||||||
},
|
},
|
||||||
onLongClickItem = {},
|
onLongClickItem = {},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
cardContent = { index, item, modifier, onClick, onLongClick ->
|
cardContent = { index, item, cardModifier, onClick, onLongClick ->
|
||||||
// TODO better aspect ration handling?
|
// TODO better aspect ration handling?
|
||||||
BannerCard(
|
BannerCard(
|
||||||
imageUrl = item?.imageUrl,
|
imageUrl = item?.imageUrl,
|
||||||
|
|
@ -189,7 +189,7 @@ fun HomePageContent(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
modifier =
|
modifier =
|
||||||
modifier
|
cardModifier
|
||||||
.ifElse(
|
.ifElse(
|
||||||
focusedItem == item,
|
focusedItem == item,
|
||||||
Modifier.focusRequester(focusRequester),
|
Modifier.focusRequester(focusRequester),
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,6 @@ import java.util.UUID
|
||||||
sealed class Destination(
|
sealed class Destination(
|
||||||
val fullScreen: Boolean = false,
|
val fullScreen: Boolean = false,
|
||||||
) : NavKey {
|
) : NavKey {
|
||||||
@Serializable
|
|
||||||
data object Setup : Destination(true)
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object ServerList : Destination(true)
|
data object ServerList : Destination(true)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import com.github.damontecres.dolphin.ui.detail.CollectionFolderTv
|
||||||
import com.github.damontecres.dolphin.ui.detail.EpisodeDetails
|
import com.github.damontecres.dolphin.ui.detail.EpisodeDetails
|
||||||
import com.github.damontecres.dolphin.ui.detail.SeasonDetails
|
import com.github.damontecres.dolphin.ui.detail.SeasonDetails
|
||||||
import com.github.damontecres.dolphin.ui.detail.SeriesDetails
|
import com.github.damontecres.dolphin.ui.detail.SeriesDetails
|
||||||
import com.github.damontecres.dolphin.ui.detail.VideoDetails
|
|
||||||
import com.github.damontecres.dolphin.ui.detail.movie.MovieDetails
|
import com.github.damontecres.dolphin.ui.detail.movie.MovieDetails
|
||||||
import com.github.damontecres.dolphin.ui.detail.series.SeriesOverview
|
import com.github.damontecres.dolphin.ui.detail.series.SeriesOverview
|
||||||
import com.github.damontecres.dolphin.ui.main.HomePage
|
import com.github.damontecres.dolphin.ui.main.HomePage
|
||||||
|
|
@ -105,7 +104,8 @@ fun DestinationContent(
|
||||||
)
|
)
|
||||||
|
|
||||||
BaseItemKind.VIDEO ->
|
BaseItemKind.VIDEO ->
|
||||||
VideoDetails(
|
// TODO Use VideoDetails
|
||||||
|
MovieDetails(
|
||||||
preferences,
|
preferences,
|
||||||
navigationManager,
|
navigationManager,
|
||||||
destination,
|
destination,
|
||||||
|
|
@ -153,6 +153,5 @@ fun DestinationContent(
|
||||||
userPreferences = preferences,
|
userPreferences = preferences,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
Destination.Setup -> TODO()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ fun NavDrawer(
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val drawerFocusRequester = remember { FocusRequester() }
|
val drawerFocusRequester = remember { FocusRequester() }
|
||||||
BackHandler(enabled = (drawerState.currentValue == DrawerValue.Closed && destination == Destination.Main)) {
|
BackHandler(enabled = (drawerState.currentValue == DrawerValue.Closed && destination is Destination.Main)) {
|
||||||
drawerState.setValue(DrawerValue.Open)
|
drawerState.setValue(DrawerValue.Open)
|
||||||
drawerFocusRequester.requestFocus()
|
drawerFocusRequester.requestFocus()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ fun PlaybackContent(
|
||||||
AmbientPlayerListener(player)
|
AmbientPlayerListener(player)
|
||||||
|
|
||||||
if (stream == null) {
|
if (stream == null) {
|
||||||
// TODO loading
|
LoadingPage()
|
||||||
} else {
|
} else {
|
||||||
stream?.let {
|
stream?.let {
|
||||||
var contentScale by remember { mutableStateOf(ContentScale.Fit) }
|
var contentScale by remember { mutableStateOf(ContentScale.Fit) }
|
||||||
|
|
|
||||||
|
|
@ -288,7 +288,6 @@ fun LeftPlaybackButtons(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (showMoreOptions) {
|
if (showMoreOptions) {
|
||||||
// TODO options need context about what to display
|
|
||||||
val options =
|
val options =
|
||||||
buildList {
|
buildList {
|
||||||
addAll(moreButtonOptions.options.keys)
|
addAll(moreButtonOptions.options.keys)
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ fun ServerList(
|
||||||
},
|
},
|
||||||
onClick = { onSwitchServer.invoke(server) },
|
onClick = { onSwitchServer.invoke(server) },
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
// TODO dialog to remove server
|
showDeleteDialog = server
|
||||||
},
|
},
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,11 @@ fun formatDateTime(dateTime: LocalDateTime): String =
|
||||||
dateTime.toString()
|
dateTime.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO multi episode support
|
|
||||||
val BaseItemDto.seasonEpisode: String?
|
val BaseItemDto.seasonEpisode: String?
|
||||||
get() =
|
get() =
|
||||||
if (parentIndexNumber != null && indexNumber != null) {
|
if (parentIndexNumber != null && indexNumber != null && indexNumberEnd != null) {
|
||||||
|
"S$parentIndexNumber E$indexNumber-E$indexNumberEnd"
|
||||||
|
} else if (parentIndexNumber != null && indexNumber != null) {
|
||||||
"S$parentIndexNumber E$indexNumber"
|
"S$parentIndexNumber E$indexNumber"
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|
@ -29,7 +30,12 @@ val BaseItemDto.seasonEpisodePadded: String?
|
||||||
if (parentIndexNumber != null && indexNumber != null) {
|
if (parentIndexNumber != null && indexNumber != null) {
|
||||||
val season = parentIndexNumber?.toString()?.padStart(2, '0')
|
val season = parentIndexNumber?.toString()?.padStart(2, '0')
|
||||||
val episode = indexNumber?.toString()?.padStart(2, '0')
|
val episode = indexNumber?.toString()?.padStart(2, '0')
|
||||||
"S${season}E$episode"
|
val endEpisode = indexNumberEnd?.toString()?.padStart(2, '0')
|
||||||
|
if (endEpisode != null) {
|
||||||
|
"S${season}E$episode-E$endEpisode"
|
||||||
|
} else {
|
||||||
|
"S${season}E$episode"
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,5 +60,6 @@
|
||||||
<string name="pref_key_update_last_check">preference.update.lastTimestamp</string>
|
<string name="pref_key_update_last_check">preference.update.lastTimestamp</string>
|
||||||
<string name="confirm">Confirm</string>
|
<string name="confirm">Confirm</string>
|
||||||
<string name="remember_selected_tab">Remember selected tabs</string>
|
<string name="remember_selected_tab">Remember selected tabs</string>
|
||||||
|
<string name="director">Director</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue