mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Improvements to overview/media info dialog (#580)
## Description * Splits video details into two columns * Show index number if multiple streams of the same type * Different font color for media section titles * Adjustments to spacing * Add hearing impaired flag to & remove AVC from subtitle info ### Related issues Closes #549
This commit is contained in:
parent
a797bd8267
commit
fd1feddab3
1 changed files with 163 additions and 91 deletions
|
|
@ -8,8 +8,10 @@ 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.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -57,30 +59,28 @@ fun ItemDetailsDialog(
|
|||
|
||||
ScrollableDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
width = 720.dp,
|
||||
maxHeight = 480.dp,
|
||||
itemSpacing = 4.dp,
|
||||
width = 680.dp,
|
||||
maxHeight = 440.dp,
|
||||
itemSpacing = 8.dp,
|
||||
) {
|
||||
item {
|
||||
Text(
|
||||
text = info.title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
}
|
||||
if (info.genres.isNotEmpty()) {
|
||||
item {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
Text(
|
||||
text = info.genres.joinToString(", "),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (info.overview.isNotNullOrBlank()) {
|
||||
item {
|
||||
Text(
|
||||
text = info.overview,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
text = info.title,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
)
|
||||
if (info.genres.isNotEmpty()) {
|
||||
Text(
|
||||
text = info.genres.joinToString(", "),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
)
|
||||
}
|
||||
if (info.overview.isNotNullOrBlank()) {
|
||||
Text(
|
||||
text = info.overview,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -89,13 +89,19 @@ fun ItemDetailsDialog(
|
|||
source.mediaStreams?.letNotEmpty { mediaStreams ->
|
||||
item {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
||||
// General file information
|
||||
item {
|
||||
val containerLabel = stringResource(R.string.container)
|
||||
MediaInfoSection(
|
||||
title = stringResource(R.string.general),
|
||||
title =
|
||||
titleIndex(
|
||||
stringResource(R.string.general),
|
||||
index,
|
||||
info.files.size,
|
||||
),
|
||||
items =
|
||||
buildList {
|
||||
source.container?.let { add(containerLabel to it) }
|
||||
|
|
@ -116,26 +122,30 @@ fun ItemDetailsDialog(
|
|||
}
|
||||
|
||||
// Video streams
|
||||
items(mediaStreams.filter { it.type == MediaStreamType.VIDEO }) { stream ->
|
||||
val videoStreams = mediaStreams.filter { it.type == MediaStreamType.VIDEO }
|
||||
itemsIndexed(videoStreams) { index, stream ->
|
||||
MediaInfoSection(
|
||||
title = videoLabel,
|
||||
items = buildVideoStreamInfo(context, stream),
|
||||
title = titleIndex(videoLabel, index, videoStreams.size),
|
||||
items = remember { buildVideoStreamInfo(context, stream) },
|
||||
additional = remember { buildVideoStreamInfoAdditional(context, stream) },
|
||||
)
|
||||
}
|
||||
|
||||
// Audio streams - display multiple per row
|
||||
items(
|
||||
mediaStreams
|
||||
.filter { it.type == MediaStreamType.AUDIO }
|
||||
.chunked(3),
|
||||
) { streamGroup ->
|
||||
val audioStreams = mediaStreams.filter { it.type == MediaStreamType.AUDIO }
|
||||
itemsIndexed(audioStreams.chunked(3)) { groupIndex, streamGroup ->
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
streamGroup.forEach { stream ->
|
||||
streamGroup.forEachIndexed { index, stream ->
|
||||
MediaInfoSection(
|
||||
title = audioLabel,
|
||||
title =
|
||||
titleIndex(
|
||||
audioLabel,
|
||||
groupIndex * 3 + index,
|
||||
audioStreams.size,
|
||||
),
|
||||
items = buildAudioStreamInfo(context, stream),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
|
@ -148,19 +158,21 @@ fun ItemDetailsDialog(
|
|||
}
|
||||
|
||||
// Subtitle streams - display multiple per row
|
||||
items(
|
||||
mediaStreams
|
||||
.filter { it.type == MediaStreamType.SUBTITLE }
|
||||
.chunked(3),
|
||||
) { streamGroup ->
|
||||
val subtitleStreams = mediaStreams.filter { it.type == MediaStreamType.SUBTITLE }
|
||||
itemsIndexed(subtitleStreams.chunked(3)) { groupIndex, streamGroup ->
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
streamGroup.forEach { stream ->
|
||||
streamGroup.forEachIndexed { index, stream ->
|
||||
MediaInfoSection(
|
||||
title = subtitleLabel,
|
||||
items = buildSubtitleStreamInfo(context, stream),
|
||||
title =
|
||||
titleIndex(
|
||||
subtitleLabel,
|
||||
groupIndex * 3 + index,
|
||||
subtitleStreams.size,
|
||||
),
|
||||
items = buildSubtitleStreamInfo(context, stream, showFilePath),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
|
|
@ -185,6 +197,7 @@ private fun MediaInfoSection(
|
|||
title: String,
|
||||
items: List<Pair<String, String>>,
|
||||
modifier: Modifier = Modifier,
|
||||
additional: List<Pair<String, String>> = listOf(),
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
|
|
@ -192,66 +205,86 @@ private fun MediaInfoSection(
|
|||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
items.forEach { (label, value) ->
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "$label: ",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
|
||||
)
|
||||
Text(
|
||||
text = value,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp),
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f, fill = false)) {
|
||||
items.forEach { (label, value) ->
|
||||
Row(
|
||||
modifier = Modifier,
|
||||
) {
|
||||
Text(
|
||||
text = "$label: ",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
|
||||
)
|
||||
Text(
|
||||
text = value,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (additional.isNotEmpty()) {
|
||||
Column(modifier = Modifier.weight(1f, fill = false)) {
|
||||
additional.forEach { (label, value) ->
|
||||
Row(
|
||||
modifier = Modifier,
|
||||
) {
|
||||
Text(
|
||||
text = "$label: ",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
|
||||
)
|
||||
Text(
|
||||
text = value,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun titleIndex(
|
||||
title: String,
|
||||
index: Int,
|
||||
total: Int,
|
||||
) = if (total > 1) {
|
||||
"$title (${index + 1})"
|
||||
} else {
|
||||
title
|
||||
}
|
||||
|
||||
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 titleLabel = context.getString(R.string.title)
|
||||
val codecLabel = context.getString(R.string.codec)
|
||||
val resolutionLabel = context.getString(R.string.resolution)
|
||||
val aspectRatioLabel = context.getString(R.string.aspect_ratio)
|
||||
val framerateLabel = context.getString(R.string.framerate)
|
||||
val bitrateLabel = context.getString(R.string.bitrate)
|
||||
val profileLabel = context.getString(R.string.profile)
|
||||
val levelLabel = context.getString(R.string.level)
|
||||
val interlacedLabel = context.getString(R.string.interlaced)
|
||||
val videoRangeLabel = context.getString(R.string.video_range)
|
||||
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}")
|
||||
}
|
||||
|
|
@ -259,23 +292,55 @@ private fun buildVideoStreamInfo(
|
|||
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.bitRate?.let { add(bitrateLabel to formatBytes(it, byteRateSuffixes)) }
|
||||
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 {
|
||||
|
||||
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.profile?.let { add(profileLabel to it) }
|
||||
stream.level?.let { add(levelLabel to it.toString()) }
|
||||
stream.isInterlaced.let { add(interlacedLabel to if (it) yesStr else noStr) }
|
||||
}
|
||||
|
||||
private fun buildVideoStreamInfoAdditional(
|
||||
context: Context,
|
||||
stream: MediaStream,
|
||||
): List<Pair<String, String>> =
|
||||
buildList {
|
||||
val yesStr = context.getString(R.string.yes)
|
||||
val noStr = context.getString(R.string.no)
|
||||
|
||||
val avcLabel = context.getString(R.string.avc)
|
||||
val anamorphicLabel = context.getString(R.string.anamorphic)
|
||||
val bitDepthLabel = context.getString(R.string.bit_depth)
|
||||
|
||||
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 dolbyVisionLabel = context.getString(R.string.dolby_vision)
|
||||
|
||||
val sdrStr = context.getString(R.string.sdr)
|
||||
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.isAvc?.let { add(avcLabel to if (it) yesStr else noStr) }
|
||||
stream.isAnamorphic?.let { add(anamorphicLabel to if (it) yesStr else noStr) }
|
||||
stream.bitDepth?.let { add(bitDepthLabel to "$it $bitUnit") }
|
||||
stream.videoRangeType?.let {
|
||||
val rangeTypeStr =
|
||||
when (it) {
|
||||
|
|
@ -304,7 +369,8 @@ private fun buildVideoStreamInfo(
|
|||
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()) }
|
||||
stream.nalLengthSize?.let { add(nalLabel to it) }
|
||||
stream.videoDoViTitle?.let { add(dolbyVisionLabel to it) }
|
||||
}
|
||||
|
||||
private fun buildAudioStreamInfo(
|
||||
|
|
@ -341,17 +407,18 @@ private fun buildAudioStreamInfo(
|
|||
private fun buildSubtitleStreamInfo(
|
||||
context: Context,
|
||||
stream: MediaStream,
|
||||
showPath: Boolean,
|
||||
): 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)
|
||||
val pathLabel = context.getString(R.string.path)
|
||||
|
||||
stream.title?.let { add(titleLabel to it) }
|
||||
stream.language?.let { add(languageLabel to languageName(it)) }
|
||||
|
|
@ -359,10 +426,15 @@ private fun buildSubtitleStreamInfo(
|
|||
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) }
|
||||
stream.isHearingImpaired?.let {
|
||||
add((stream.localizedHearingImpaired ?: "SDH") to if (it) yesStr else noStr)
|
||||
}
|
||||
if (showPath) {
|
||||
stream.path?.let { pathLabel to it }
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateAspectRatio(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue