refactor main view titles and poster placeholders

This commit is contained in:
Owen LeJeune
2023-06-02 23:30:19 -04:00
parent 918a13d75a
commit 1aee028cb7
19 changed files with 156 additions and 136 deletions

View File

@@ -92,13 +92,13 @@ class MainActivity : MonetCompatActivity() {
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route val currentRoute = navBackStackEntry?.destination?.route
val appBarTitle = rememberSaveable { mutableStateOf(BottomNavItem.getByRoute(currentRoute)?.name ?: BottomNavItem.SortedItems[0].name) }
val decayAnimationSpec = rememberSplineBasedDecay<Float>() val decayAnimationSpec = rememberSplineBasedDecay<Float>()
val topAppBarScrollState = rememberTopAppBarScrollState() val topAppBarScrollState = rememberTopAppBarScrollState()
val scrollBehavior = remember(decayAnimationSpec) { val scrollBehavior = remember(decayAnimationSpec) {
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec, topAppBarScrollState) TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec, topAppBarScrollState)
} }
val appBarTitle = remember { mutableStateOf<@Composable () -> Unit>({}) }
val appBarActions = remember { mutableStateOf<@Composable RowScope.() -> Unit>({}) } val appBarActions = remember { mutableStateOf<@Composable RowScope.() -> Unit>({}) }
val fab = remember { mutableStateOf<@Composable () -> Unit>({}) } val fab = remember { mutableStateOf<@Composable () -> Unit>({}) }
@@ -108,7 +108,7 @@ class MainActivity : MonetCompatActivity() {
if (windowSize != WindowSizeClass.Expanded) { if (windowSize != WindowSizeClass.Expanded) {
TopBar( TopBar(
appNavController = appNavController, appNavController = appNavController,
title = appBarTitle, title = appBarTitle.value,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
appBarActions = appBarActions appBarActions = appBarActions
) )
@@ -119,7 +119,7 @@ class MainActivity : MonetCompatActivity() {
}, },
bottomBar = { bottomBar = {
if (windowSize != WindowSizeClass.Expanded) { if (windowSize != WindowSizeClass.Expanded) {
BottomNavBar(navController = navController, appBarTitle = appBarTitle) BottomNavBar(navController = navController)
} }
} }
) { innerPadding -> ) { innerPadding ->
@@ -141,7 +141,7 @@ class MainActivity : MonetCompatActivity() {
@Composable @Composable
private fun TopBar( private fun TopBar(
appNavController: NavHostController, appNavController: NavHostController,
title: MutableState<String>, title: @Composable () -> Unit,
scrollBehavior: TopAppBarScrollBehavior, scrollBehavior: TopAppBarScrollBehavior,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}) appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({})
) { ) {
@@ -155,7 +155,7 @@ class MainActivity : MonetCompatActivity() {
} }
} }
LargeTopAppBar( LargeTopAppBar(
title = { Text(text = title.value) }, title = title,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults colors = TopAppBarDefaults
.largeTopAppBarColors( .largeTopAppBarColors(
@@ -171,7 +171,6 @@ class MainActivity : MonetCompatActivity() {
@Composable @Composable
private fun BottomNavBar( private fun BottomNavBar(
navController: NavController, navController: NavController,
appBarTitle: MutableState<String>,
preferences: AppPreferences = get(AppPreferences::class.java) preferences: AppPreferences = get(AppPreferences::class.java)
) { ) {
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navController.currentBackStackEntryAsState()
@@ -192,25 +191,12 @@ class MainActivity : MonetCompatActivity() {
selected = isSelected, selected = isSelected,
onClick = { onClick = {
if (!isSelected) { if (!isSelected) {
onBottomAppBarItemClicked(
navController = navController,
appBarTitle = appBarTitle,
item = item
)
}
}
)
}
}
}
private fun onBottomAppBarItemClicked(
navController: NavController,
appBarTitle: MutableState<String>,
item: BottomNavItem
) {
navController.navigateInBottomBar(item.route) navController.navigateInBottomBar(item.route)
appBarTitle.value = item.name }
}
)
}
}
} }
@Composable @Composable
@@ -220,7 +206,7 @@ class MainActivity : MonetCompatActivity() {
navController: NavHostController, navController: NavHostController,
fab: MutableState<@Composable () -> Unit>, fab: MutableState<@Composable () -> Unit>,
topBarScrollBehaviour: TopAppBarScrollBehavior, topBarScrollBehaviour: TopAppBarScrollBehavior,
appBarTitle: MutableState<String>, appBarTitle: MutableState<@Composable () -> Unit>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}), appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route mainNavStartRoute: String = BottomNavItem.SortedItems[0].route
) { ) {
@@ -251,7 +237,7 @@ class MainActivity : MonetCompatActivity() {
appNavController: NavHostController, appNavController: NavHostController,
navController: NavHostController, navController: NavHostController,
fab: MutableState<@Composable () -> Unit>, fab: MutableState<@Composable () -> Unit>,
appBarTitle: MutableState<String>, appBarTitle: MutableState<@Composable () -> Unit>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}), appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route mainNavStartRoute: String = BottomNavItem.SortedItems[0].route
) { ) {
@@ -271,7 +257,7 @@ class MainActivity : MonetCompatActivity() {
navController: NavHostController, navController: NavHostController,
fab: MutableState<@Composable () -> Unit>, fab: MutableState<@Composable () -> Unit>,
topBarScrollBehaviour: TopAppBarScrollBehavior, topBarScrollBehaviour: TopAppBarScrollBehavior,
appBarTitle: MutableState<String>, appBarTitle: MutableState<@Composable () -> Unit>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}), appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route, mainNavStartRoute: String = BottomNavItem.SortedItems[0].route,
preferences: AppPreferences = get(AppPreferences::class.java) preferences: AppPreferences = get(AppPreferences::class.java)
@@ -290,11 +276,7 @@ class MainActivity : MonetCompatActivity() {
selected = isSelected, selected = isSelected,
onClick = { onClick = {
if (!isSelected) { if (!isSelected) {
onBottomAppBarItemClicked( navController.navigateInBottomBar(item.route)
navController = navController,
appBarTitle = appBarTitle,
item = item
)
} }
} }
) )
@@ -307,7 +289,7 @@ class MainActivity : MonetCompatActivity() {
Column { Column {
TopBar( TopBar(
appNavController = appNavController, appNavController = appNavController,
title = appBarTitle, title = appBarTitle.value,
scrollBehavior = topBarScrollBehaviour, scrollBehavior = topBarScrollBehaviour,
appBarActions = appBarActions appBarActions = appBarActions
) )
@@ -328,7 +310,7 @@ class MainActivity : MonetCompatActivity() {
appNavController: NavHostController, appNavController: NavHostController,
navController: NavHostController, navController: NavHostController,
fab: MutableState<@Composable () -> Unit>, fab: MutableState<@Composable () -> Unit>,
appBarTitle: MutableState<String>, appBarTitle: MutableState<@Composable () -> Unit>,
appBarActions: MutableState<RowScope.() -> Unit> = mutableStateOf({}), appBarActions: MutableState<RowScope.() -> Unit> = mutableStateOf({}),
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route mainNavStartRoute: String = BottomNavItem.SortedItems[0].route
) { ) {

View File

@@ -1,9 +1,11 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3.model package com.owenlejeune.tvtime.api.tmdb.api.v3.model
import android.content.Context import android.content.Context
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.HomePageService import com.owenlejeune.tvtime.api.tmdb.api.v3.HomePageService
import com.owenlejeune.tvtime.ui.navigation.MediaFetchFun import com.owenlejeune.tvtime.ui.navigation.MediaFetchFun
import org.koin.core.Koin import org.koin.core.Koin
@@ -13,9 +15,14 @@ import retrofit2.Response
class HomePagePagingSource( class HomePagePagingSource(
private val service: HomePageService, private val service: HomePageService,
private val mediaFetch: MediaFetchFun private val mediaFetch: MediaFetchFun,
private val tag: String
): PagingSource<Int, TmdbItem>(), KoinComponent { ): PagingSource<Int, TmdbItem>(), KoinComponent {
companion object {
val TAG = HomePagePagingSource::class.java.simpleName
}
private val context: Context by inject() private val context: Context by inject()
override fun getRefreshKey(state: PagingState<Int, TmdbItem>): Int? { override fun getRefreshKey(state: PagingState<Int, TmdbItem>): Int? {
@@ -25,17 +32,26 @@ class HomePagePagingSource(
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TmdbItem> { override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TmdbItem> {
return try { return try {
val nextPage = params.key ?: 1 val nextPage = params.key ?: 1
Log.d(TAG, "Loading $tag page $nextPage")
val mediaResponse = mediaFetch.invoke(service, nextPage) val mediaResponse = mediaFetch.invoke(service, nextPage)
if (mediaResponse.isSuccessful) { if (mediaResponse.isSuccessful) {
val responseBody = mediaResponse.body() val responseBody = mediaResponse.body()
val results = responseBody?.results ?: emptyList() val results = responseBody?.results ?: emptyList()
LoadResult.Page( LoadResult.Page(
data = results, data = results,
prevKey = if (nextPage == 1) null else nextPage - 1, prevKey = if (nextPage == 1) {
nextKey = if (results.isEmpty() || responseBody == null) null else responseBody.page + 1 null
} else {
nextPage - 1
},
nextKey = if (results.isEmpty() || responseBody == null) {
null
} else {
responseBody.page + 1
}
) )
} else { } else {
// Toast.makeText(context, "No more results found", Toast.LENGTH_SHORT).show() Toast.makeText(context, context.getString(R.string.no_result_found), Toast.LENGTH_SHORT).show()
LoadResult.Invalid() LoadResult.Invalid()
} }
} catch (e: Exception) { } catch (e: Exception) {

View File

@@ -1,4 +1,4 @@
package com.owenlejeune.tvtime.ui.viewmodel package com.owenlejeune.tvtime.api.tmdb.viewmodel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@@ -14,18 +14,19 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
import com.owenlejeune.tvtime.ui.navigation.MediaFetchFun import com.owenlejeune.tvtime.ui.navigation.MediaFetchFun
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
sealed class MediaTabViewModel(service: HomePageService, mediaFetchFun: MediaFetchFun): ViewModel() { sealed class MediaTabViewModel(service: HomePageService, mediaFetchFun: MediaFetchFun, tag: String): ViewModel() {
val mediaItems: Flow<PagingData<TmdbItem>> = Pager(PagingConfig(pageSize = Int.MAX_VALUE)) {
HomePagePagingSource(service = service, mediaFetch = mediaFetchFun) val mediaItems: Flow<PagingData<TmdbItem>> = Pager(PagingConfig(pageSize = ViewModelConstants.PAGING_SIZE)) {
HomePagePagingSource(service = service, mediaFetch = mediaFetchFun, tag = tag)
}.flow.cachedIn(viewModelScope) }.flow.cachedIn(viewModelScope)
object PopularMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getPopular(p) }) object PopularMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getPopular(p) }, PopularMoviesVM::class.java.simpleName)
object TopRatedMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getTopRated(p) }) object TopRatedMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getTopRated(p) }, TopRatedMoviesVM::class.java.simpleName)
object NowPlayingMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getNowPlaying(p) }) object NowPlayingMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getNowPlaying(p) }, NowPlayingMoviesVM::class.java.simpleName)
object UpcomingMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getUpcoming(p) }) object UpcomingMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getUpcoming(p) }, UpcomingMoviesVM::class.java.simpleName)
object PopularTvVM: MediaTabViewModel(TvService(), { s, p -> s.getPopular(p) }) object PopularTvVM: MediaTabViewModel(TvService(), { s, p -> s.getPopular(p) }, PopularTvVM::class.java.simpleName)
object TopRatedTvVM: MediaTabViewModel(TvService(), { s, p -> s.getTopRated(p) }) object TopRatedTvVM: MediaTabViewModel(TvService(), { s, p -> s.getTopRated(p) }, TopRatedTvVM::class.java.simpleName)
object AiringTodayTvVM: MediaTabViewModel(TvService(), { s, p -> s.getNowPlaying(p) }) object AiringTodayTvVM: MediaTabViewModel(TvService(), { s, p -> s.getNowPlaying(p) }, AiringTodayTvVM::class.java.simpleName)
object OnTheAirTvVM: MediaTabViewModel(TvService(), { s, p -> s.getUpcoming(p) }) object OnTheAirTvVM: MediaTabViewModel(TvService(), { s, p -> s.getUpcoming(p) }, OnTheAirTvVM::class.java.simpleName)
} }

