mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Updates to details pages including videos' stream details display (#279)
## Details This PR updates the detail pages for Movies & TV Shows. - Increase backdrop image size & brightness (now matches home page) - Increase max width for titles - Decrease max width for detail text - Switch to using a customized display labels for video, audio, & subtitle streams Additional change for movie pages: - Don't dim movie details text - Move video stream details below play buttons - Move first row up higher ### Labels Instead of using the server provided `DisplayTitle`, create display labels in-app. In general, this shortens the text without losing significant detail. It is similar to the official app. Some of the changes: - Don't show "SDR", only "HDR" - Simplify audio channel info, eg "Surround 5.1" => "5.1" - Show chosen subtitle language - Show chosen subtitle codec - Show "(+#)" if there are additional audio or subtitle tracks - Use gray background slightly tinted by the theme color ## Issues Closes #239
This commit is contained in:
parent
7be4fb5fdf
commit
5b96be6662
14 changed files with 427 additions and 291 deletions
|
|
@ -2,19 +2,9 @@ package com.github.damontecres.wholphin.ui
|
|||
|
||||
import android.os.Build
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ChosenStreams
|
||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||
import com.github.damontecres.wholphin.data.model.chooseSource
|
||||
import com.github.damontecres.wholphin.data.model.chooseStream
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.util.languageName
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.MediaSegmentType
|
||||
import org.jellyfin.sdk.model.api.MediaStream
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
|
@ -81,53 +71,6 @@ val BaseItemDto.seasonEpisodePadded: String?
|
|||
null
|
||||
}
|
||||
|
||||
fun formatSubtitleLang(mediaStreams: List<MediaStream>?): String? =
|
||||
mediaStreams
|
||||
?.filter { it.type == MediaStreamType.SUBTITLE && it.language.isNotNullOrBlank() }
|
||||
?.mapNotNull { it.language }
|
||||
?.distinct()
|
||||
?.joinToString(", ") { languageName(it) }
|
||||
|
||||
/**
|
||||
* Gets the selected audio display title for the given item & chosen streams
|
||||
*/
|
||||
fun getAudioDisplay(
|
||||
item: BaseItemDto,
|
||||
chosenStreams: ChosenStreams?,
|
||||
preferences: UserPreferences,
|
||||
) = getAudioDisplay(item, chosenStreams?.itemPlayback, preferences)
|
||||
|
||||
/**
|
||||
* Gets the selected audio display title for the given item & chosen streams
|
||||
*/
|
||||
fun getAudioDisplay(
|
||||
item: BaseItemDto,
|
||||
itemPlayback: ItemPlayback?,
|
||||
preferences: UserPreferences,
|
||||
) = chooseStream(item, itemPlayback, MediaStreamType.AUDIO, preferences)
|
||||
?.displayTitle
|
||||
?.replace(" - Default", "")
|
||||
?.ifBlank { null }
|
||||
|
||||
/**
|
||||
* Gets the selected subtitle language for the given item & chosen streams
|
||||
*
|
||||
* If none are chosen, returns a concatenated list of languages available
|
||||
*/
|
||||
@Composable
|
||||
fun getSubtitleDisplay(
|
||||
item: BaseItemDto,
|
||||
chosenStreams: ChosenStreams?,
|
||||
) = if (chosenStreams?.subtitlesDisabled == true) {
|
||||
stringResource(R.string.disabled)
|
||||
} else if (chosenStreams?.subtitleStream != null) {
|
||||
languageName(chosenStreams.subtitleStream.language)
|
||||
} else {
|
||||
chooseSource(item, chosenStreams?.itemPlayback)?.let {
|
||||
formatSubtitleLang(it.mediaStreams)
|
||||
}
|
||||
}
|
||||
|
||||
private val abbrevSuffixes = listOf("", "K", "M", "B")
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package com.github.damontecres.wholphin.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import coil3.compose.AsyncImage
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
|
||||
@Composable
|
||||
fun DetailsBackdropImage(
|
||||
backdropImageUrl: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (backdropImageUrl.isNotNullOrBlank()) {
|
||||
val gradientColor = MaterialTheme.colorScheme.background
|
||||
AsyncImage(
|
||||
model = backdropImageUrl,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
alignment = Alignment.TopEnd,
|
||||
modifier =
|
||||
modifier
|
||||
.fillMaxHeight(.85f)
|
||||
.alpha(.75f)
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
drawRect(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(Color.Transparent, gradientColor),
|
||||
startY = size.height * .5f,
|
||||
),
|
||||
)
|
||||
drawRect(
|
||||
Brush.horizontalGradient(
|
||||
colors = listOf(Color.Transparent, gradientColor),
|
||||
endX = 0f,
|
||||
startX = size.width * .75f,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -52,6 +52,7 @@ fun TitleValueText(
|
|||
playSoundOnFocus: Boolean = false,
|
||||
onClick: (() -> Unit)? = null,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
valueTextOverflow: TextOverflow = TextOverflow.Ellipsis,
|
||||
) {
|
||||
MaybeClickColumn(
|
||||
onClick = onClick,
|
||||
|
|
@ -70,7 +71,7 @@ fun TitleValueText(
|
|||
style = MaterialTheme.typography.labelLarge.copy(fontWeight = FontWeight.Normal),
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
overflow = valueTextOverflow,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,321 @@
|
|||
package com.github.damontecres.wholphin.ui.components
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.ProvideTextStyle
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||
import com.github.damontecres.wholphin.data.model.TrackIndex
|
||||
import com.github.damontecres.wholphin.data.model.chooseSource
|
||||
import com.github.damontecres.wholphin.data.model.chooseStream
|
||||
import com.github.damontecres.wholphin.preferences.AppThemeColors
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.FontAwesome
|
||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||
import com.github.damontecres.wholphin.ui.playback.audioStreamCount
|
||||
import com.github.damontecres.wholphin.ui.playback.embeddedSubtitleCount
|
||||
import com.github.damontecres.wholphin.ui.playback.externalSubtitlesCount
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
import com.github.damontecres.wholphin.util.languageName
|
||||
import com.github.damontecres.wholphin.util.profile.Codec
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.api.VideoRange
|
||||
import org.jellyfin.sdk.model.api.VideoRangeType
|
||||
|
||||
@Composable
|
||||
fun VideoStreamDetails(
|
||||
preferences: UserPreferences,
|
||||
dto: BaseItemDto,
|
||||
itemPlayback: ItemPlayback?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = modifier,
|
||||
) {
|
||||
val source = remember(dto, itemPlayback) { chooseSource(dto, itemPlayback) }
|
||||
|
||||
val videoStream =
|
||||
remember(dto, itemPlayback) {
|
||||
chooseStream(
|
||||
dto,
|
||||
itemPlayback,
|
||||
MediaStreamType.VIDEO,
|
||||
preferences,
|
||||
)
|
||||
}
|
||||
val video =
|
||||
remember(videoStream) {
|
||||
videoStream
|
||||
?.let {
|
||||
val width = it.width
|
||||
val height = it.height
|
||||
val resName =
|
||||
if (width != null && height != null) {
|
||||
resolutionString(
|
||||
width,
|
||||
height,
|
||||
videoStream.isInterlaced,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val range = formatVideoRange(context, it.videoRange, it.videoRangeType)
|
||||
listOfNotNull(
|
||||
resName.concatWithSpace(range),
|
||||
it.codec?.uppercase(),
|
||||
)
|
||||
}.orEmpty()
|
||||
}
|
||||
video.forEach {
|
||||
StreamLabel(it)
|
||||
}
|
||||
|
||||
val audioStream =
|
||||
remember(dto, itemPlayback) {
|
||||
chooseStream(
|
||||
dto,
|
||||
itemPlayback,
|
||||
MediaStreamType.AUDIO,
|
||||
preferences,
|
||||
)
|
||||
}
|
||||
val audioCount = remember(source) { source?.audioStreamCount ?: 0 }
|
||||
val audio =
|
||||
if (audioCount == 0 || audioStream == null) {
|
||||
stringResource(R.string.none)
|
||||
} else {
|
||||
listOfNotNull(
|
||||
languageName(audioStream.language),
|
||||
formatAudioCodec(context, audioStream.codec, audioStream.profile),
|
||||
audioStream.channelLayout,
|
||||
).joinToString(" ")
|
||||
}
|
||||
StreamLabel(
|
||||
text = audio,
|
||||
count = audioCount,
|
||||
icon = R.string.fa_volume_high,
|
||||
modifier = Modifier.widthIn(max = 200.dp),
|
||||
)
|
||||
|
||||
val subtitleStream =
|
||||
remember(dto, itemPlayback) {
|
||||
chooseStream(
|
||||
dto,
|
||||
itemPlayback,
|
||||
MediaStreamType.SUBTITLE,
|
||||
preferences,
|
||||
)
|
||||
}
|
||||
val subtitleCount =
|
||||
remember(source) {
|
||||
(source?.embeddedSubtitleCount ?: 0) + (source?.externalSubtitlesCount ?: 0)
|
||||
}
|
||||
val subtitle =
|
||||
if (itemPlayback?.subtitleIndex == TrackIndex.DISABLED) {
|
||||
stringResource(R.string.disabled)
|
||||
} else if (subtitleCount == 0 || subtitleStream == null) {
|
||||
null
|
||||
} else {
|
||||
listOfNotNull(
|
||||
languageName(subtitleStream.language),
|
||||
"SDH".takeIf { subtitleStream.isHearingImpaired },
|
||||
formatSubtitleCodec(subtitleStream.codec),
|
||||
).joinToString(" ")
|
||||
}
|
||||
subtitle?.let {
|
||||
StreamLabel(
|
||||
text = it,
|
||||
count = subtitleCount,
|
||||
icon = R.string.fa_closed_captioning,
|
||||
modifier = Modifier.widthIn(max = 160.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun interlaced(interlaced: Boolean) = if (interlaced) "i" else "p"
|
||||
|
||||
// Adapted from https://github.com/jellyfin/jellyfin/blob/aa4ddd139a7c01889a99561fc314121ba198dd70/MediaBrowser.Model/Entities/MediaStream.cs#L714
|
||||
fun resolutionString(
|
||||
width: Int,
|
||||
height: Int,
|
||||
interlaced: Boolean,
|
||||
): String =
|
||||
if (height > width) {
|
||||
// Vertical video
|
||||
resolutionString(height, width, interlaced)
|
||||
} else {
|
||||
when {
|
||||
width <= 256 && height <= 144 -> "144" + interlaced(interlaced)
|
||||
width <= 426 && height <= 240 -> "240" + interlaced(interlaced)
|
||||
width <= 640 && height <= 360 -> "360" + interlaced(interlaced)
|
||||
width <= 682 && height <= 384 -> "384" + interlaced(interlaced)
|
||||
width <= 720 && height <= 404 -> "404" + interlaced(interlaced)
|
||||
width <= 854 && height <= 480 -> "480" + interlaced(interlaced)
|
||||
width <= 960 && height <= 544 -> "540" + interlaced(interlaced)
|
||||
width <= 1024 && height <= 576 -> "576" + interlaced(interlaced)
|
||||
width <= 1280 && height <= 962 -> "720" + interlaced(interlaced)
|
||||
width <= 2560 && height <= 1440 -> "1080" + interlaced(interlaced)
|
||||
width <= 4096 && height <= 3072 -> "4K"
|
||||
width <= 8192 && height <= 6144 -> "8K"
|
||||
else -> height.toString() + interlaced(interlaced)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StreamLabel(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
@StringRes icon: Int? = null,
|
||||
count: Int = 0,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
modifier =
|
||||
modifier
|
||||
.background(
|
||||
MaterialTheme.colorScheme.secondaryContainer,
|
||||
// MaterialTheme.colorScheme.surfaceVariant,
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
).padding(vertical = 4.dp, horizontal = 6.dp),
|
||||
) {
|
||||
ProvideTextStyle(
|
||||
TextStyle(
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
),
|
||||
) {
|
||||
if (icon != null) {
|
||||
Text(
|
||||
text = stringResource(icon),
|
||||
fontFamily = FontAwesome,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = text,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier,
|
||||
)
|
||||
if (count > 1) {
|
||||
Text(
|
||||
text = "(+${count - 1})",
|
||||
maxLines = 1,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewTvSpec
|
||||
@Composable
|
||||
private fun StreamLabelPreview() {
|
||||
WholphinTheme(appThemeColors = AppThemeColors.PURPLE) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(8.dp),
|
||||
) {
|
||||
StreamLabel("1080p")
|
||||
StreamLabel("H264")
|
||||
StreamLabel("HDR")
|
||||
StreamLabel("AC3 5.1", icon = R.string.fa_volume_high, count = 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun formatVideoRange(
|
||||
context: Context,
|
||||
videoRange: VideoRange?,
|
||||
type: VideoRangeType?,
|
||||
): String? =
|
||||
when (videoRange) {
|
||||
VideoRange.UNKNOWN,
|
||||
VideoRange.SDR, null,
|
||||
-> null
|
||||
|
||||
VideoRange.HDR ->
|
||||
when (type) {
|
||||
VideoRangeType.UNKNOWN,
|
||||
VideoRangeType.SDR,
|
||||
null,
|
||||
-> null
|
||||
|
||||
VideoRangeType.HDR10 -> "HDR10"
|
||||
VideoRangeType.HDR10_PLUS -> "HDR10+"
|
||||
VideoRangeType.HLG -> "HLG"
|
||||
|
||||
VideoRangeType.DOVI,
|
||||
VideoRangeType.DOVI_WITH_HDR10,
|
||||
VideoRangeType.DOVI_WITH_HLG,
|
||||
VideoRangeType.DOVI_WITH_SDR,
|
||||
-> context.getString(R.string.dolby_vision)
|
||||
}
|
||||
}
|
||||
|
||||
fun formatAudioCodec(
|
||||
context: Context,
|
||||
codec: String?,
|
||||
profile: String?,
|
||||
): String? =
|
||||
when {
|
||||
profile?.contains("Dolby Atmos", true) == true -> context.getString(R.string.dolby_atmos)
|
||||
profile?.contains("DTS:X", true) == true -> "DTS:X"
|
||||
profile?.contains("DTS:HD", true) == true -> "DTS:HD"
|
||||
else ->
|
||||
when (codec?.lowercase()) {
|
||||
Codec.Audio.TRUEHD -> "TrueHD"
|
||||
Codec.Audio.OGG,
|
||||
Codec.Audio.OPUS,
|
||||
Codec.Audio.VORBIS,
|
||||
-> codec.replaceFirstChar { it.uppercase() }
|
||||
|
||||
null -> null
|
||||
else -> codec.uppercase()
|
||||
}
|
||||
}
|
||||
|
||||
fun formatSubtitleCodec(codec: String?): String? =
|
||||
when (codec?.lowercase()) {
|
||||
Codec.Subtitle.DVBSUB -> "DVB"
|
||||
Codec.Subtitle.DVDSUB -> "DVD"
|
||||
Codec.Subtitle.PGSSUB -> "PGS"
|
||||
Codec.Subtitle.SUBRIP -> "SRT"
|
||||
null -> null
|
||||
else -> codec.uppercase()
|
||||
}
|
||||
|
||||
fun String?.concatWithSpace(str: String?): String? =
|
||||
when {
|
||||
this != null && str != null -> "$this $str"
|
||||
this == null -> str
|
||||
else -> this
|
||||
}
|
||||
|
|
@ -4,10 +4,8 @@ import androidx.compose.foundation.layout.Arrangement
|
|||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
|
|
@ -22,16 +20,10 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.focusRestorer
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
|
@ -42,7 +34,6 @@ import androidx.lifecycle.compose.LifecycleResumeEffect
|
|||
import androidx.lifecycle.compose.LifecycleStartEffect
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import coil3.compose.AsyncImage
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ChosenStreams
|
||||
import com.github.damontecres.wholphin.data.ExtrasItem
|
||||
|
|
@ -63,12 +54,14 @@ import com.github.damontecres.wholphin.ui.cards.ExtrasRow
|
|||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||
import com.github.damontecres.wholphin.ui.cards.PersonRow
|
||||
import com.github.damontecres.wholphin.ui.cards.SeasonCard
|
||||
import com.github.damontecres.wholphin.ui.components.DetailsBackdropImage
|
||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButtons
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.Optional
|
||||
import com.github.damontecres.wholphin.ui.components.VideoStreamDetails
|
||||
import com.github.damontecres.wholphin.ui.components.chooseStream
|
||||
import com.github.damontecres.wholphin.ui.components.chooseVersionParams
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
|
|
@ -80,7 +73,6 @@ import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
|||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItems
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForPerson
|
||||
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
|
||||
|
|
@ -386,35 +378,7 @@ fun MovieDetailsContent(
|
|||
focusRequesters.getOrNull(position)?.tryRequestFocus()
|
||||
}
|
||||
Box(modifier = modifier) {
|
||||
if (backdropImageUrl.isNotNullOrBlank()) {
|
||||
val gradientColor = MaterialTheme.colorScheme.background
|
||||
AsyncImage(
|
||||
model = backdropImageUrl,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
alignment = Alignment.TopEnd,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight(.75f)
|
||||
.alpha(.5f)
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
drawRect(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(Color.Transparent, gradientColor),
|
||||
startY = size.height * .5f,
|
||||
),
|
||||
)
|
||||
drawRect(
|
||||
Brush.horizontalGradient(
|
||||
colors = listOf(Color.Transparent, gradientColor),
|
||||
endX = 0f,
|
||||
startX = size.width * .75f,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
DetailsBackdropImage(backdropImageUrl)
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
contentPadding = PaddingValues(horizontal = 32.dp, vertical = 8.dp),
|
||||
|
|
@ -422,11 +386,12 @@ fun MovieDetailsContent(
|
|||
) {
|
||||
item {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(0.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.bringIntoViewRequester(bringIntoViewRequester)
|
||||
.padding(bottom = 56.dp),
|
||||
.fillMaxWidth()
|
||||
.bringIntoViewRequester(bringIntoViewRequester),
|
||||
// .padding(bottom = 16.dp),
|
||||
) {
|
||||
MovieDetailsHeader(
|
||||
preferences = preferences,
|
||||
|
|
@ -434,9 +399,7 @@ fun MovieDetailsContent(
|
|||
chosenStreams = chosenStreams,
|
||||
bringIntoViewRequester = bringIntoViewRequester,
|
||||
overviewOnClick = overviewOnClick,
|
||||
Modifier
|
||||
.fillMaxWidth(.75f)
|
||||
.padding(bottom = 8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
ExpandablePlayButtons(
|
||||
resumePosition = resumePosition,
|
||||
|
|
@ -461,6 +424,14 @@ fun MovieDetailsContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
VideoStreamDetails(
|
||||
preferences = preferences,
|
||||
dto = dto,
|
||||
itemPlayback = chosenStreams?.itemPlayback,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
if (people.isNotEmpty()) {
|
||||
item {
|
||||
PersonRow(
|
||||
|
|
|
|||
|
|
@ -7,15 +7,13 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
|
@ -26,18 +24,13 @@ import androidx.tv.material3.Text
|
|||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ChosenStreams
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.chooseStream
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.components.DotSeparatedRow
|
||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||
import com.github.damontecres.wholphin.ui.components.SimpleStarRating
|
||||
import com.github.damontecres.wholphin.ui.components.TitleValueText
|
||||
import com.github.damontecres.wholphin.ui.getAudioDisplay
|
||||
import com.github.damontecres.wholphin.ui.getSubtitleDisplay
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||
import com.github.damontecres.wholphin.ui.timeRemaining
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.api.PersonKind
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
|
||||
|
|
@ -64,11 +57,12 @@ fun MovieDetailsHeader(
|
|||
style = MaterialTheme.typography.displayMedium,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.fillMaxWidth(.75f),
|
||||
)
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.alpha(0.75f),
|
||||
modifier = Modifier.fillMaxWidth(.60f),
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
|
|
@ -92,10 +86,15 @@ fun MovieDetailsHeader(
|
|||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
SimpleStarRating(
|
||||
dto.communityRating,
|
||||
Modifier.height(20.dp),
|
||||
)
|
||||
}
|
||||
dto.taglines?.firstOrNull()?.let { tagline ->
|
||||
Text(
|
||||
text = tagline,
|
||||
|
|
@ -128,59 +127,6 @@ fun MovieDetailsHeader(
|
|||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
// Key-Values
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(start = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
chooseStream(dto, chosenStreams?.itemPlayback, MediaStreamType.VIDEO, preferences)
|
||||
?.displayTitle
|
||||
?.let {
|
||||
TitleValueText(
|
||||
stringResource(R.string.video),
|
||||
it,
|
||||
modifier = Modifier.widthIn(max = 200.dp),
|
||||
)
|
||||
}
|
||||
val audioDisplay = getAudioDisplay(movie.data, chosenStreams, preferences)
|
||||
audioDisplay
|
||||
?.let {
|
||||
TitleValueText(
|
||||
stringResource(R.string.audio),
|
||||
it,
|
||||
modifier = Modifier.widthIn(max = 200.dp),
|
||||
)
|
||||
}
|
||||
|
||||
getSubtitleDisplay(movie.data, chosenStreams)
|
||||
?.let {
|
||||
if (it.isNotNullOrBlank()) {
|
||||
TitleValueText(
|
||||
stringResource(R.string.subtitles),
|
||||
it,
|
||||
modifier = Modifier.widthIn(max = 200.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
// TODO add writers, studio, etc to overview dialog
|
||||
// dto.studios?.letNotEmpty {
|
||||
// TitleValueText(
|
||||
// stringResource(R.string.studios),
|
||||
// it.joinToString(", ") { s -> s.name ?: "" },
|
||||
// modifier = Modifier.widthIn(max = 80.dp),
|
||||
// )
|
||||
// }
|
||||
// dto.genres?.letNotEmpty {
|
||||
// TitleValueText(
|
||||
// stringResource(R.string.genres),
|
||||
// it.joinToString(", "),
|
||||
// modifier = Modifier.widthIn(max = 80.dp),
|
||||
// )
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,25 +2,16 @@ package com.github.damontecres.wholphin.ui.detail.series
|
|||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ChosenStreams
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.chooseStream
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButtons
|
||||
import com.github.damontecres.wholphin.ui.components.TitleValueText
|
||||
import com.github.damontecres.wholphin.ui.getAudioDisplay
|
||||
import com.github.damontecres.wholphin.ui.getSubtitleDisplay
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import com.github.damontecres.wholphin.ui.components.VideoStreamDetails
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import kotlin.time.Duration
|
||||
|
||||
|
|
@ -54,40 +45,11 @@ fun FocusedEpisodeFooter(
|
|||
buttonOnFocusChanged = {},
|
||||
modifier = Modifier,
|
||||
)
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
chooseStream(dto, chosenStreams?.itemPlayback, MediaStreamType.VIDEO, preferences)
|
||||
?.displayTitle
|
||||
?.let {
|
||||
TitleValueText(
|
||||
stringResource(R.string.video),
|
||||
it,
|
||||
modifier = Modifier.widthIn(max = 160.dp),
|
||||
)
|
||||
}
|
||||
|
||||
val audioDisplay = getAudioDisplay(ep.data, chosenStreams, preferences)
|
||||
audioDisplay?.let {
|
||||
TitleValueText(
|
||||
stringResource(R.string.audio),
|
||||
it,
|
||||
modifier = Modifier.widthIn(max = 200.dp),
|
||||
)
|
||||
}
|
||||
|
||||
val subtitleText = getSubtitleDisplay(ep.data, chosenStreams)
|
||||
subtitleText
|
||||
?.let {
|
||||
if (it.isNotNullOrBlank()) {
|
||||
TitleValueText(
|
||||
stringResource(R.string.subtitles),
|
||||
it,
|
||||
modifier = Modifier.widthIn(max = 160.dp),
|
||||
VideoStreamDetails(
|
||||
preferences = preferences,
|
||||
dto = dto,
|
||||
itemPlayback = chosenStreams?.itemPlayback,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.data.ChosenStreams
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.components.DotSeparatedRow
|
||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||
import com.github.damontecres.wholphin.ui.components.SimpleStarRating
|
||||
|
|
@ -24,14 +26,16 @@ import org.jellyfin.sdk.model.extensions.ticks
|
|||
|
||||
@Composable
|
||||
fun FocusedEpisodeHeader(
|
||||
preferences: UserPreferences,
|
||||
ep: BaseItem?,
|
||||
chosenStreams: ChosenStreams?,
|
||||
overviewOnClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val dto = ep?.data
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
|
|
@ -24,16 +23,11 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
|
@ -42,7 +36,6 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|||
import androidx.lifecycle.compose.LifecycleStartEffect
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import coil3.compose.AsyncImage
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ExtrasItem
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
|
|
@ -56,6 +49,7 @@ import com.github.damontecres.wholphin.ui.cards.ItemRow
|
|||
import com.github.damontecres.wholphin.ui.cards.PersonRow
|
||||
import com.github.damontecres.wholphin.ui.cards.SeasonCard
|
||||
import com.github.damontecres.wholphin.ui.components.ConfirmDialog
|
||||
import com.github.damontecres.wholphin.ui.components.DetailsBackdropImage
|
||||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
|
|
@ -76,7 +70,6 @@ import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
|||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForPerson
|
||||
import com.github.damontecres.wholphin.ui.detail.movie.TrailerRow
|
||||
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.rememberInt
|
||||
|
|
@ -319,35 +312,7 @@ fun SeriesDetailsContent(
|
|||
Box(
|
||||
modifier = modifier,
|
||||
) {
|
||||
if (series.backdropImageUrl.isNotNullOrBlank()) {
|
||||
val gradientColor = MaterialTheme.colorScheme.background
|
||||
AsyncImage(
|
||||
model = series.backdropImageUrl,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
alignment = Alignment.TopEnd,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight(.75f)
|
||||
.alpha(.5f)
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
drawRect(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(Color.Transparent, gradientColor),
|
||||
startY = size.height * .5f,
|
||||
),
|
||||
)
|
||||
drawRect(
|
||||
Brush.horizontalGradient(
|
||||
colors = listOf(Color.Transparent, gradientColor),
|
||||
endX = 0f,
|
||||
startX = size.width * .75f,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
DetailsBackdropImage(series.backdropImageUrl)
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -27,23 +26,18 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
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.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Text
|
||||
import coil3.compose.AsyncImage
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ChosenStreams
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
|
|
@ -51,12 +45,12 @@ import com.github.damontecres.wholphin.preferences.UserPreferences
|
|||
import com.github.damontecres.wholphin.ui.AspectRatios
|
||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||
import com.github.damontecres.wholphin.ui.cards.BannerCard
|
||||
import com.github.damontecres.wholphin.ui.components.DetailsBackdropImage
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.TabRow
|
||||
import com.github.damontecres.wholphin.ui.formatDateTime
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.logTab
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import kotlin.time.Duration
|
||||
|
|
@ -103,35 +97,7 @@ fun SeriesOverviewContent(
|
|||
.height(460.dp)
|
||||
.bringIntoViewRequester(bringIntoViewRequester),
|
||||
) {
|
||||
if (backdropImageUrl.isNotNullOrBlank()) {
|
||||
val gradientColor = MaterialTheme.colorScheme.background
|
||||
AsyncImage(
|
||||
model = backdropImageUrl,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
alignment = Alignment.TopEnd,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight(.6f)
|
||||
.alpha(.4f)
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
drawRect(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(Color.Transparent, gradientColor),
|
||||
startY = 500f,
|
||||
),
|
||||
)
|
||||
drawRect(
|
||||
Brush.horizontalGradient(
|
||||
colors = listOf(gradientColor, Color.Transparent),
|
||||
endX = 400f,
|
||||
startX = 100f,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
DetailsBackdropImage(backdropImageUrl)
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
|
|
@ -180,9 +146,11 @@ fun SeriesOverviewContent(
|
|||
item {
|
||||
// Episode header
|
||||
FocusedEpisodeHeader(
|
||||
preferences = preferences,
|
||||
ep = focusedEpisode,
|
||||
chosenStreams = chosenStreams,
|
||||
overviewOnClick = overviewOnClick,
|
||||
modifier = Modifier.fillMaxWidth(.66f),
|
||||
modifier = Modifier.fillMaxWidth(.6f),
|
||||
)
|
||||
}
|
||||
item {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ val PurpleThemeColors =
|
|||
val onPrimaryContainerDark = Color(0xFFEADCFF)
|
||||
val secondaryDark = Color(0xFFD2BCFF)
|
||||
val onSecondaryDark = Color(0xFF3B167D)
|
||||
val secondaryContainerDark = Color(0xFF533295)
|
||||
val secondaryContainerDark = Color(0xFF48415B)
|
||||
val onSecondaryContainerDark = Color(0xFFC2A6FF)
|
||||
val tertiaryDark = Color(0xFFA071F8)
|
||||
val onTertiaryDark = Color(0xFF5E0052)
|
||||
|
|
|
|||
|
|
@ -146,7 +146,9 @@ fun checkForSupport(tracks: Tracks): List<TrackSupport> =
|
|||
}
|
||||
|
||||
fun languageName(code: String?): String =
|
||||
if (code != null) {
|
||||
if (code == "und") {
|
||||
"Unknown"
|
||||
} else if (code != null) {
|
||||
try {
|
||||
Locale(code).displayLanguage
|
||||
} catch (ex: Exception) {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
<string name="fa_list_ul" translatable="false"></string>
|
||||
<string name="fa_file_video" translatable="false"></string>
|
||||
<string name="fa_volume_low" translatable="false"></string>
|
||||
<string name="fa_volume_high" translatable="false"></string>
|
||||
<string name="fa_ellipsis" translatable="false"></string>
|
||||
<string name="fa_ellipsis_vertical" translatable="false"></string>
|
||||
<string name="fa_filter" translatable="false"></string>
|
||||
|
|
|
|||
|
|
@ -324,6 +324,8 @@
|
|||
<string name="year">Year</string>
|
||||
<string name="decade">Decade</string>
|
||||
<string name="remove">Remove</string>
|
||||
<string name="dolby_vision">Dolby Vision</string>
|
||||
<string name="dolby_atmos">Dolby Atmos</string>
|
||||
|
||||
<string-array name="theme_song_volume">
|
||||
<item>Disabled</item>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue