Compare commits
9 Commits
f107085385
...
ce539a1a5a
| Author | SHA1 | Date | |
|---|---|---|---|
| ce539a1a5a | |||
| b08b74bab3 | |||
| 6852e0b13a | |||
| 72f6a37203 | |||
| c85767cf25 | |||
| 8563b41111 | |||
| 3daf45a421 | |||
| 74c532140e | |||
| e8938a9290 |
@@ -12,9 +12,9 @@
|
||||
android:screenOrientation="portrait"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/purefin_logo"
|
||||
android:icon="@mipmap/purefin_logo_2"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/purefin_logo_round"
|
||||
android:roundIcon="@mipmap/purefin_logo_2_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Purefin">
|
||||
<activity
|
||||
|
||||
@@ -6,8 +6,6 @@ import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -40,7 +38,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import hu.bbara.purefin.common.ui.MediaCastRow
|
||||
import hu.bbara.purefin.common.ui.MediaMetaChip
|
||||
import hu.bbara.purefin.common.ui.MediaSynopsis
|
||||
import hu.bbara.purefin.common.ui.components.GhostIconButton
|
||||
import hu.bbara.purefin.common.ui.components.MediaActionButton
|
||||
@@ -114,7 +111,6 @@ internal fun EpisodeTopBar(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
internal fun EpisodeDetails(
|
||||
episode: Episode,
|
||||
@@ -134,37 +130,6 @@ internal fun EpisodeDetails(
|
||||
}
|
||||
|
||||
Column(modifier = modifier) {
|
||||
Text(
|
||||
text = episode.title,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 38.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "Episode ${episode.index}",
|
||||
color = scheme.onBackground,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
MediaMetaChip(text = episode.releaseDate)
|
||||
MediaMetaChip(text = episode.rating)
|
||||
MediaMetaChip(text = episode.runtime)
|
||||
MediaMetaChip(
|
||||
text = episode.format,
|
||||
background = scheme.primary.copy(alpha = 0.2f),
|
||||
border = scheme.primary.copy(alpha = 0.3f),
|
||||
textColor = scheme.primary
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
MediaSynopsis(
|
||||
synopsis = episode.synopsis
|
||||
)
|
||||
|
||||
@@ -4,30 +4,44 @@ import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import hu.bbara.purefin.common.ui.MediaMetaChip
|
||||
import hu.bbara.purefin.common.ui.PurefinWaitingScreen
|
||||
import hu.bbara.purefin.common.ui.components.MediaHero
|
||||
import hu.bbara.purefin.core.data.navigation.EpisodeDto
|
||||
import hu.bbara.purefin.core.data.navigation.LocalNavigationBackStack
|
||||
import hu.bbara.purefin.core.data.navigation.LocalNavigationManager
|
||||
import hu.bbara.purefin.core.data.navigation.Route
|
||||
import hu.bbara.purefin.core.model.Episode
|
||||
import hu.bbara.purefin.core.model.CastMember
|
||||
import hu.bbara.purefin.core.model.Episode
|
||||
import hu.bbara.purefin.feature.download.DownloadState
|
||||
import hu.bbara.purefin.feature.shared.content.episode.EpisodeScreenViewModel
|
||||
import hu.bbara.purefin.ui.theme.AppTheme
|
||||
@@ -39,7 +53,6 @@ fun EpisodeScreen(
|
||||
viewModel: EpisodeScreenViewModel = hiltViewModel(),
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val navigationManager = LocalNavigationManager.current
|
||||
val backStack = LocalNavigationBackStack.current
|
||||
val previousRoute = remember(backStack) { backStack.getOrNull(backStack.lastIndex - 1) }
|
||||
|
||||
@@ -120,12 +133,7 @@ private fun EpisodeScreenInternal(
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = episode.heroImageUrl,
|
||||
backgroundColor = MaterialTheme.colorScheme.background,
|
||||
heightFraction = 0.30f,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
EpisodeHeroSection(episode = episode)
|
||||
EpisodeDetails(
|
||||
episode = episode,
|
||||
downloadState = downloadState,
|
||||
@@ -139,6 +147,86 @@ private fun EpisodeScreenInternal(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EpisodeHeroSection(
|
||||
episode: Episode,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
|
||||
val sectionHeight = screenHeight * 0.4f
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(sectionHeight)
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = episode.heroImageUrl,
|
||||
backgroundColor = scheme.background,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.Transparent,
|
||||
scheme.background.copy(alpha = 0.5f),
|
||||
scheme.background
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = episode.title,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 38.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "Episode ${episode.index}",
|
||||
color = scheme.onBackground,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
EpisodeMetaChips(episode = episode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun EpisodeMetaChips(episode: Episode) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
MediaMetaChip(text = episode.releaseDate)
|
||||
MediaMetaChip(text = episode.rating)
|
||||
MediaMetaChip(text = episode.runtime)
|
||||
MediaMetaChip(
|
||||
text = episode.format,
|
||||
background = scheme.primary.copy(alpha = 0.2f),
|
||||
border = scheme.primary.copy(alpha = 0.3f),
|
||||
textColor = scheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun EpisodeScreenPreview() {
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.content.Intent
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -12,9 +11,7 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Add
|
||||
import androidx.compose.material.icons.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.Cast
|
||||
import androidx.compose.material.icons.outlined.Close
|
||||
@@ -33,7 +30,6 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import hu.bbara.purefin.common.ui.MediaCastRow
|
||||
import hu.bbara.purefin.common.ui.MediaMetaChip
|
||||
import hu.bbara.purefin.common.ui.MediaSynopsis
|
||||
import hu.bbara.purefin.common.ui.components.GhostIconButton
|
||||
import hu.bbara.purefin.common.ui.components.MediaActionButton
|
||||
@@ -89,30 +85,6 @@ internal fun MovieDetails(
|
||||
}
|
||||
|
||||
Column(modifier = modifier) {
|
||||
Text(
|
||||
text = movie.title,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 38.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
MediaMetaChip(text = movie.year)
|
||||
MediaMetaChip(text = movie.rating)
|
||||
MediaMetaChip(text = movie.runtime)
|
||||
MediaMetaChip(
|
||||
text = movie.format,
|
||||
background = scheme.primary.copy(alpha = 0.2f),
|
||||
border = scheme.primary.copy(alpha = 0.3f),
|
||||
textColor = scheme.primary
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
MediaSynopsis(
|
||||
synopsis = movie.synopsis
|
||||
)
|
||||
|
||||
@@ -4,21 +4,35 @@ import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import hu.bbara.purefin.common.ui.MediaMetaChip
|
||||
import hu.bbara.purefin.common.ui.PurefinWaitingScreen
|
||||
import hu.bbara.purefin.common.ui.components.MediaHero
|
||||
import hu.bbara.purefin.core.data.navigation.MovieDto
|
||||
@@ -93,12 +107,7 @@ private fun MovieScreenInternal(
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = movie.heroImageUrl,
|
||||
backgroundColor = MaterialTheme.colorScheme.background,
|
||||
heightFraction = 0.30f,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
MediaHeroSection(movie = movie)
|
||||
MovieDetails(
|
||||
movie = movie,
|
||||
downloadState = downloadState,
|
||||
@@ -112,6 +121,69 @@ private fun MovieScreenInternal(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MediaHeroSection(
|
||||
movie: Movie,
|
||||
modifier: Modifier = Modifier) {
|
||||
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
|
||||
val sectionHeight = screenHeight * 0.4f
|
||||
|
||||
Box (
|
||||
modifier = modifier
|
||||
.height(sectionHeight)
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = movie.heroImageUrl,
|
||||
backgroundColor = MaterialTheme.colorScheme.background,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.Transparent,
|
||||
scheme.background.copy(alpha = 0.5f),
|
||||
scheme.background
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
Column (
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = movie.title,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 38.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
MediaMetaChip(text = movie.year)
|
||||
MediaMetaChip(text = movie.rating)
|
||||
MediaMetaChip(text = movie.runtime)
|
||||
MediaMetaChip(
|
||||
text = movie.format,
|
||||
background = scheme.primary.copy(alpha = 0.2f),
|
||||
border = scheme.primary.copy(alpha = 0.3f),
|
||||
textColor = scheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun MovieScreenPreview() {
|
||||
|
||||
@@ -19,7 +19,13 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
@@ -128,28 +134,13 @@ private fun SeriesScreenInternal(
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = series.heroImageUrl,
|
||||
heightFraction = 0.30f,
|
||||
backgroundColor = MaterialTheme.colorScheme.background,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
SeriesHeroSection(series = series)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(bottom = innerPadding.calculateBottomPadding())
|
||||
) {
|
||||
Text(
|
||||
text = series.name,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 30.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 36.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
SeriesMetaChips(series = series)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
SeriesActionButtons(
|
||||
nextUpEpisode = nextUpEpisode,
|
||||
seriesDownloadState = seriesDownloadState,
|
||||
@@ -192,6 +183,58 @@ private fun SeriesScreenInternal(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SeriesHeroSection(
|
||||
series: Series,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
|
||||
val sectionHeight = screenHeight * 0.4f
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.height(sectionHeight)
|
||||
) {
|
||||
MediaHero(
|
||||
imageUrl = series.heroImageUrl,
|
||||
backgroundColor = scheme.background,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.Transparent,
|
||||
scheme.background.copy(alpha = 0.5f),
|
||||
scheme.background
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = series.name,
|
||||
color = scheme.onBackground,
|
||||
fontSize = 30.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 36.sp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
SeriesMetaChips(series = series)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SeriesScreenPreview() {
|
||||
|
||||
@@ -22,10 +22,10 @@ import hu.bbara.purefin.app.home.ui.homePreviewLibraryContent
|
||||
import hu.bbara.purefin.app.home.ui.homePreviewNextUp
|
||||
import hu.bbara.purefin.app.home.ui.search.HomeSearchOverlay
|
||||
import hu.bbara.purefin.feature.shared.home.ContinueWatchingItem
|
||||
import hu.bbara.purefin.feature.shared.home.SuggestedItem
|
||||
import hu.bbara.purefin.feature.shared.home.LibraryItem
|
||||
import hu.bbara.purefin.feature.shared.home.NextUpItem
|
||||
import hu.bbara.purefin.feature.shared.home.PosterItem
|
||||
import hu.bbara.purefin.feature.shared.home.SuggestedItem
|
||||
import hu.bbara.purefin.ui.theme.AppTheme
|
||||
import org.jellyfin.sdk.model.UUID
|
||||
|
||||
@@ -67,8 +67,6 @@ fun HomeScreen(
|
||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||
topBar = {
|
||||
HomeTopBar(
|
||||
title = "Watch now",
|
||||
subtitle = subtitle,
|
||||
onSearchClick = { isSearchVisible = true },
|
||||
onProfileClick = onProfileClick,
|
||||
onSettingsClick = onSettingsClick,
|
||||
|
||||
@@ -11,6 +11,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Person
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
@@ -81,13 +82,10 @@ fun DefaultTopBar(
|
||||
.size(56.dp)
|
||||
.clip(CircleShape),
|
||||
) {
|
||||
HomeAvatar(
|
||||
size = 56.dp,
|
||||
borderWidth = 1.dp,
|
||||
borderColor = scheme.outlineVariant,
|
||||
backgroundColor = scheme.secondaryContainer,
|
||||
icon = Icons.Outlined.Person,
|
||||
iconTint = scheme.onSecondaryContainer
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Person,
|
||||
contentDescription = "Profile",
|
||||
modifier = Modifier.size(30.dp),
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package hu.bbara.purefin.app.home.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
||||
@Composable
|
||||
fun HomeAvatar(
|
||||
size: Dp,
|
||||
borderWidth: Dp,
|
||||
borderColor: Color,
|
||||
backgroundColor: Color,
|
||||
icon: ImageVector,
|
||||
iconTint: Color,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(size)
|
||||
.clip(CircleShape)
|
||||
.border(borderWidth, borderColor, CircleShape)
|
||||
.background(backgroundColor),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = iconTint
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package hu.bbara.purefin.app.home.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -11,6 +13,7 @@ import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconButtonColors
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
@@ -20,16 +23,19 @@ 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.draw.clip
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import hu.bbara.purefin.common.ui.components.PurefinLogo
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HomeTopBar(
|
||||
title: String,
|
||||
subtitle: String,
|
||||
onSearchClick: () -> Unit,
|
||||
onProfileClick: () -> Unit,
|
||||
onSettingsClick: () -> Unit,
|
||||
@@ -41,42 +47,57 @@ fun HomeTopBar(
|
||||
|
||||
TopAppBar(
|
||||
title = {
|
||||
Column {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
Row(
|
||||
modifier = Modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
PurefinLogo(
|
||||
contentDescription = "Purefin",
|
||||
modifier = Modifier.size(48.dp),
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = scheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
text = "PureFin",
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontStyle = FontStyle.Italic,
|
||||
color = scheme.onSecondary
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = onSearchClick) {
|
||||
IconButton(
|
||||
onClick = onSearchClick,
|
||||
colors = IconButtonColors(
|
||||
containerColor = scheme.secondary,
|
||||
contentColor = scheme.onSecondary,
|
||||
disabledContainerColor = scheme.secondary,
|
||||
disabledContentColor = scheme.onSecondary),
|
||||
modifier = Modifier.size(50.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Search,
|
||||
contentDescription = "Search"
|
||||
contentDescription = "Search",
|
||||
modifier = Modifier.size(30.dp),
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.size(12.dp))
|
||||
IconButton(
|
||||
onClick = { isProfileMenuExpanded = true },
|
||||
colors = IconButtonColors(
|
||||
containerColor = scheme.secondary,
|
||||
contentColor = scheme.onSecondary,
|
||||
disabledContainerColor = scheme.secondary,
|
||||
disabledContentColor = scheme.onSecondary),
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.size(50.dp)
|
||||
.clip(CircleShape),
|
||||
) {
|
||||
HomeAvatar(
|
||||
size = 40.dp,
|
||||
borderWidth = 1.dp,
|
||||
borderColor = scheme.outlineVariant,
|
||||
backgroundColor = scheme.secondaryContainer,
|
||||
icon = Icons.Outlined.Person,
|
||||
iconTint = scheme.onSecondaryContainer
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Person,
|
||||
contentDescription = "Profile",
|
||||
modifier = Modifier.size(30.dp),
|
||||
)
|
||||
}
|
||||
DropdownMenu(
|
||||
|
||||
@@ -76,9 +76,6 @@ internal fun SuggestionCard(
|
||||
.padding(24.dp)
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
Text(
|
||||
text = item.title
|
||||
)
|
||||
Text(
|
||||
text = item.title,
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
|
||||
@@ -13,10 +13,12 @@ import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PageSize
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -32,13 +34,18 @@ fun SuggestionsSection(
|
||||
|
||||
val pagerState = rememberPagerState(pageCount = { items.size })
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
pagerState.scrollToPage(0)
|
||||
}
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(14.dp),
|
||||
modifier = modifier
|
||||
modifier = modifier.fillMaxWidth()
|
||||
) {
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
contentPadding = PaddingValues(start = 16.dp, end = 56.dp),
|
||||
pageSize = PageSize.Fixed(360.dp),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
pageSpacing = 16.dp,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) { page ->
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
package hu.bbara.purefin.common.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.TextUnitType
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun MediaPlayButton(
|
||||
backgroundColor: Color,
|
||||
foregroundColor: Color,
|
||||
text: String = "Play",
|
||||
subText: String? = null,
|
||||
size: Dp,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.height(size)
|
||||
.clip(CircleShape)
|
||||
.background(backgroundColor)
|
||||
.padding(start = 16.dp, end = 32.dp)
|
||||
.clickable { onClick() },
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.PlayArrow,
|
||||
contentDescription = "Play",
|
||||
tint = foregroundColor,
|
||||
modifier = Modifier.size(42.dp)
|
||||
)
|
||||
Column() {
|
||||
Text(
|
||||
text = text,
|
||||
color = foregroundColor,
|
||||
fontSize = TextUnit(
|
||||
value = 16f,
|
||||
type = TextUnitType.Sp
|
||||
)
|
||||
)
|
||||
subText?.let {
|
||||
Text(
|
||||
text = subText,
|
||||
color = foregroundColor.copy(alpha = 0.7f),
|
||||
fontSize = TextUnit(
|
||||
value = 14f,
|
||||
type = TextUnitType.Sp
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
@@ -22,6 +21,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -50,7 +50,7 @@ fun MediaResumeButton(
|
||||
.background(onPrimaryColor),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
ButtonContent(text = text, color = primaryColor)
|
||||
ButtonContent(text = text, color = Color.White)
|
||||
}
|
||||
|
||||
// Top layer: primary colors, clipped to the progress %
|
||||
@@ -71,7 +71,7 @@ fun MediaResumeButton(
|
||||
.background(primaryColor),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
ButtonContent(text = text, color = onPrimaryColor)
|
||||
ButtonContent(text = text, color = Color.White)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package hu.bbara.purefin.common.ui.components
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import hu.bbara.purefin.R
|
||||
|
||||
@Composable
|
||||
fun PurefinLogo(
|
||||
modifier: Modifier = Modifier,
|
||||
contentDescription: String? = null,
|
||||
contentScale: ContentScale = ContentScale.Fit,
|
||||
) {
|
||||
val resources = LocalContext.current.resources
|
||||
val logoBitmap = remember(resources) {
|
||||
BitmapFactory.decodeResource(resources, R.raw.logo_raw)?.asImageBitmap()
|
||||
}
|
||||
|
||||
logoBitmap?.let { bitmap ->
|
||||
Image(
|
||||
bitmap = bitmap,
|
||||
contentDescription = contentDescription,
|
||||
contentScale = contentScale,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
BIN
app/src/main/purefin_logo_2-playstore.png
Normal file
|
After Width: | Height: | Size: 86 KiB |
6
app/src/main/res/mipmap-anydpi-v26/purefin_logo_2.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/purefin_logo_2_background"/>
|
||||
<foreground android:drawable="@mipmap/purefin_logo_2_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/purefin_logo_2_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/purefin_logo_2_background"/>
|
||||
<foreground android:drawable="@mipmap/purefin_logo_2_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/purefin_logo_2_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
app/src/main/res/mipmap-hdpi/purefin_logo_2.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-hdpi/purefin_logo_2_foreground.webp
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
app/src/main/res/mipmap-hdpi/purefin_logo_2_round.webp
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
app/src/main/res/mipmap-mdpi/purefin_logo_2.webp
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/mipmap-mdpi/purefin_logo_2_foreground.webp
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
app/src/main/res/mipmap-mdpi/purefin_logo_2_round.webp
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
app/src/main/res/mipmap-xhdpi/purefin_logo_2.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xhdpi/purefin_logo_2_foreground.webp
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
app/src/main/res/mipmap-xhdpi/purefin_logo_2_round.webp
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/purefin_logo_2.webp
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/purefin_logo_2_foreground.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/purefin_logo_2_round.webp
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/purefin_logo_2.webp
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/purefin_logo_2_foreground.webp
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/purefin_logo_2_round.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/src/main/res/raw/logo_raw.png
Normal file
|
After Width: | Height: | Size: 242 KiB |
4
app/src/main/res/values/purefin_logo_2_background.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purefin_logo_2_background">#201337</color>
|
||||
</resources>
|
||||
@@ -152,16 +152,6 @@ class AppViewModel @Inject constructor(
|
||||
initialValue = emptyMap()
|
||||
)
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
appContentRepository.ensureReady()
|
||||
} catch (e: Exception) {
|
||||
// State is already set to Error by ensureReady; don't crash the app
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onLibrarySelected(id: UUID, name: String) {
|
||||
viewModelScope.launch {
|
||||
navigationManager.navigate(Route.LibraryRoute(library = LibraryDto(id = id, name = name)))
|
||||
|
||||