TV Guide UI followup to #517 (#521)

## Description
- Instead of just replacing newline characters, collapse white-space
into single space characters which is roughly how the web UI does it
with CSS's `white-space: nowrap;`
- Use an actual arrow indicator instead of a `<` character for when a
program begins before the guide start

### Related issues
#517
https://github.com/damontecres/Wholphin/issues/434
https://github.com/damontecres/Wholphin/issues/435
https://github.com/damontecres/Wholphin/issues/436

### Screenshots
<img width="605" height="316" alt="image"
src="https://github.com/user-attachments/assets/6a5227b5-9960-4c6d-a086-a4a9867ea60b"
/>
This commit is contained in:
Ray 2025-12-20 14:47:28 -05:00 committed by GitHub
parent a65b93e064
commit 11dda19b8a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 53 additions and 38 deletions

View file

@ -13,6 +13,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@ -21,6 +22,8 @@ import androidx.tv.material3.Text
import androidx.tv.material3.contentColorFor import androidx.tv.material3.contentColorFor
import androidx.tv.material3.surfaceColorAtElevation import androidx.tv.material3.surfaceColorAtElevation
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.ui.FontAwesome
import java.time.LocalDateTime import java.time.LocalDateTime
@Composable @Composable
@ -56,14 +59,7 @@ fun Program(
RoundedCornerShape(cornerSize) RoundedCornerShape(cornerSize)
} }
} }
val title = val title = program.name ?: program.id.toString()
remember(startedBeforeGuide) {
if (startedBeforeGuide) {
"< "
} else {
""
} + (program.name ?: program.id.toString())
}
Box( Box(
modifier = modifier =
modifier modifier
@ -74,35 +70,51 @@ fun Program(
shape = shape, shape = shape,
), ),
) { ) {
Column( Row(
verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.fillMaxSize(),
modifier =
Modifier
.fillMaxSize()
.padding(4.dp),
) { ) {
Text( if (startedBeforeGuide) {
text = title, Text(
color = textColor, text = stringResource(R.string.fa_caret_left),
fontSize = 16.sp, fontFamily = FontAwesome,
maxLines = 1, color = textColor,
overflow = TextOverflow.Ellipsis, fontSize = 16.sp,
modifier = Modifier, modifier =
) Modifier
listOfNotNull( .align(Alignment.CenterVertically)
program.seasonEpisode?.let { "S${it.season} E${it.episode}" }, .padding(start = 2.dp),
program.subtitle, )
).joinToString(" - ") }
.ifBlank { null } Column(
?.let { verticalArrangement = Arrangement.spacedBy(4.dp),
Text( modifier =
text = it, Modifier
color = textColor, .fillMaxSize()
fontSize = 14.sp, .padding(start = 2.dp, end = 4.dp, top = 4.dp, bottom = 4.dp),
overflow = TextOverflow.Ellipsis, ) {
modifier = Modifier, Text(
) text = title,
} color = textColor,
fontSize = 16.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier,
)
listOfNotNull(
program.seasonEpisode?.let { "S${it.season} E${it.episode}" },
program.subtitle,
).joinToString(" - ")
.ifBlank { null }
?.let {
Text(
text = it,
color = textColor,
fontSize = 14.sp,
overflow = TextOverflow.Ellipsis,
modifier = Modifier,
)
}
}
} }
RecordingMarker( RecordingMarker(
isRecording = program.isRecording, isRecording = program.isRecording,

View file

@ -229,11 +229,12 @@ class LiveTvViewModel
} else { } else {
null null
} }
val name = (dto.seriesName ?: dto.name)?.replace(Regex("[\n\r]"), "") // Clean up name/subtitles by collapsing whitespace (including newlines) into single spaces
val name = (dto.seriesName ?: dto.name)?.replace(Regex("\\s+"), " ")
val subtitle = val subtitle =
dto.episodeTitle dto.episodeTitle
.takeIf { dto.isSeries ?: false } .takeIf { dto.isSeries ?: false }
?.replace(Regex("[\n\r]"), "") ?.replace(Regex("\\s+"), " ")
val p = val p =
TvProgram( TvProgram(
id = dto.id, id = dto.id,

View file

@ -14,6 +14,8 @@
<string name="fa_magnifying_glass_minus" translatable="false">&#xf010;</string> <string name="fa_magnifying_glass_minus" translatable="false">&#xf010;</string>
<string name="fa_caret_up" translatable="false">&#xf0d8;</string> <string name="fa_caret_up" translatable="false">&#xf0d8;</string>
<string name="fa_caret_down" translatable="false">&#xf0d7;</string> <string name="fa_caret_down" translatable="false">&#xf0d7;</string>
<string name="fa_caret_left" translatable="false">&#xf0d9;</string>
<string name="fa_caret_right" translatable="false">&#xf0da;</string>
<string name="fa_heart" translatable="false">&#xf004;</string> <string name="fa_heart" translatable="false">&#xf004;</string>
<string name="fa_video" translatable="false">&#xf03d;</string> <string name="fa_video" translatable="false">&#xf03d;</string>
<string name="fa_play" translatable="false">&#xf04b;</string> <string name="fa_play" translatable="false">&#xf04b;</string>