mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-24 03:36:51 +00:00
Compare commits
6 Commits
f75f18d2af
...
dddf6e4cfa
| Author | SHA1 | Date | |
|---|---|---|---|
| dddf6e4cfa | |||
| 7a04c59023 | |||
| 3384a6795b | |||
| dc5c324962 | |||
| 56fe574940 | |||
| 607bfca0c0 |
@@ -19,7 +19,7 @@ android {
|
||||
applicationId = "hu.bbara.purefin.tv"
|
||||
minSdk = 29
|
||||
targetSdk = 36
|
||||
versionCode = 2000003
|
||||
versionCode = 2000004
|
||||
versionName = "0.1"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -29,12 +29,13 @@ 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 hu.bbara.purefin.ui.common.badge.WatchStateBadge
|
||||
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
|
||||
import hu.bbara.purefin.core.model.EpisodeUiModel
|
||||
import hu.bbara.purefin.core.model.MediaUiModel
|
||||
import hu.bbara.purefin.core.model.MovieUiModel
|
||||
import hu.bbara.purefin.core.model.SeriesUiModel
|
||||
import hu.bbara.purefin.ui.common.badge.UnwatchedEpisodeBadge
|
||||
import hu.bbara.purefin.ui.common.badge.WatchStateBadge
|
||||
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
|
||||
import java.util.UUID
|
||||
|
||||
@Composable
|
||||
@@ -134,10 +135,17 @@ fun PosterCardContent(
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(indicatorPadding),
|
||||
watched = model.watched,
|
||||
started = (model.progress ?: 0f) > 0f
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
is SeriesUiModel -> {
|
||||
UnwatchedEpisodeBadge(
|
||||
unwatchedCount = model.unwatchedEpisodeCount,
|
||||
size = 26,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(indicatorPadding),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Column(
|
||||
|
||||
@@ -3,6 +3,7 @@ package hu.bbara.purefin.ui.screen.home.components
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.focusGroup
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -14,14 +15,21 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.selection.selectableGroup
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.focusProperties
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.semantics.selected
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.tv.material3.DrawerValue
|
||||
import androidx.tv.material3.Icon
|
||||
import androidx.tv.material3.NavigationDrawer
|
||||
@@ -117,6 +125,8 @@ fun TvDrawerHeader(
|
||||
AnimatedVisibility(visible = expanded) {
|
||||
Text(
|
||||
text = "Purefin",
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontSize = 16.sp,
|
||||
modifier = Modifier.testTag(TvDrawerTitleTag)
|
||||
)
|
||||
}
|
||||
@@ -132,13 +142,18 @@ private fun androidx.tv.material3.NavigationDrawerScope.TvNavigationDrawerRail(
|
||||
) {
|
||||
val expanded = drawerValue == DrawerValue.Open
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
var hasDrawerFocus by remember { mutableStateOf(false) }
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.background(scheme.surface.copy(alpha = 0.96f))
|
||||
.padding(horizontal = 12.dp, vertical = 24.dp)
|
||||
.selectableGroup(),
|
||||
.selectableGroup()
|
||||
.focusGroup()
|
||||
.onFocusChanged { state ->
|
||||
hasDrawerFocus = state.isFocused
|
||||
},
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
TvDrawerHeader(
|
||||
@@ -152,7 +167,8 @@ private fun androidx.tv.material3.NavigationDrawerScope.TvNavigationDrawerRail(
|
||||
onClick = { onDestinationSelected(destination.destination) },
|
||||
modifier = Modifier
|
||||
.testTag("$TvDrawerItemTagPrefix$index")
|
||||
.semantics { selected = isSelected },
|
||||
.semantics { selected = isSelected }
|
||||
.focusProperties { canFocus = hasDrawerFocus || isSelected },
|
||||
leadingContent = {
|
||||
Icon(
|
||||
imageVector = destination.icon,
|
||||
|
||||
@@ -304,7 +304,6 @@ private fun TvEpisodeCard(
|
||||
} else {
|
||||
WatchStateBadge(
|
||||
watched = episode.watched,
|
||||
started = (episode.progress ?: 0.0) > 0.0,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(8.dp)
|
||||
|
||||
@@ -17,7 +17,7 @@ android {
|
||||
applicationId = "hu.bbara.purefin"
|
||||
minSdk = 29
|
||||
targetSdk = 36
|
||||
versionCode = 1000008
|
||||
versionCode = 1000009
|
||||
versionName = "0.1"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -117,7 +117,6 @@ fun PosterCardContent(
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(indicatorPadding),
|
||||
watched = model.watched,
|
||||
started = (model.progress ?: 0f) > 0f
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
|
||||
@@ -26,11 +26,9 @@ fun ContinueWatchingSection(
|
||||
) {
|
||||
if (items.isEmpty()) return
|
||||
|
||||
val filteredItems = items.filter { !it.watched }
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
LaunchedEffect(filteredItems) {
|
||||
LaunchedEffect(items) {
|
||||
listState.scrollToItem(index = 0)
|
||||
}
|
||||
|
||||
@@ -46,7 +44,7 @@ fun ContinueWatchingSection(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = listState
|
||||
) {
|
||||
itemsIndexed(items = filteredItems, key = { _, item -> item.id }) { index, item ->
|
||||
itemsIndexed(items = items, key = { _, item -> item.id }) { index, item ->
|
||||
ContinueWatchingCard(
|
||||
item = item,
|
||||
sharedBoundsKey = homeMediaSharedBoundsKey("continue-$index", item.id),
|
||||
|
||||
@@ -6,13 +6,15 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import hu.bbara.purefin.core.model.EpisodeUiModel
|
||||
import hu.bbara.purefin.core.model.MediaUiModel
|
||||
import hu.bbara.purefin.core.model.MovieUiModel
|
||||
import hu.bbara.purefin.core.model.SeriesUiModel
|
||||
import hu.bbara.purefin.ui.common.badge.UnwatchedEpisodeBadge
|
||||
import hu.bbara.purefin.ui.common.badge.WatchStateBadge
|
||||
import hu.bbara.purefin.ui.common.card.MediaImageCard
|
||||
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsSource
|
||||
import hu.bbara.purefin.ui.common.media.rememberHomeMediaSharedBoundsClick
|
||||
import hu.bbara.purefin.core.model.EpisodeUiModel
|
||||
import hu.bbara.purefin.core.model.MediaUiModel
|
||||
import hu.bbara.purefin.core.model.MovieUiModel
|
||||
|
||||
@Composable
|
||||
internal fun HomeBrowseCard(
|
||||
@@ -42,10 +44,16 @@ internal fun HomeBrowseCard(
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(8.dp),
|
||||
watched = uiModel.watched,
|
||||
started = (uiModel.progress ?: 0f) > 0f
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
is SeriesUiModel -> {
|
||||
UnwatchedEpisodeBadge(
|
||||
unwatchedCount = uiModel.unwatchedEpisodeCount,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,13 +20,13 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import hu.bbara.purefin.core.feature.browse.library.LibraryViewModel
|
||||
import hu.bbara.purefin.core.navigation.LibraryDto
|
||||
import hu.bbara.purefin.ui.common.badge.WatchStateBadge
|
||||
import hu.bbara.purefin.ui.common.card.MediaImageCard
|
||||
import hu.bbara.purefin.core.model.EpisodeUiModel
|
||||
import hu.bbara.purefin.core.model.MediaUiModel
|
||||
import hu.bbara.purefin.core.model.MovieUiModel
|
||||
import hu.bbara.purefin.core.model.SeriesUiModel
|
||||
import hu.bbara.purefin.core.navigation.LibraryDto
|
||||
import hu.bbara.purefin.ui.common.badge.WatchStateBadge
|
||||
import hu.bbara.purefin.ui.common.card.MediaImageCard
|
||||
import hu.bbara.purefin.ui.screen.library.components.LibraryTopBar
|
||||
|
||||
@Composable
|
||||
@@ -98,7 +98,6 @@ internal fun LibraryPosterGrid(
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(8.dp),
|
||||
watched = item.watched,
|
||||
started = (item.progress ?: 0f) > 0f
|
||||
)
|
||||
}
|
||||
else -> Unit
|
||||
|
||||
@@ -59,13 +59,13 @@ import hu.bbara.purefin.core.download.DownloadState
|
||||
import hu.bbara.purefin.core.feature.content.series.SeriesViewModel
|
||||
import hu.bbara.purefin.core.image.ArtworkKind
|
||||
import hu.bbara.purefin.core.image.ImageUrlBuilder
|
||||
import hu.bbara.purefin.core.navigation.EpisodeDto
|
||||
import hu.bbara.purefin.core.navigation.Route
|
||||
import hu.bbara.purefin.model.CastMember
|
||||
import hu.bbara.purefin.model.Episode
|
||||
import hu.bbara.purefin.model.Season
|
||||
import hu.bbara.purefin.model.Series
|
||||
import hu.bbara.purefin.core.navigation.EpisodeDto
|
||||
import hu.bbara.purefin.navigation.LocalNavigationManager
|
||||
import hu.bbara.purefin.core.navigation.Route
|
||||
import hu.bbara.purefin.player.PlayerActivity
|
||||
import hu.bbara.purefin.ui.common.badge.WatchStateBadge
|
||||
import hu.bbara.purefin.ui.common.bar.MediaProgressBar
|
||||
@@ -376,7 +376,6 @@ private fun EpisodeCard(
|
||||
} else {
|
||||
WatchStateBadge(
|
||||
watched = episode.watched,
|
||||
started = (episode.progress ?: 0.0) > 0.0,
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(8.dp)
|
||||
|
||||
@@ -21,7 +21,7 @@ fun UnwatchedEpisodeBadge(
|
||||
unwatchedCount: Int,
|
||||
foregroundColor: Color = MaterialTheme.colorScheme.onPrimary,
|
||||
backgroundColor: Color = MaterialTheme.colorScheme.primary,
|
||||
size: Int = 24,
|
||||
size: Int = 28,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
if (unwatchedCount == 0) {
|
||||
@@ -31,16 +31,16 @@ fun UnwatchedEpisodeBadge(
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier
|
||||
.border(1.dp, backgroundColor.copy(alpha = 0.8f), CircleShape)
|
||||
.background(backgroundColor.copy(alpha = 0.8f), CircleShape)
|
||||
.border(1.dp, backgroundColor, CircleShape)
|
||||
.background(backgroundColor, CircleShape)
|
||||
.size(size.dp)
|
||||
.clip(CircleShape)
|
||||
) {
|
||||
Text(
|
||||
text = if (unwatchedCount > 9) "9+" else unwatchedCount.toString(),
|
||||
color = foregroundColor.copy(alpha = 0.8f),
|
||||
fontWeight = FontWeight.W900,
|
||||
fontSize = 15.sp
|
||||
text = if (unwatchedCount > 99) "99+" else unwatchedCount.toString(),
|
||||
color = foregroundColor,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 13.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,42 +22,33 @@ import androidx.compose.ui.unit.dp
|
||||
@Composable
|
||||
fun WatchStateBadge(
|
||||
watched: Boolean,
|
||||
started: Boolean,
|
||||
watchedColor: Color = MaterialTheme.colorScheme.onPrimary,
|
||||
watchedBackgroundColor: Color = MaterialTheme.colorScheme.primary,
|
||||
startedColor: Color = MaterialTheme.colorScheme.onSecondary,
|
||||
startedBackgroundColor: Color = MaterialTheme.colorScheme.secondary,
|
||||
size: Int = 24,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
if (!watched && !started) {
|
||||
if (!watched) {
|
||||
return
|
||||
}
|
||||
|
||||
val foregroundColor = if (watched) watchedColor.copy(alpha = 0.8f) else startedColor.copy(alpha = 0.3f)
|
||||
val backgroundColor = if (watched) watchedBackgroundColor.copy(alpha = 0.8f) else startedBackgroundColor.copy(alpha = 0.3f)
|
||||
val borderColor = if (watched) watchedBackgroundColor.copy(alpha = 0.8f) else startedBackgroundColor.copy(alpha = 0.8f)
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = modifier
|
||||
.border(1.dp, borderColor, CircleShape)
|
||||
.background(backgroundColor, CircleShape)
|
||||
.border(1.dp, watchedBackgroundColor, CircleShape)
|
||||
.background(watchedBackgroundColor, CircleShape)
|
||||
.size(size.dp)
|
||||
.clip(CircleShape)
|
||||
) {
|
||||
if (watched) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Check,
|
||||
contentDescription = "Check",
|
||||
tint = foregroundColor,
|
||||
tint = watchedColor,
|
||||
modifier = Modifier
|
||||
.padding(1.dp)
|
||||
.matchParentSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
@@ -65,15 +56,12 @@ private fun WatchStateBadgePreview() {
|
||||
Column {
|
||||
WatchStateBadge(
|
||||
watched = false,
|
||||
started = false
|
||||
)
|
||||
WatchStateBadge(
|
||||
watched = true,
|
||||
started = false
|
||||
)
|
||||
WatchStateBadge(
|
||||
watched = false,
|
||||
started = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ class MovieUiModel: MediaUiModel {
|
||||
override val secondaryText: String
|
||||
override val description: String
|
||||
private val prefixImageUrl: String
|
||||
override val watched: Boolean
|
||||
override val primaryImageUrl: String
|
||||
get() {
|
||||
return ImageUrlBuilder.finishImageUrl(
|
||||
@@ -49,6 +50,7 @@ class MovieUiModel: MediaUiModel {
|
||||
description = movie.synopsis
|
||||
prefixImageUrl = movie.imageUrlPrefix
|
||||
progress = (movie.progress?.toFloat() ?: 0f) / 100f
|
||||
watched = movie.watched
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -78,7 +80,9 @@ class SeriesUiModel : MediaUiModel {
|
||||
override val primaryText: String
|
||||
override val secondaryText: String
|
||||
override val description: String
|
||||
override val watched: Boolean
|
||||
private val prefixImageUrl: String
|
||||
val unwatchedEpisodeCount: Int
|
||||
override val primaryImageUrl: String
|
||||
get() {
|
||||
return ImageUrlBuilder.finishImageUrl(
|
||||
@@ -100,6 +104,8 @@ class SeriesUiModel : MediaUiModel {
|
||||
secondaryText = "${series.seasonCount} seasons"
|
||||
description = series.synopsis
|
||||
prefixImageUrl = series.imageUrlPrefix
|
||||
watched = series.unwatchedEpisodeCount == 0
|
||||
unwatchedEpisodeCount = series.unwatchedEpisodeCount
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +115,7 @@ class EpisodeUiModel : MediaUiModel {
|
||||
override val secondaryText: String
|
||||
override val description: String
|
||||
private val prefixImageUrl: String
|
||||
override val watched: Boolean
|
||||
override val primaryImageUrl: String
|
||||
get() {
|
||||
return ImageUrlBuilder.finishImageUrl(
|
||||
@@ -136,5 +143,6 @@ class EpisodeUiModel : MediaUiModel {
|
||||
progress = (episode.progress?.toFloat() ?: 0f) / 100f
|
||||
seriesId = episode.seriesId
|
||||
seasonId = episode.seasonId
|
||||
watched = episode.watched
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user