View File

@@ -1,4 +1,4 @@
package com.owenlejeune.tvtime.ui.viewmodel package com.owenlejeune.tvtime.api.tmdb.viewmodel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.Flow
class PeopleTabViewModel: ViewModel() { class PeopleTabViewModel: ViewModel() {
val popularPeople: Flow<PagingData<HomePagePerson>> = Pager(PagingConfig(pageSize = Int.MAX_VALUE)) { val popularPeople: Flow<PagingData<HomePagePerson>> = Pager(PagingConfig(pageSize = ViewModelConstants.PAGING_SIZE)) {
HomePagePeoplePagingSource() HomePagePeoplePagingSource()
}.flow.cachedIn(viewModelScope) }.flow.cachedIn(viewModelScope)

View File

@@ -1,17 +0,0 @@
//package com.owenlejeune.tvtime.api.tmdb.viewmodel
//
//import androidx.lifecycle.ViewModel
//import androidx.lifecycle.viewModelScope
//import androidx.paging.Pager
//import androidx.paging.PagingConfig
//import androidx.paging.PagingData
//import androidx.paging.cachedIn
//import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PopularMovie
//import com.owenlejeune.tvtime.api.tmdb.paging.PopularMovieSource
//import kotlinx.coroutines.flow.Flow
//
//class PopularMovieViewModel: ViewModel() {
// val moviePage: Flow<PagingData<PopularMovie>> = Pager(PagingConfig(pageSize = PopularMovieSource.MAX_PAGE)) {
// PopularMovieSource()
// }.flow.cachedIn(viewModelScope)
//}

View File

@@ -1,4 +1,4 @@
package com.owenlejeune.tvtime.ui.viewmodel package com.owenlejeune.tvtime.api.tmdb.viewmodel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@@ -8,14 +8,13 @@ import androidx.paging.PagingData
import androidx.paging.cachedIn import androidx.paging.cachedIn
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RecommendedMediaPagingSource import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RecommendedMediaPagingSource
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.RecommendedMedia
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
sealed class RecommendedMediaViewModel(mediaType: MediaViewType): ViewModel(), KoinComponent { sealed class RecommendedMediaViewModel(mediaType: MediaViewType): ViewModel(), KoinComponent {
val mediaItems: Flow<PagingData<TmdbItem>> = Pager(PagingConfig(pageSize = Int.MAX_VALUE)) { val mediaItems: Flow<PagingData<TmdbItem>> = Pager(PagingConfig(pageSize = ViewModelConstants.PAGING_SIZE)) {
RecommendedMediaPagingSource(mediaType) RecommendedMediaPagingSource(mediaType)
}.flow.cachedIn(viewModelScope) }.flow.cachedIn(viewModelScope)

View File

@@ -0,0 +1,7 @@
package com.owenlejeune.tvtime.api.tmdb.viewmodel
object ViewModelConstants {
const val PAGING_SIZE = 6
}

View File

@@ -16,6 +16,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.FractionalThreshold import androidx.compose.material.FractionalThreshold
import androidx.compose.material.ThresholdConfig import androidx.compose.material.ThresholdConfig
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.rememberSwipeableState import androidx.compose.material.rememberSwipeableState
import androidx.compose.material.swipeable import androidx.compose.material.swipeable
import androidx.compose.material3.* import androidx.compose.material3.*
@@ -25,6 +27,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
@@ -187,8 +190,7 @@ fun TwoLineImageTextCard(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
subtitle: String? = null, subtitle: String? = null,
imageUrl: String? = null, imageUrl: String? = null,
noDataImage: Int = R.drawable.placeholder, placeholder: ImageVector = Icons.Filled.Person,
placeholder: Int = R.drawable.placeholder,
titleTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant, titleTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
subtitleTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant, subtitleTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
onItemClicked: () -> Unit = {} onItemClicked: () -> Unit = {}
@@ -198,7 +200,6 @@ fun TwoLineImageTextCard(
width = 120.dp, width = 120.dp,
onClick = onItemClicked, onClick = onItemClicked,
url = imageUrl, url = imageUrl,
noDataImage = noDataImage,
placeholder = placeholder, placeholder = placeholder,
title = title, title = title,
elevation = 0.dp, elevation = 0.dp,

View File

@@ -1,16 +1,24 @@
package com.owenlejeune.tvtime.ui.components package com.owenlejeune.tvtime.ui.components
import android.util.Log import android.util.Log
import android.widget.Toast
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.magnifier
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BrokenImage
import androidx.compose.material.icons.filled.Movie
import androidx.compose.material.icons.filled.Person
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
@@ -19,9 +27,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
@@ -118,7 +128,7 @@ fun PagingPeoplePosterGrid(
person?.let { person?.let {
PosterItem( PosterItem(
url = TmdbUtils.getFullPersonImagePath(person.profilePath), url = TmdbUtils.getFullPersonImagePath(person.profilePath),
noDataImage = R.drawable.no_person_photo, placeholder = Icons.Filled.Person,
modifier = Modifier.padding(5.dp), modifier = Modifier.padding(5.dp),
onClick = { onClick = {
onClick(person.id) onClick(person.id)
@@ -155,7 +165,7 @@ fun PeoplePosterGrid(
listItems(peopleList.value) { person -> listItems(peopleList.value) { person ->
PosterItem( PosterItem(
url = TmdbUtils.getFullPersonImagePath(person.profilePath), url = TmdbUtils.getFullPersonImagePath(person.profilePath),
noDataImage = R.drawable.no_person_photo, placeholder = Icons.Filled.Person,
modifier = Modifier.padding(5.dp), modifier = Modifier.padding(5.dp),
onClick = { onClick = {
onClick(person.id) onClick(person.id)
@@ -209,6 +219,7 @@ fun PosterItem(
) )
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun PosterItem( fun PosterItem(
url: String?, url: String?,
@@ -216,59 +227,68 @@ fun PosterItem(
width: Dp = POSTER_WIDTH, width: Dp = POSTER_WIDTH,
onClick: () -> Unit = {}, onClick: () -> Unit = {},
enabled: Boolean = true, enabled: Boolean = true,
noDataImage: Int = R.drawable.placeholder, placeholder: ImageVector = Icons.Filled.Movie,
placeholder: Int = R.drawable.placeholder,
elevation: Dp = 8.dp, elevation: Dp = 8.dp,
title: String?, title: String?,
overrideShowTitle: Boolean? = null, overrideShowTitle: Boolean? = null,
preferences: AppPreferences = get(AppPreferences::class.java) preferences: AppPreferences = get(AppPreferences::class.java)
) { ) {
var sizeImage by remember { mutableStateOf(IntSize.Zero) }
Card( Card(
elevation = elevation, elevation = CardDefaults.elevatedCardElevation(defaultElevation = elevation),
modifier = modifier modifier = modifier
.width(width = width) .width(width = width)
.wrapContentHeight(), .wrapContentHeight()
shape = RoundedCornerShape(5.dp) .clickable(
) { enabled = enabled,
Box(
modifier = Modifier.clickable(
enabled = true,
onClick = onClick onClick = onClick
) ),
shape = RoundedCornerShape(5.dp),
colors = CardDefaults.cardColors(containerColor = Color.Transparent)
) { ) {
var sizeImage by remember { mutableStateOf(IntSize.Zero) } var backgroundColor by remember { mutableStateOf(Color.Gray) }
Box(
modifier = Modifier
.width(width = width)
.height(height = POSTER_HEIGHT)
.background(color = backgroundColor)
.clip(RoundedCornerShape(5.dp))
.onGloballyPositioned { sizeImage = it.size }
) {
var bgIcon by remember { mutableStateOf(placeholder) }
Icon(
imageVector = bgIcon,
contentDescription = null,
modifier = Modifier
.focusable(enabled = false)
.size(36.dp)
.align(Alignment.Center),
tint = MaterialTheme.colorScheme.background
)
val gradient = Brush.verticalGradient( val gradient = Brush.verticalGradient(
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.7f)), colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.7f)),
startY = sizeImage.height.toFloat() / 3f, startY = sizeImage.height.toFloat() / 3f,
endY = sizeImage.height.toFloat() endY = sizeImage.height.toFloat()
) )
if (url != null) {
AsyncImage( AsyncImage(
modifier = Modifier modifier = Modifier
.width(width = width) .width(width = width)
.wrapContentHeight() .wrapContentHeight()
.clip(RoundedCornerShape(5.dp)) .clip(RoundedCornerShape(5.dp))
.onGloballyPositioned { sizeImage = it.size }, .onGloballyPositioned { sizeImage = it.size },
onError = { Log.d("Poster", "Error loading: $url") }, onError = {
error = rememberAsyncImagePainter(model = noDataImage), if (url != null) {
model = url, bgIcon = Icons.Filled.BrokenImage
placeholder = rememberAsyncImagePainter(model = placeholder), Log.d("Poster", "Error loading: $url")
contentDescription = title,
contentScale = ContentScale.FillWidth
)
} else {
Image(
modifier = Modifier
.width(width = width)
.height(height = POSTER_HEIGHT)
.clip(RoundedCornerShape(5.dp))
.onGloballyPositioned { sizeImage = it.size },
painter = rememberAsyncImagePainter(model = noDataImage),
contentDescription = title,
contentScale = ContentScale.FillBounds
)
} }
},
model = url,
contentDescription = title,
contentScale = ContentScale.FillWidth,
onSuccess = { backgroundColor = Color.Transparent }
)
val showTitle = overrideShowTitle ?: preferences.showPosterTitles val showTitle = overrideShowTitle ?: preferences.showPosterTitles
if (showTitle) { if (showTitle) {

View File

@@ -7,7 +7,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.HomePageService
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePageResponse import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePageResponse
import com.owenlejeune.tvtime.ui.screens.main.MediaTabContent import com.owenlejeune.tvtime.ui.screens.main.MediaTabContent
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
import com.owenlejeune.tvtime.ui.viewmodel.MediaTabViewModel import com.owenlejeune.tvtime.api.tmdb.viewmodel.MediaTabViewModel
import com.owenlejeune.tvtime.utils.ResourceUtils import com.owenlejeune.tvtime.utils.ResourceUtils
import org.koin.core.component.inject import org.koin.core.component.inject
import retrofit2.Response import retrofit2.Response

View File

@@ -22,7 +22,7 @@ fun MainNavGraph(
appNavController: NavHostController, appNavController: NavHostController,
navController: NavHostController, navController: NavHostController,
fab: MutableState<@Composable () -> Unit>, fab: MutableState<@Composable () -> Unit>,
appBarTitle: MutableState<String>, appBarTitle: MutableState<@Composable () -> Unit>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}), appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
startDestination: String = BottomNavItem.SortedItems[0].route startDestination: String = BottomNavItem.SortedItems[0].route
) { ) {

View File

@@ -38,7 +38,7 @@ import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem
import com.owenlejeune.tvtime.ui.navigation.ListFetchFun import com.owenlejeune.tvtime.ui.navigation.ListFetchFun
import com.owenlejeune.tvtime.ui.navigation.MainNavItem import com.owenlejeune.tvtime.ui.navigation.MainNavItem
import com.owenlejeune.tvtime.ui.screens.main.tabs.top.ScrollableTabs import com.owenlejeune.tvtime.ui.screens.main.tabs.top.ScrollableTabs
import com.owenlejeune.tvtime.ui.viewmodel.RecommendedMediaViewModel import com.owenlejeune.tvtime.api.tmdb.viewmodel.RecommendedMediaViewModel
import com.owenlejeune.tvtime.utils.SessionManager import com.owenlejeune.tvtime.utils.SessionManager
import com.owenlejeune.tvtime.utils.TmdbUtils import com.owenlejeune.tvtime.utils.TmdbUtils
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -50,7 +50,7 @@ import kotlin.reflect.KClass
@Composable @Composable
fun AccountTab( fun AccountTab(
appNavController: NavHostController, appNavController: NavHostController,
appBarTitle: MutableState<String>, appBarTitle: MutableState<@Composable () -> Unit>,
appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}), appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}),
doSignInPartTwo: Boolean = false doSignInPartTwo: Boolean = false
) { ) {
@@ -60,7 +60,7 @@ fun AccountTab(
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
if (currentSession?.isAuthorized == false) { if (currentSession?.isAuthorized == false) {
appBarTitle.value = stringResource(id = R.string.account_not_logged_in) appBarTitle.value = { Text(text = stringResource(id = R.string.account_not_logged_in)) }
if (doSignInPartTwo) { if (doSignInPartTwo) {
AccountLoadingView() AccountLoadingView()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -72,13 +72,9 @@ fun AccountTab(
} else { } else {
if (currentSession?.isAuthorized == true) { if (currentSession?.isAuthorized == true) {
val accountDetails = remember { currentSession.accountDetails } val accountDetails = remember { currentSession.accountDetails }
appBarTitle.value = appBarTitle.value = { Text(text = stringResource(id = R.string.account_header_title_formatted, getAccountName(accountDetails.value))) }
stringResource(
id = R.string.account_header_title_formatted,
getAccountName(accountDetails.value)
)
} else { } else {
appBarTitle.value = stringResource(id = R.string.account_not_logged_in) appBarTitle.value = { Text(text = stringResource(id = R.string.account_not_logged_in)) }
} }
appBarActions.value = { appBarActions.value = {

View File

@@ -11,6 +11,7 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Movie
import androidx.compose.material.icons.filled.Send import androidx.compose.material.icons.filled.Send
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
@@ -849,7 +850,6 @@ private fun CastCrewCard(appNavController: NavController, person: Person) {
else -> null else -> null
}, },
imageUrl = TmdbUtils.getFullPersonImagePath(person), imageUrl = TmdbUtils.getFullPersonImagePath(person),
noDataImage = R.drawable.no_person_photo,
titleTextColor = MaterialTheme.colorScheme.onPrimary, titleTextColor = MaterialTheme.colorScheme.onPrimary,
subtitleTextColor = MaterialTheme.colorScheme.onSecondary, subtitleTextColor = MaterialTheme.colorScheme.onSecondary,
onItemClicked = { onItemClicked = {
@@ -899,7 +899,8 @@ fun SimilarContentCard(
appNavController.navigate( appNavController.navigate(
"${MainNavItem.DetailView.route}/${mediaType}/${content.id}" "${MainNavItem.DetailView.route}/${mediaType}/${content.id}"
) )
} },
placeholder = Icons.Filled.Movie
) )
} }
} }

View File

@@ -1,6 +1,7 @@
package com.owenlejeune.tvtime.ui.screens.main package com.owenlejeune.tvtime.ui.screens.main
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -17,25 +18,26 @@ import com.owenlejeune.tvtime.ui.components.SearchView
import com.owenlejeune.tvtime.ui.navigation.MainNavItem import com.owenlejeune.tvtime.ui.navigation.MainNavItem
import com.owenlejeune.tvtime.ui.navigation.MediaTabNavItem import com.owenlejeune.tvtime.ui.navigation.MediaTabNavItem
import com.owenlejeune.tvtime.ui.screens.main.tabs.top.Tabs import com.owenlejeune.tvtime.ui.screens.main.tabs.top.Tabs
import com.owenlejeune.tvtime.ui.viewmodel.MediaTabViewModel import com.owenlejeune.tvtime.api.tmdb.viewmodel.MediaTabViewModel
@OptIn(ExperimentalPagerApi::class) @OptIn(ExperimentalPagerApi::class)
@Composable @Composable
fun MediaTab( fun MediaTab(
appBarTitle: MutableState<String>, appBarTitle: MutableState<@Composable () -> Unit>,
appNavController: NavHostController, appNavController: NavHostController,
mediaType: MediaViewType, mediaType: MediaViewType,
fab: MutableState<@Composable () -> Unit> fab: MutableState<@Composable () -> Unit>
) { ) {
appBarTitle.value = when (mediaType) { val titleText = when (mediaType) {
MediaViewType.MOVIE -> stringResource(id = R.string.nav_movies_title) MediaViewType.MOVIE -> stringResource(id = R.string.nav_movies_title)
MediaViewType.TV -> stringResource(id = R.string.nav_tv_title) MediaViewType.TV -> stringResource(id = R.string.nav_tv_title)
else -> "" else -> ""
} }
appBarTitle.value = @Composable { Text(text = titleText) }
Column { Column {
SearchView( SearchView(
title = appBarTitle.value, title = titleText,
appNavController = appNavController, appNavController = appNavController,
mediaType = mediaType, mediaType = mediaType,
fab = fab fab = fab

View File

@@ -49,7 +49,7 @@ fun PersonDetailView(
val decayAnimationSpec = rememberSplineBasedDecay<Float>() val decayAnimationSpec = rememberSplineBasedDecay<Float>()
val topAppBarScrollState = rememberTopAppBarScrollState() val topAppBarScrollState = rememberTopAppBarScrollState()
val scrollBehaviour = remember(decayAnimationSpec) { val scrollBehaviour = remember(decayAnimationSpec) {
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec, topAppBarScrollState) TopAppBarDefaults.pinnedScrollBehavior(topAppBarScrollState)
} }
Scaffold( Scaffold(
@@ -58,7 +58,7 @@ fun PersonDetailView(
SmallTopAppBar( SmallTopAppBar(
scrollBehavior = scrollBehaviour, scrollBehavior = scrollBehaviour,
colors = TopAppBarDefaults colors = TopAppBarDefaults
.largeTopAppBarColors( .smallTopAppBarColors(
scrolledContainerColor = MaterialTheme.colorScheme.background, scrolledContainerColor = MaterialTheme.colorScheme.background,
titleContentColor = MaterialTheme.colorScheme.primary titleContentColor = MaterialTheme.colorScheme.primary
), ),

View File

@@ -14,19 +14,20 @@ import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.ui.components.PagingPeoplePosterGrid import com.owenlejeune.tvtime.ui.components.PagingPeoplePosterGrid
import com.owenlejeune.tvtime.ui.components.SearchView import com.owenlejeune.tvtime.ui.components.SearchView
import com.owenlejeune.tvtime.ui.navigation.MainNavItem import com.owenlejeune.tvtime.ui.navigation.MainNavItem
import com.owenlejeune.tvtime.ui.viewmodel.PeopleTabViewModel import com.owenlejeune.tvtime.api.tmdb.viewmodel.PeopleTabViewModel
@Composable @Composable
fun PeopleTab( fun PeopleTab(
appBarTitle: MutableState<String>, appBarTitle: MutableState<@Composable () -> Unit>,
appNavController: NavHostController, appNavController: NavHostController,
fab: MutableState<@Composable () -> Unit> fab: MutableState<@Composable () -> Unit>
) { ) {
appBarTitle.value = stringResource(id = R.string.nav_people_title) val titleText = stringResource(id = R.string.nav_people_title)
appBarTitle.value = { Text(text = titleText) }
Column { Column {
SearchView( SearchView(
title = appBarTitle.value, title = titleText,
appNavController = appNavController, appNavController = appNavController,
mediaType = MediaViewType.PERSON, mediaType = MediaViewType.PERSON,
fab = fab fab = fab

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="@android:color/darker_gray"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M21,5v6.59l-3,-3.01 -4,4.01 -4,-4 -4,4 -3,-3.01L3,5c0,-1.1 0.9,-2 2,-2h14c1.1,0 2,0.9 2,2zM18,11.42l3,3.01L21,19c0,1.1 -0.9,2 -2,2L5,21c-1.1,0 -2,-0.9 -2,-2v-6.58l3,2.99 4,-4 4,4 4,-3.99z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="@android:color/darker_gray"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M2,4h20v16h-20z"/>
</vector>

View File

@@ -212,4 +212,5 @@
<string name="no_recommended_movies">No Recommended Movies</string> <string name="no_recommended_movies">No Recommended Movies</string>
<string name="recommended_tv_title">Recommended TV</string> <string name="recommended_tv_title">Recommended TV</string>
<string name="no_recommended_tv">No Recommended TV</string> <string name="no_recommended_tv">No Recommended TV</string>
<string name="no_result_found">No more results found</string>
</resources> </resources>