mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-22 19:50:54 -05:00
redesign account tab media views
This commit is contained in:
@@ -338,10 +338,6 @@ class MainActivity : MonetCompatActivity() {
|
||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val currentRoute = navBackStackEntry?.destination?.route
|
||||
|
||||
// if (currentRoute in searchableScreens) {
|
||||
// SearchBar(appBarTitle.value)
|
||||
// }
|
||||
|
||||
MainNavGraph(
|
||||
activity = this@MainActivity,
|
||||
appNavController = appNavController,
|
||||
|
||||
@@ -6,5 +6,5 @@ class AuthorDetails(
|
||||
@SerializedName("name") val name: String,
|
||||
@SerializedName("username") val username: String,
|
||||
@SerializedName("avatar_path") val avatarPath: String?,
|
||||
@SerializedName("rating") val rating: Int
|
||||
@SerializedName("rating") val rating: Float
|
||||
)
|
||||
@@ -456,8 +456,6 @@ fun RatingRing(
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(size)
|
||||
// .size(60.dp)
|
||||
// .padding(8.dp)
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
@@ -716,22 +714,6 @@ fun AvatarImage(
|
||||
size = size,
|
||||
character = text
|
||||
)
|
||||
// Box(
|
||||
// modifier = Modifier
|
||||
// .clip(CircleShape)
|
||||
// .size(size)
|
||||
// .background(color = MaterialTheme.colorScheme.tertiary)
|
||||
// ) {
|
||||
// Text(
|
||||
// modifier = Modifier
|
||||
// .fillMaxSize()
|
||||
// .padding(top = size / 5),
|
||||
// text = if (author.name.isNotEmpty()) author.name[0].uppercase() else author.username[0].toString(),
|
||||
// color = MaterialTheme.colorScheme.onTertiary,
|
||||
// textAlign = TextAlign.Center,
|
||||
// style = MaterialTheme.typography.titleLarge
|
||||
// )
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -739,8 +721,7 @@ fun AvatarImage(
|
||||
fun RoundedLetterImage(
|
||||
size: Dp,
|
||||
character: Char,
|
||||
modifier: Modifier = Modifier,
|
||||
topPadding: Dp = size / 5
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
@@ -750,8 +731,7 @@ fun RoundedLetterImage(
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = topPadding),
|
||||
.align(Alignment.Center),
|
||||
text = character.uppercase(),
|
||||
color = MaterialTheme.colorScheme.onTertiary,
|
||||
textAlign = TextAlign.Center,
|
||||
|
||||
@@ -11,24 +11,34 @@ import com.owenlejeune.tvtime.utils.SessionManager
|
||||
import org.koin.core.component.inject
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
sealed class AccountTabNavItem(stringRes: Int, route: String, val mediaType: MediaViewType, val screen: AccountNavComposableFun, val listFetchFun: ListFetchFun, val listType: KClass<*>): TabNavItem(route) {
|
||||
sealed class AccountTabNavItem(
|
||||
stringRes: Int,
|
||||
route: String,
|
||||
val mediaType: MediaViewType,
|
||||
val screen: AccountNavComposableFun,
|
||||
val listFetchFun: ListFetchFun,
|
||||
val listType: KClass<*>,
|
||||
val ordinal: Int
|
||||
): TabNavItem(route) {
|
||||
private val resourceUtils: ResourceUtils by inject()
|
||||
|
||||
override val name = resourceUtils.getString(stringRes)
|
||||
|
||||
companion object {
|
||||
val GuestItems = listOf(RatedMovies, RatedTvShows, RatedTvEpisodes)
|
||||
val AuthorizedItems = listOf(RatedMovies, RatedTvShows, RatedTvEpisodes, FavoriteMovies, FavoriteTvShows, MovieWatchlist, TvWatchlist)
|
||||
val GuestItems
|
||||
get() = listOf(RatedMovies, RatedTvShows, RatedTvEpisodes)
|
||||
|
||||
val AuthorizedItems
|
||||
get() = listOf(RatedMovies, RatedTvShows, RatedTvEpisodes, FavoriteMovies, FavoriteTvShows, MovieWatchlist, TvWatchlist).filter { it.ordinal > -1 }.sortedBy { it.ordinal }
|
||||
}
|
||||
|
||||
object RatedMovies: AccountTabNavItem(R.string.nav_rated_movies_title, "rated_movies_route", MediaViewType.MOVIE, screenContent, { SessionManager.currentSession?.ratedMovies ?: emptyList() }, RatedMovie::class)
|
||||
object RatedTvShows: AccountTabNavItem(R.string.nav_rated_shows_title, "rated_shows_route", MediaViewType.TV, screenContent, { SessionManager.currentSession?.ratedTvShows ?: emptyList() }, RatedTv::class)
|
||||
object RatedTvEpisodes: AccountTabNavItem(R.string.nav_rated_episodes_title, "rated_episodes_route", MediaViewType.EPISODE, screenContent, { SessionManager.currentSession?.ratedTvEpisodes ?: emptyList() }, RatedEpisode::class)
|
||||
// object Lists
|
||||
object FavoriteMovies: AccountTabNavItem(R.string.nav_favorite_movies_title, "favorite_movies_route", MediaViewType.MOVIE, screenContent, { SessionManager.currentSession?.favoriteMovies ?: emptyList() }, FavoriteMovie::class)
|
||||
object FavoriteTvShows: AccountTabNavItem(R.string.nav_favorite_tv_show_title, "favorite_shows_route", MediaViewType.TV, screenContent, { SessionManager.currentSession?.favoriteTvShows ?: emptyList() }, FavoriteTvSeries::class)
|
||||
object MovieWatchlist: AccountTabNavItem(R.string.nav_movie_watchlist_title, "movie_watchlist_route", MediaViewType.MOVIE, screenContent, { SessionManager.currentSession?.movieWatchlist ?: emptyList() }, WatchlistMovie::class)
|
||||
object TvWatchlist: AccountTabNavItem(R.string.nav_tv_watchlist_title, "tv_watchlist_route", MediaViewType.TV, screenContent, { SessionManager.currentSession?.tvWatchlist ?: emptyList() }, WatchlistTvSeries::class)
|
||||
object RatedMovies: AccountTabNavItem(R.string.nav_rated_movies_title, "rated_movies_route", MediaViewType.MOVIE, screenContent, { SessionManager.currentSession?.ratedMovies ?: emptyList() }, RatedMovie::class, 0)
|
||||
object RatedTvShows: AccountTabNavItem(R.string.nav_rated_shows_title, "rated_shows_route", MediaViewType.TV, screenContent, { SessionManager.currentSession?.ratedTvShows ?: emptyList() }, RatedTv::class, 1)
|
||||
object RatedTvEpisodes: AccountTabNavItem(R.string.nav_rated_episodes_title, "rated_episodes_route", MediaViewType.EPISODE, screenContent, { SessionManager.currentSession?.ratedTvEpisodes ?: emptyList() }, RatedEpisode::class, 2)
|
||||
object FavoriteMovies: AccountTabNavItem(R.string.nav_favorite_movies_title, "favorite_movies_route", MediaViewType.MOVIE, screenContent, { SessionManager.currentSession?.favoriteMovies ?: emptyList() }, FavoriteMovie::class, 3)
|
||||
object FavoriteTvShows: AccountTabNavItem(R.string.nav_favorite_tv_show_title, "favorite_shows_route", MediaViewType.TV, screenContent, { SessionManager.currentSession?.favoriteTvShows ?: emptyList() }, FavoriteTvSeries::class, 4)
|
||||
object MovieWatchlist: AccountTabNavItem(R.string.nav_movie_watchlist_title, "movie_watchlist_route", MediaViewType.MOVIE, screenContent, { SessionManager.currentSession?.movieWatchlist ?: emptyList() }, WatchlistMovie::class, 5)
|
||||
object TvWatchlist: AccountTabNavItem(R.string.nav_tv_watchlist_title, "tv_watchlist_route", MediaViewType.TV, screenContent, { SessionManager.currentSession?.tvWatchlist ?: emptyList() }, WatchlistTvSeries::class, 6)
|
||||
}
|
||||
|
||||
private val screenContent: AccountNavComposableFun = { appNavController, mediaViewType, listFetchFun, clazz ->
|
||||
|
||||
@@ -47,6 +47,7 @@ fun MainNavGraph(
|
||||
}
|
||||
composable(BottomNavItem.Account.route) {
|
||||
AccountTab(appBarTitle = appBarTitle, appNavController = appNavController, appBarActions = appBarActions)
|
||||
fab.value = {}
|
||||
}
|
||||
composable(BottomNavItem.People.route) {
|
||||
appBarActions.value = {}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package com.owenlejeune.tvtime.ui.screens.main
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccountCircle
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
@@ -15,13 +16,19 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.constraintlayout.compose.Dimension
|
||||
import androidx.navigation.NavHostController
|
||||
import coil.compose.AsyncImage
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
@@ -43,7 +50,6 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.java.KoinJavaComponent
|
||||
import org.koin.java.KoinJavaComponent.get
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@@ -184,9 +190,11 @@ fun <T: Any> AccountTabContent(
|
||||
mediaViewType = mediaViewType,
|
||||
id = item.id,
|
||||
posterPath = TmdbUtils.getFullPosterPath(item.posterPath),
|
||||
backdropPath = TmdbUtils.getFullBackdropPath(item.backdropPath),
|
||||
name = item.name,
|
||||
date = item.releaseDate,
|
||||
rating = item.rating
|
||||
rating = item.rating,
|
||||
description = item.overview
|
||||
)
|
||||
}
|
||||
RatedEpisode::class -> {
|
||||
@@ -197,7 +205,8 @@ fun <T: Any> AccountTabContent(
|
||||
id = item.id,
|
||||
name = item.name,
|
||||
date = item.releaseDate,
|
||||
rating = item.rating
|
||||
rating = item.rating,
|
||||
description = item.overview
|
||||
)
|
||||
}
|
||||
FavoriteMovie::class, FavoriteTvSeries::class -> {
|
||||
@@ -207,8 +216,10 @@ fun <T: Any> AccountTabContent(
|
||||
mediaViewType = mediaViewType,
|
||||
id = item.id,
|
||||
posterPath = TmdbUtils.getFullPosterPath(item.posterPath),
|
||||
backdropPath = TmdbUtils.getFullBackdropPath(item.backdropPath),
|
||||
name = item.title,
|
||||
date = item.releaseDate
|
||||
date = item.releaseDate,
|
||||
description = item.overview
|
||||
)
|
||||
}
|
||||
WatchlistMovie::class, WatchlistTvSeries::class -> {
|
||||
@@ -218,8 +229,10 @@ fun <T: Any> AccountTabContent(
|
||||
mediaViewType = mediaViewType,
|
||||
id = item.id,
|
||||
posterPath = TmdbUtils.getFullPosterPath(item.posterPath),
|
||||
backdropPath = TmdbUtils.getFullBackdropPath(item.backdropPath),
|
||||
name = item.title,
|
||||
date = item.releaseDate
|
||||
date = item.releaseDate,
|
||||
description = item.overview
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -228,6 +241,7 @@ fun <T: Any> AccountTabContent(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun MediaItemRow(
|
||||
appNavController: NavHostController,
|
||||
@@ -235,12 +249,18 @@ private fun MediaItemRow(
|
||||
id: Int,
|
||||
name: String,
|
||||
date: String,
|
||||
description: String,
|
||||
posterPath: String? = null,
|
||||
backdropPath: String? = null,
|
||||
rating: Float? = null
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier.clickable(
|
||||
Card(
|
||||
shape = RoundedCornerShape(10.dp),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 10.dp),
|
||||
modifier = Modifier
|
||||
.background(color = MaterialTheme.colorScheme.surface)
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
onClick = {
|
||||
appNavController.navigate(
|
||||
"${MainNavItem.DetailView.route}/${mediaViewType}/${id}"
|
||||
@@ -248,37 +268,94 @@ private fun MediaItemRow(
|
||||
}
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.height(112.dp)
|
||||
) {
|
||||
AsyncImage(
|
||||
model = backdropPath,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.blur(radius = 10.dp)
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
)
|
||||
|
||||
backdropPath?.let {
|
||||
Box(modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black.copy(alpha = 0.7f))
|
||||
.blur(radius = 10.dp)
|
||||
)
|
||||
}
|
||||
|
||||
ConstraintLayout(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
val (poster, content, ratingView) = createRefs()
|
||||
|
||||
AsyncImage(
|
||||
modifier = Modifier
|
||||
.size(width = 60.dp, height = 80.dp),
|
||||
.constrainAs(poster) {
|
||||
start.linkTo(parent.start)
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
height = Dimension.fillToConstraints
|
||||
}
|
||||
.clip(RoundedCornerShape(10.dp)),
|
||||
model = posterPath,
|
||||
contentDescription = ""
|
||||
contentDescription = "",
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.height(80.dp),
|
||||
verticalArrangement = Arrangement.SpaceBetween
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier
|
||||
.constrainAs(content) {
|
||||
end.linkTo(
|
||||
rating?.let { ratingView.start } ?: parent.end,
|
||||
margin = 8.dp
|
||||
)
|
||||
start.linkTo(poster.end, margin = 8.dp)
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
width = Dimension.fillToConstraints
|
||||
height = Dimension.fillToConstraints
|
||||
}
|
||||
) {
|
||||
val releaseYear = date.split("-")[0]
|
||||
Text(
|
||||
text = name,
|
||||
text = "$name (${releaseYear})",
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
fontSize = 18.sp
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
Text(
|
||||
text = date,
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
text = description,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
fontSize = 14.sp,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
|
||||
if (rating != null) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.rating_test, (rating * 10).toInt()),
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
rating?.let {
|
||||
RatingView(
|
||||
progress = rating/10,
|
||||
modifier = Modifier
|
||||
.constrainAs(ratingView) {
|
||||
end.linkTo(parent.end)
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AccountDropdownMenu(
|
||||
@@ -290,7 +367,6 @@ private fun AccountDropdownMenu(
|
||||
IconButton(
|
||||
onClick = { expanded.value = true }
|
||||
) {
|
||||
// Icon(imageVector = Icons.Filled.MoreVert, contentDescription = null)
|
||||
Icon(imageVector = Icons.Filled.AccountCircle, contentDescription = stringResource(id = R.string.nav_account_title))
|
||||
}
|
||||
|
||||
@@ -372,18 +448,17 @@ private fun NoSessionMenuItems(
|
||||
}
|
||||
}
|
||||
|
||||
if (!preferences.useV4Api) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(id = R.string.action_sign_in)) },
|
||||
onClick = { showSignInDialog.value = true }
|
||||
)
|
||||
} else {
|
||||
val context = LocalContext.current
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(id = R.string.action_sign_in)) },
|
||||
onClick = { v4SignInPart1(context) }
|
||||
)
|
||||
onClick = {
|
||||
if (preferences.useV4Api) {
|
||||
v4SignInPart1(context)
|
||||
} else {
|
||||
showSignInDialog.value = true
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = stringResource(id = R.string.action_sign_in_as_guest)) },
|
||||
@@ -414,7 +489,7 @@ private fun v4SignInPart2(lastSelectedOption: MutableState<String>) {
|
||||
@Composable
|
||||
private fun GuestSessionIcon() {
|
||||
val guestName = stringResource(id = R.string.account_name_guest)
|
||||
RoundedLetterImage(size = 60.dp, character = guestName[0], topPadding = 60.dp / 4)
|
||||
RoundedLetterImage(size = 60.dp, character = guestName[0])
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -435,7 +510,7 @@ private fun AuthorizedSessionIcon() {
|
||||
Box(modifier = Modifier.padding(start = 12.dp)) {
|
||||
if (accountDetails == null || avatarUrl == null) {
|
||||
val accLetter = (accountDetails?.name?.ifEmpty { accountDetails.username } ?: " ")[0]
|
||||
RoundedLetterImage(size = 60.dp, character = accLetter, topPadding = 60.dp / 4)
|
||||
RoundedLetterImage(size = 60.dp, character = accLetter)
|
||||
} else {
|
||||
Box(modifier = Modifier.size(60.dp)) {
|
||||
AsyncImage(
|
||||
|
||||
@@ -75,7 +75,7 @@ fun MediaDetailView(
|
||||
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
||||
val topAppBarScrollState = rememberTopAppBarScrollState()
|
||||
val scrollBehavior = remember(decayAnimationSpec) {
|
||||
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec, topAppBarScrollState)
|
||||
TopAppBarDefaults.pinnedScrollBehavior(topAppBarScrollState)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
@@ -84,7 +84,7 @@ fun MediaDetailView(
|
||||
SmallTopAppBar(
|
||||
scrollBehavior = scrollBehavior,
|
||||
colors = TopAppBarDefaults
|
||||
.largeTopAppBarColors(
|
||||
.smallTopAppBarColors(
|
||||
scrolledContainerColor = MaterialTheme.colorScheme.background,
|
||||
titleContentColor = MaterialTheme.colorScheme.primary
|
||||
),
|
||||
@@ -902,8 +902,15 @@ private fun ReviewsCard(
|
||||
textColor = MaterialTheme.colorScheme.onSecondary
|
||||
)
|
||||
|
||||
val context = LocalContext.current
|
||||
CircleBackgroundColorImage(
|
||||
modifier = Modifier.align(Alignment.CenterVertically),
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
.clickable (
|
||||
onClick = {
|
||||
Toast.makeText(context, "TODO", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
),
|
||||
size = 40.dp,
|
||||
backgroundColor = MaterialTheme.colorScheme.tertiary,
|
||||
image = Icons.Filled.Send,
|
||||
|
||||
@@ -51,7 +51,6 @@ object SessionManager: KoinComponent {
|
||||
_currentSession = null
|
||||
preferences.guestSessionId = ""
|
||||
preferences.authorizedSessionValues = null
|
||||
// preferences.authorizedSessionId = ""
|
||||
}
|
||||
onResponse(deleteResponse.isSuccessful)
|
||||
}
|
||||
@@ -68,7 +67,6 @@ object SessionManager: KoinComponent {
|
||||
_currentSession = null
|
||||
preferences.guestSessionId = ""
|
||||
preferences.authorizedSessionValues = null
|
||||
// preferences.authorizedSessionId = ""
|
||||
}
|
||||
onResponse(deleteResponse.isSuccessful)
|
||||
}
|
||||
@@ -314,14 +312,10 @@ object SessionManager: KoinComponent {
|
||||
if (changed.contains(Changed.AccountDetails)) {
|
||||
service.getAccountDetails().apply {
|
||||
if (isSuccessful) {
|
||||
// withContext(Dispatchers.Main) {
|
||||
_accountDetails = body() ?: _accountDetails
|
||||
accountDetails?.let {
|
||||
// CoroutineScope(Dispatchers.IO).launch {
|
||||
refreshWithAccountId(it.id, changed)
|
||||
// }
|
||||
}
|
||||
// }
|
||||
}
|
||||
}
|
||||
} else if (accountDetails != null) {
|
||||
|
||||
Reference in New Issue
Block a user