Server changes/Sonarr

This commit is contained in:
ponzischeme89
2026-07-27 21:06:51 +12:00
parent 62f6345a40
commit 8d6cf2f5a1
42 changed files with 2388 additions and 284 deletions
@@ -69,16 +69,25 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AutoAwesome
import androidx.compose.material.icons.filled.BrokenImage
import androidx.compose.material.icons.filled.CalendarMonth
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.ChevronLeft
import androidx.compose.material.icons.filled.ChevronRight
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.LiveTv
import androidx.compose.material.icons.filled.Movie
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.PlayCircleFilled
import androidx.compose.material.icons.filled.Recommend
import androidx.compose.material.icons.filled.SentimentVerySatisfied
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.filled.SkipNext
import androidx.compose.material.icons.filled.TheaterComedy
import androidx.compose.material.icons.filled.Tv
import androidx.compose.material.icons.filled.VideoLibrary
import androidx.tv.material3.Icon
import androidx.tv.material3.Button
import androidx.tv.material3.Text
@@ -120,6 +129,58 @@ data class HomeBrowseRow(
val showSecondaryMetadata: Boolean = true,
)
private data class HomeRowVisual(
val icon: ImageVector,
val colors: List<Color>,
)
private fun homeRowVisual(row: HomeBrowseRow): HomeRowVisual = when {
row.id == "continue" -> HomeRowVisual(
Icons.Default.PlayCircleFilled,
listOf(Color(0xFF3A7BD5), Color(0xFF6C4DFF)),
)
row.id == "next-up" -> HomeRowVisual(
Icons.Default.SkipNext,
listOf(Color(0xFF00A896), Color(0xFF28C2A0)),
)
row.kind == MediaRowKind.FAVORITES -> HomeRowVisual(
Icons.Default.Favorite,
listOf(Color(0xFFFF5C8A), Color(0xFFB84DFF)),
)
row.id == "latest-movies" -> HomeRowVisual(
Icons.Default.Movie,
listOf(Color(0xFFFF8A3D), Color(0xFFE34B6F)),
)
row.id == "sonarr-airing-today" -> HomeRowVisual(
Icons.Default.CalendarMonth,
listOf(Color(0xFF00A9CE), Color(0xFF4169E1)),
)
row.id == "curated:apple-tv" -> HomeRowVisual(
Icons.Default.LiveTv,
listOf(Color(0xFF252A31), Color(0xFF66717E)),
)
row.id == "curated:drama-shows" -> HomeRowVisual(
Icons.Default.TheaterComedy,
listOf(Color(0xFF6A3FB5), Color(0xFFB64272)),
)
row.id == "curated:comedy-shows" -> HomeRowVisual(
Icons.Default.SentimentVerySatisfied,
listOf(Color(0xFFFFB52E), Color(0xFFFF7433)),
)
row.id.startsWith("similar:") -> HomeRowVisual(
Icons.Default.AutoAwesome,
listOf(Color(0xFF8A5CF6), Color(0xFFE458A3)),
)
row.id == "recommended" -> HomeRowVisual(
Icons.Default.Recommend,
listOf(Color(0xFF45B649), Color(0xFF00A896)),
)
else -> HomeRowVisual(
Icons.Default.VideoLibrary,
listOf(Color(0xFF536976), Color(0xFF738A96)),
)
}
@Composable
fun TvNavigationRail(
selected: BrowseDestination,
@@ -160,11 +221,27 @@ fun TvNavigationRail(
) {
Image(
painter = painterResource(R.drawable.emby_logo),
contentDescription = "Emby",
contentDescription = "Memby",
modifier = Modifier.width(32.dp).height(27.dp),
)
if (expanded) {
Text("Emby", color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Bold)
Column(verticalArrangement = Arrangement.Center) {
Text(
"Memby",
color = Color.White,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
maxLines = 1,
)
Text(
"Matts Android TV client",
color = QuietText,
fontSize = 9.sp,
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
Spacer(Modifier.height(16.dp))
@@ -528,6 +605,10 @@ fun MediaQuickActionsOverlay(
@Composable
private fun MetadataContent(item: BaseItem, sectionLabel: String) {
if (item.isSonarrSchedule) {
ScheduleMetadataContent(item, sectionLabel)
return
}
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(7.dp),
@@ -574,6 +655,62 @@ private fun MetadataContent(item: BaseItem, sectionLabel: String) {
}
}
@Composable
private fun ScheduleMetadataContent(item: BaseItem, sectionLabel: String) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(7.dp),
) {
Text(
sectionLabel.uppercase(),
color = EmbyGreen,
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
letterSpacing = 1.2.sp,
)
Text(
item.name,
color = Color.White,
fontSize = 30.sp,
lineHeight = 34.sp,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
listOfNotNull(
item.membyEpisodeCode,
item.membyEpisodeTitle?.takeIf(String::isNotBlank),
).joinToString(""),
color = MutedText,
fontSize = 16.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
item.membyAirLabel?.takeIf(String::isNotBlank)?.let { MediaBadge(it.uppercase()) }
item.membyAvailabilityText?.takeIf(String::isNotBlank)?.let {
MediaBadge(it.uppercase())
}
}
Text(
item.overview?.takeIf(String::isNotBlank)
?: "Episode information will appear when Sonarr receives it.",
color = Color(0xFFD0D4D7),
fontSize = 15.sp,
lineHeight = 20.sp,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.fillMaxWidth(0.74f),
)
Text(
"Schedule information from Sonarr",
color = QuietText,
fontSize = 12.sp,
)
}
}
@Composable
private fun MetadataStatus(item: BaseItem) {
val position = item.userData?.playbackPositionTicks ?: 0L
@@ -656,6 +793,23 @@ fun MediaRow(
modifier = Modifier.fillMaxWidth().padding(horizontal = 36.dp),
verticalAlignment = Alignment.CenterVertically,
) {
val visual = homeRowVisual(row)
Box(
modifier = Modifier
.size(28.dp)
.clip(CircleShape)
.background(Brush.linearGradient(visual.colors))
.border(1.dp, Color.White.copy(alpha = 0.20f), CircleShape),
contentAlignment = Alignment.Center,
) {
Icon(
imageVector = visual.icon,
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(15.dp),
)
}
Spacer(Modifier.width(10.dp))
Text(
row.title,
color = Color(0xFFF1F3F4),
@@ -978,6 +1132,12 @@ private fun MediaCard(
}
}
}
if (item.isSonarrSchedule) {
ScheduleStatusBadge(
status = item.membyAvailability.orEmpty(),
modifier = Modifier.align(Alignment.TopEnd).padding(8.dp),
)
}
}
Text(
item.name,
@@ -1004,6 +1164,28 @@ private fun MediaCard(
}
}
@Composable
private fun ScheduleStatusBadge(status: String, modifier: Modifier = Modifier) {
val (label, color) = when (status) {
"available" -> "ADDED" to EmbyGreen
"downloading" -> "DOWNLOADING" to Color(0xFF5DA9FF)
"awaiting" -> "AWAITING" to Color(0xFFFFB454)
"unmonitored" -> "UNMONITORED" to QuietText
else -> "TODAY" to Color(0xFFE1E5E8)
}
Text(
text = label,
color = if (status == "available") Color(0xFF071008) else Color(0xFF090B0D),
fontSize = 9.sp,
fontWeight = FontWeight.Bold,
letterSpacing = 0.5.sp,
modifier = modifier
.clip(RoundedCornerShape(4.dp))
.background(color)
.padding(horizontal = 7.dp, vertical = 4.dp),
)
}
@Composable
fun FocusScaleContainer(
onFocused: () -> Unit,
@@ -1127,6 +1309,7 @@ private const val BACKDROP_SETTLE_DELAY_MS = 240L
private fun cardSubtitle(item: BaseItem, showProgress: Boolean, positionTicks: Long): String = when {
showProgress && positionTicks > 0L -> "Resume at ${formatTvPosition(positionTicks)}"
item.isSonarrSchedule -> item.membyAirLabel ?: "Airing today"
item.isEpisode -> item.seriesName ?: "Up next"
item.productionYear != null && item.runtimeMinutes != null ->
"${item.productionYear}${formatTvRuntime(requireNotNull(item.runtimeMinutes))}"
@@ -1141,6 +1324,10 @@ private fun cardDescription(item: BaseItem, progress: Float): String = buildStri
if (progress > 0f) append(", ${(progress * 100).toInt()} percent watched")
if (item.userData?.played == true) append(", watched")
if (item.isFavorite) append(", favourite")
if (item.isSonarrSchedule) {
item.membyAirLabel?.let { append(", ").append(it) }
item.membyAvailabilityText?.let { append(", ").append(it) }
}
}
private fun formatTvRuntime(minutes: Int): String {