mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Add media information dialog. (#416)
Addresses #414 --------- Co-authored-by: Damontecres <damontecres@gmail.com>
This commit is contained in:
parent
6d6156917c
commit
c3f6fd7f60
7 changed files with 401 additions and 105 deletions
|
|
@ -310,6 +310,9 @@ fun DialogPopup(
|
|||
@Composable
|
||||
fun ScrollableDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
width: Dp = 600.dp,
|
||||
maxHeight: Dp = 380.dp,
|
||||
itemSpacing: Dp = 8.dp,
|
||||
content: LazyListScope.() -> Unit,
|
||||
) {
|
||||
val scrollAmount = 100f
|
||||
|
|
@ -331,12 +334,12 @@ fun ScrollableDialog(
|
|||
LazyColumn(
|
||||
state = columnState,
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(itemSpacing),
|
||||
content = content,
|
||||
modifier =
|
||||
Modifier
|
||||
.width(600.dp)
|
||||
.heightIn(max = 380.dp)
|
||||
.width(width)
|
||||
.heightIn(max = maxHeight)
|
||||
.focusable()
|
||||
.background(
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
|
||||
|
|
|
|||
|
|
@ -1,28 +1,35 @@
|
|||
package com.github.damontecres.wholphin.ui.data
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
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.ui.byteRateSuffixes
|
||||
import com.github.damontecres.wholphin.ui.components.ScrollableDialog
|
||||
import com.github.damontecres.wholphin.ui.components.formatAudioCodec
|
||||
import com.github.damontecres.wholphin.ui.components.formatSubtitleCodec
|
||||
import com.github.damontecres.wholphin.ui.formatBytes
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.util.languageName
|
||||
import org.jellyfin.sdk.model.api.AudioSpatialFormat
|
||||
import org.jellyfin.sdk.model.api.MediaSourceInfo
|
||||
import org.jellyfin.sdk.model.api.MediaStream
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.api.VideoRange
|
||||
import org.jellyfin.sdk.model.api.VideoRangeType
|
||||
import java.util.Locale
|
||||
|
||||
data class ItemDetailsDialogInfo(
|
||||
|
|
@ -37,129 +44,336 @@ fun ItemDetailsDialog(
|
|||
info: ItemDetailsDialogInfo,
|
||||
showFilePath: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
) = ScrollableDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = info.title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
}
|
||||
if (info.genres.isNotEmpty()) {
|
||||
val context = LocalContext.current
|
||||
// Extract stringResource calls outside of ScrollableDialog's non-composable lambda
|
||||
val pathLabel = stringResource(R.string.path)
|
||||
val fileSizeLabel = stringResource(R.string.file_size)
|
||||
val videoLabel = stringResource(R.string.video)
|
||||
val audioLabel = stringResource(R.string.audio)
|
||||
val subtitleLabel = stringResource(R.string.subtitle)
|
||||
val bitrateLabel = stringResource(R.string.bitrate)
|
||||
|
||||
ScrollableDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
width = 720.dp,
|
||||
maxHeight = 480.dp,
|
||||
itemSpacing = 4.dp,
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = info.genres.joinToString(", "),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
text = info.title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (info.overview.isNotNullOrBlank()) {
|
||||
item {
|
||||
Text(
|
||||
text = info.overview,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
if (info.genres.isNotEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
text = info.genres.joinToString(", "),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (info.files.isNotEmpty()) {
|
||||
item {
|
||||
Spacer(Modifier.height(12.dp))
|
||||
if (info.overview.isNotNullOrBlank()) {
|
||||
item {
|
||||
Text(
|
||||
text = info.overview,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
items(info.files) { file ->
|
||||
ProvideTextStyle(MaterialTheme.typography.bodyMedium) {
|
||||
MediaSourceInfo(file, showFilePath, Modifier.padding(top = 12.dp))
|
||||
|
||||
// Show detailed media information for the selected source (first one if multiple)
|
||||
info.files.forEachIndexed { index, source ->
|
||||
source.mediaStreams?.letNotEmpty { mediaStreams ->
|
||||
item {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
// General file information
|
||||
item {
|
||||
val containerLabel = stringResource(R.string.container)
|
||||
MediaInfoSection(
|
||||
title = stringResource(R.string.general),
|
||||
items =
|
||||
buildList {
|
||||
source.container?.let { add(containerLabel to it) }
|
||||
if (showFilePath) {
|
||||
source.path?.let { add(pathLabel to it) }
|
||||
}
|
||||
source.size?.let {
|
||||
add(fileSizeLabel to formatBytes(it))
|
||||
}
|
||||
source.bitrate?.let {
|
||||
add(
|
||||
bitrateLabel to formatBytes(it, byteRateSuffixes),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Video streams
|
||||
items(mediaStreams.filter { it.type == MediaStreamType.VIDEO }) { stream ->
|
||||
MediaInfoSection(
|
||||
title = videoLabel,
|
||||
items = buildVideoStreamInfo(context, stream),
|
||||
)
|
||||
}
|
||||
|
||||
// Audio streams - display multiple per row
|
||||
items(
|
||||
mediaStreams
|
||||
.filter { it.type == MediaStreamType.AUDIO }
|
||||
.chunked(3),
|
||||
) { streamGroup ->
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
streamGroup.forEach { stream ->
|
||||
MediaInfoSection(
|
||||
title = audioLabel,
|
||||
items = buildAudioStreamInfo(context, stream),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
// Fill remaining space if less than 3 items
|
||||
repeat(3 - streamGroup.size) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subtitle streams - display multiple per row
|
||||
items(
|
||||
mediaStreams
|
||||
.filter { it.type == MediaStreamType.SUBTITLE }
|
||||
.chunked(3),
|
||||
) { streamGroup ->
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
streamGroup.forEach { stream ->
|
||||
MediaInfoSection(
|
||||
title = subtitleLabel,
|
||||
items = buildSubtitleStreamInfo(context, stream),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
// Fill remaining space if less than 3 items
|
||||
repeat(3 - streamGroup.size) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (index != info.files.lastIndex) {
|
||||
item {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MediaSourceInfo(
|
||||
source: MediaSourceInfo,
|
||||
showFilePath: Boolean,
|
||||
private fun MediaInfoSection(
|
||||
title: String,
|
||||
items: List<Pair<String, String>>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
modifier = modifier.padding(vertical = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.name) + ": ${source.name}",
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Text(
|
||||
text = "ID: ${source.id}",
|
||||
)
|
||||
if (showFilePath) {
|
||||
Text(
|
||||
text = stringResource(R.string.path) + ": ${source.path}",
|
||||
)
|
||||
}
|
||||
source.size?.let { size ->
|
||||
Text(
|
||||
text = stringResource(R.string.file_size) + ": ${formatBytes(size)}",
|
||||
)
|
||||
}
|
||||
source.bitrate?.let { bitrate ->
|
||||
Text(
|
||||
text =
|
||||
stringResource(R.string.bitrate) + ": ${
|
||||
formatBytes(
|
||||
bitrate,
|
||||
byteRateSuffixes,
|
||||
)
|
||||
}",
|
||||
)
|
||||
}
|
||||
source.mediaStreams?.letNotEmpty { streams ->
|
||||
streams.filter { it.type == MediaStreamType.VIDEO }.forEach { stream ->
|
||||
val data =
|
||||
buildList {
|
||||
if (stream.width != null && stream.height != null) {
|
||||
add("${stream.width}x${stream.height}")
|
||||
}
|
||||
stream.averageFrameRate?.let {
|
||||
add(String.format(Locale.getDefault(), "%.3f", it) + " fps")
|
||||
}
|
||||
stream.bitRate?.let { add(formatBytes(it, byteRateSuffixes)) }
|
||||
stream.codec?.let(::add)
|
||||
stream.profile?.let(::add)
|
||||
}
|
||||
items.forEach { (label, value) ->
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.video) + ": " + data.joinToString(" - "),
|
||||
text = "$label: ",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
|
||||
)
|
||||
}
|
||||
|
||||
streams.filter { it.type == MediaStreamType.AUDIO }.forEachIndexed { index, stream ->
|
||||
val data =
|
||||
buildList {
|
||||
stream.language?.let { add(languageName(it)) }
|
||||
stream.codec?.let(::add)
|
||||
stream.channelLayout?.let(::add)
|
||||
stream.bitRate?.let { add(formatBytes(it, byteRateSuffixes)) }
|
||||
if (stream.audioSpatialFormat != AudioSpatialFormat.NONE) add(stream.audioSpatialFormat.serialName)
|
||||
if (stream.isDefault) add(stringResource(R.string.default_track))
|
||||
}
|
||||
Text(
|
||||
text = stringResource(R.string.audio) + " ${index + 1}: " + data.joinToString(" - "),
|
||||
)
|
||||
}
|
||||
|
||||
streams.filter { it.type == MediaStreamType.SUBTITLE }.forEachIndexed { index, stream ->
|
||||
val data =
|
||||
buildList {
|
||||
stream.language?.let { add(languageName(it)) }
|
||||
stream.codec?.let(::add)
|
||||
if (stream.isDefault) add(stringResource(R.string.default_track))
|
||||
if (stream.isForced) add(stringResource(R.string.forced_track))
|
||||
if (stream.isExternal) add(stringResource(R.string.external_track))
|
||||
}
|
||||
Text(
|
||||
text =
|
||||
stringResource(R.string.subtitle) + " ${index + 1}: " +
|
||||
data.joinToString(" - "),
|
||||
text = value,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildVideoStreamInfo(
|
||||
context: Context,
|
||||
stream: MediaStream,
|
||||
): List<Pair<String, String>> =
|
||||
buildList {
|
||||
val titleLabel = context.getString(R.string.title)
|
||||
val codecLabel = context.getString(R.string.codec)
|
||||
val avcLabel = context.getString(R.string.avc)
|
||||
val profileLabel = context.getString(R.string.profile)
|
||||
val levelLabel = context.getString(R.string.level)
|
||||
val resolutionLabel = context.getString(R.string.resolution)
|
||||
val aspectRatioLabel = context.getString(R.string.aspect_ratio)
|
||||
val anamorphicLabel = context.getString(R.string.anamorphic)
|
||||
val interlacedLabel = context.getString(R.string.interlaced)
|
||||
val framerateLabel = context.getString(R.string.framerate)
|
||||
val bitrateLabel = context.getString(R.string.bitrate)
|
||||
val bitDepthLabel = context.getString(R.string.bit_depth)
|
||||
val videoRangeLabel = context.getString(R.string.video_range)
|
||||
val videoRangeTypeLabel = context.getString(R.string.video_range_type)
|
||||
val colorSpaceLabel = context.getString(R.string.color_space)
|
||||
val colorTransferLabel = context.getString(R.string.color_transfer)
|
||||
val colorPrimariesLabel = context.getString(R.string.color_primaries)
|
||||
val pixelFormatLabel = context.getString(R.string.pixel_format)
|
||||
val refFramesLabel = context.getString(R.string.ref_frames)
|
||||
val nalLabel = context.getString(R.string.nal)
|
||||
val yesStr = context.getString(R.string.yes)
|
||||
val noStr = context.getString(R.string.no)
|
||||
val sdrStr = context.getString(R.string.sdr)
|
||||
val hdrStr = context.getString(R.string.hdr)
|
||||
val hdr10Str = context.getString(R.string.hdr10)
|
||||
val hdr10PlusStr = context.getString(R.string.hdr10_plus)
|
||||
val hlgStr = context.getString(R.string.hlg)
|
||||
val bitUnit = context.getString(R.string.bit_unit)
|
||||
|
||||
stream.title?.let { add(titleLabel to it) }
|
||||
stream.codec?.let { add(codecLabel to it.uppercase()) }
|
||||
stream.isAvc?.let { add(avcLabel to if (it) yesStr else noStr) }
|
||||
stream.profile?.let { add(profileLabel to it) }
|
||||
stream.level?.let { add(levelLabel to it.toString()) }
|
||||
if (stream.width != null && stream.height != null) {
|
||||
add(resolutionLabel to "${stream.width}x${stream.height}")
|
||||
}
|
||||
if (stream.width != null && stream.height != null) {
|
||||
val aspectRatio = calculateAspectRatio(stream.width!!, stream.height!!)
|
||||
add(aspectRatioLabel to aspectRatio)
|
||||
}
|
||||
stream.isAnamorphic?.let { add(anamorphicLabel to if (it) yesStr else noStr) }
|
||||
stream.isInterlaced?.let { add(interlacedLabel to if (it) yesStr else noStr) }
|
||||
stream.averageFrameRate?.let {
|
||||
add(framerateLabel to String.format(Locale.getDefault(), "%.3f", it))
|
||||
}
|
||||
stream.bitRate?.let { add(bitrateLabel to formatBytes(it, byteRateSuffixes)) }
|
||||
stream.bitDepth?.let { add(bitDepthLabel to "$it $bitUnit") }
|
||||
stream.videoRange?.let {
|
||||
val rangeStr =
|
||||
when (it) {
|
||||
VideoRange.SDR -> sdrStr
|
||||
VideoRange.HDR -> hdrStr
|
||||
VideoRange.UNKNOWN -> null
|
||||
else -> null
|
||||
}
|
||||
rangeStr?.let { add(videoRangeLabel to it) }
|
||||
}
|
||||
stream.videoRangeType?.let {
|
||||
val rangeTypeStr =
|
||||
when (it) {
|
||||
VideoRangeType.SDR -> sdrStr
|
||||
|
||||
VideoRangeType.HDR10 -> hdr10Str
|
||||
|
||||
VideoRangeType.HDR10_PLUS -> hdr10PlusStr
|
||||
|
||||
VideoRangeType.HLG -> hlgStr
|
||||
|
||||
VideoRangeType.DOVI,
|
||||
VideoRangeType.DOVI_WITH_HDR10,
|
||||
VideoRangeType.DOVI_WITH_HLG,
|
||||
VideoRangeType.DOVI_WITH_SDR,
|
||||
-> context.getString(R.string.dolby_vision)
|
||||
|
||||
VideoRangeType.UNKNOWN -> null
|
||||
|
||||
else -> null
|
||||
}
|
||||
rangeTypeStr?.let { add(videoRangeTypeLabel to it) }
|
||||
}
|
||||
stream.colorSpace?.let { add(colorSpaceLabel to it) }
|
||||
stream.colorTransfer?.let { add(colorTransferLabel to it) }
|
||||
stream.colorPrimaries?.let { add(colorPrimariesLabel to it) }
|
||||
stream.pixelFormat?.let { add(pixelFormatLabel to it) }
|
||||
stream.refFrames?.let { add(refFramesLabel to it.toString()) }
|
||||
stream.nalLengthSize?.let { add(nalLabel to it.toString()) }
|
||||
}
|
||||
|
||||
private fun buildAudioStreamInfo(
|
||||
context: Context,
|
||||
stream: MediaStream,
|
||||
): List<Pair<String, String>> =
|
||||
buildList {
|
||||
val titleLabel = context.getString(R.string.title)
|
||||
val languageLabel = context.getString(R.string.language)
|
||||
val codecLabel = context.getString(R.string.codec)
|
||||
val layoutLabel = context.getString(R.string.layout)
|
||||
val channelsLabel = context.getString(R.string.channels)
|
||||
val bitrateLabel = context.getString(R.string.bitrate)
|
||||
val sampleRateLabel = context.getString(R.string.sample_rate)
|
||||
val defaultLabel = context.getString(R.string.default_track)
|
||||
val externalLabel = context.getString(R.string.external_track)
|
||||
val yesStr = context.getString(R.string.yes)
|
||||
val noStr = context.getString(R.string.no)
|
||||
val sampleRateUnit = context.getString(R.string.sample_rate_unit)
|
||||
|
||||
stream.title?.let { add(titleLabel to it) }
|
||||
stream.language?.let { add(languageLabel to languageName(it)) }
|
||||
stream.codec?.let {
|
||||
val formattedCodec = formatAudioCodec(context, it, stream.profile) ?: it.uppercase()
|
||||
add(codecLabel to formattedCodec)
|
||||
}
|
||||
stream.channelLayout?.let { add(layoutLabel to it) }
|
||||
stream.channels?.let { add(channelsLabel to it.toString()) }
|
||||
stream.bitRate?.let { add(bitrateLabel to formatBytes(it, byteRateSuffixes)) }
|
||||
stream.sampleRate?.let { add(sampleRateLabel to "$it $sampleRateUnit") }
|
||||
stream.isDefault?.let { add(defaultLabel to if (it) yesStr else noStr) }
|
||||
}
|
||||
|
||||
private fun buildSubtitleStreamInfo(
|
||||
context: Context,
|
||||
stream: MediaStream,
|
||||
): List<Pair<String, String>> =
|
||||
buildList {
|
||||
val titleLabel = context.getString(R.string.title)
|
||||
val languageLabel = context.getString(R.string.language)
|
||||
val codecLabel = context.getString(R.string.codec)
|
||||
val avcLabel = context.getString(R.string.avc)
|
||||
val defaultLabel = context.getString(R.string.default_track)
|
||||
val forcedLabel = context.getString(R.string.forced_track)
|
||||
val externalLabel = context.getString(R.string.external_track)
|
||||
val yesStr = context.getString(R.string.yes)
|
||||
val noStr = context.getString(R.string.no)
|
||||
|
||||
stream.title?.let { add(titleLabel to it) }
|
||||
stream.language?.let { add(languageLabel to languageName(it)) }
|
||||
stream.codec?.let {
|
||||
val formattedCodec = formatSubtitleCodec(it) ?: it.uppercase()
|
||||
add(codecLabel to formattedCodec)
|
||||
}
|
||||
stream.isAvc?.let { add(avcLabel to if (it) yesStr else noStr) }
|
||||
stream.isDefault?.let { add(defaultLabel to if (it) yesStr else noStr) }
|
||||
stream.isForced?.let { add(forcedLabel to if (it) yesStr else noStr) }
|
||||
stream.isExternal?.let { add(externalLabel to if (it) yesStr else noStr) }
|
||||
}
|
||||
|
||||
private fun calculateAspectRatio(
|
||||
width: Int,
|
||||
height: Int,
|
||||
): String {
|
||||
val gcd = gcd(width, height)
|
||||
val w = width / gcd
|
||||
val h = height / gcd
|
||||
return "$w:$h"
|
||||
}
|
||||
|
||||
private fun gcd(
|
||||
a: Int,
|
||||
b: Int,
|
||||
): Int = if (b == 0) a else gcd(b, a % b)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.content.Context
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
|
@ -43,6 +44,7 @@ data class MoreDialogActions(
|
|||
* @param navigateTo a function to trigger a navigation
|
||||
* @param onChooseVersion callback to pick a version of the item
|
||||
* @param onChooseTracks callback to pick a track for the given type of the item
|
||||
* @param onShowOverview callback to show overview dialog with media information
|
||||
*/
|
||||
fun buildMoreDialogItems(
|
||||
context: Context,
|
||||
|
|
@ -54,6 +56,7 @@ fun buildMoreDialogItems(
|
|||
actions: MoreDialogActions,
|
||||
onChooseVersion: () -> Unit,
|
||||
onChooseTracks: (MediaStreamType) -> Unit,
|
||||
onShowOverview: () -> Unit,
|
||||
): List<DialogItem> =
|
||||
buildList {
|
||||
add(
|
||||
|
|
@ -159,6 +162,16 @@ fun buildMoreDialogItems(
|
|||
},
|
||||
)
|
||||
}
|
||||
if (item.data.mediaSources?.isNotEmpty() == true) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.media_information),
|
||||
Icons.Default.Info,
|
||||
) {
|
||||
onShowOverview.invoke()
|
||||
},
|
||||
)
|
||||
}
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.play_with_transcoding),
|
||||
|
|
|
|||
|
|
@ -195,6 +195,18 @@ fun EpisodeDetails(
|
|||
)
|
||||
}
|
||||
},
|
||||
onShowOverview = {
|
||||
val source = chosenStreams?.source ?: ep.data.mediaSources?.firstOrNull()
|
||||
if (source != null) {
|
||||
overviewDialog =
|
||||
ItemDetailsDialogInfo(
|
||||
title = ep.name ?: context.getString(R.string.unknown),
|
||||
overview = ep.data.overview,
|
||||
genres = ep.data.genres.orEmpty(),
|
||||
files = listOf(source),
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -242,6 +242,17 @@ fun MovieDetails(
|
|||
)
|
||||
}
|
||||
},
|
||||
onShowOverview = {
|
||||
overviewDialog =
|
||||
ItemDetailsDialogInfo(
|
||||
title =
|
||||
movie.name
|
||||
?: context.getString(R.string.unknown),
|
||||
overview = movie.data.overview,
|
||||
genres = movie.data.genres.orEmpty(),
|
||||
files = movie.data.mediaSources.orEmpty(),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -278,6 +278,15 @@ fun SeriesOverview(
|
|||
)
|
||||
}
|
||||
},
|
||||
onShowOverview = {
|
||||
overviewDialog =
|
||||
ItemDetailsDialogInfo(
|
||||
title = ep.name ?: context.getString(R.string.unknown),
|
||||
overview = ep.data.overview,
|
||||
genres = ep.data.genres.orEmpty(),
|
||||
files = ep.data.mediaSources.orEmpty(),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@
|
|||
<string name="watch_live">Watch live</string>
|
||||
<string name="years_old">%1$d years old</string>
|
||||
<string name="play_with_transcoding">Play with transcoding</string>
|
||||
<string name="media_information">Media Information</string>
|
||||
<string name="show_clock">Show Clock</string>
|
||||
<string name="aired_episode_order">Aired Episode Order</string>
|
||||
<string name="create_playlist">Create new playlist</string>
|
||||
|
|
@ -354,6 +355,39 @@
|
|||
<string name="automatic">Automatic</string>
|
||||
<string name="username_or_password">Use username/password</string>
|
||||
<string name="login">Login</string>
|
||||
<string name="general">General</string>
|
||||
<string name="container">Container</string>
|
||||
<string name="title">Title</string>
|
||||
<string name="codec">Codec</string>
|
||||
<string name="profile">Profile</string>
|
||||
<string name="level">Level</string>
|
||||
<string name="resolution">Resolution</string>
|
||||
<string name="anamorphic">Anamorphic</string>
|
||||
<string name="interlaced">Interlaced</string>
|
||||
<string name="framerate">Framerate</string>
|
||||
<string name="bit_depth">Bit depth</string>
|
||||
<string name="video_range">Video range</string>
|
||||
<string name="video_range_type">Video range type</string>
|
||||
<string name="color_space">Color space</string>
|
||||
<string name="color_transfer">Color transfer</string>
|
||||
<string name="color_primaries">Color primaries</string>
|
||||
<string name="pixel_format">Pixel format</string>
|
||||
<string name="ref_frames">Ref frames</string>
|
||||
<string name="nal">NAL</string>
|
||||
<string name="language">Language</string>
|
||||
<string name="layout">Layout</string>
|
||||
<string name="channels">Channels</string>
|
||||
<string name="sample_rate">Sample rate</string>
|
||||
<string name="avc">AVC</string>
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
<string name="sdr">SDR</string>
|
||||
<string name="hdr">HDR</string>
|
||||
<string name="hdr10">HDR10</string>
|
||||
<string name="hdr10_plus">HDR10+</string>
|
||||
<string name="hlg">HLG</string>
|
||||
<string name="bit_unit">bit</string>
|
||||
<string name="sample_rate_unit">Hz</string>
|
||||
<string name="show_titles">Show titles</string>
|
||||
|
||||
<string-array name="theme_song_volume">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue