diff --git a/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt b/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt index 2fc89ed..065c074 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt @@ -92,13 +92,13 @@ class MainActivity : MonetCompatActivity() { val navBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = navBackStackEntry?.destination?.route - val appBarTitle = rememberSaveable { mutableStateOf(BottomNavItem.getByRoute(currentRoute)?.name ?: BottomNavItem.SortedItems[0].name) } val decayAnimationSpec = rememberSplineBasedDecay() val topAppBarScrollState = rememberTopAppBarScrollState() val scrollBehavior = remember(decayAnimationSpec) { TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec, topAppBarScrollState) } + val appBarTitle = remember { mutableStateOf<@Composable () -> Unit>({}) } val appBarActions = remember { mutableStateOf<@Composable RowScope.() -> Unit>({}) } val fab = remember { mutableStateOf<@Composable () -> Unit>({}) } @@ -108,7 +108,7 @@ class MainActivity : MonetCompatActivity() { if (windowSize != WindowSizeClass.Expanded) { TopBar( appNavController = appNavController, - title = appBarTitle, + title = appBarTitle.value, scrollBehavior = scrollBehavior, appBarActions = appBarActions ) @@ -119,7 +119,7 @@ class MainActivity : MonetCompatActivity() { }, bottomBar = { if (windowSize != WindowSizeClass.Expanded) { - BottomNavBar(navController = navController, appBarTitle = appBarTitle) + BottomNavBar(navController = navController) } } ) { innerPadding -> @@ -141,7 +141,7 @@ class MainActivity : MonetCompatActivity() { @Composable private fun TopBar( appNavController: NavHostController, - title: MutableState, + title: @Composable () -> Unit, scrollBehavior: TopAppBarScrollBehavior, appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}) ) { @@ -155,7 +155,7 @@ class MainActivity : MonetCompatActivity() { } } LargeTopAppBar( - title = { Text(text = title.value) }, + title = title, scrollBehavior = scrollBehavior, colors = TopAppBarDefaults .largeTopAppBarColors( @@ -171,7 +171,6 @@ class MainActivity : MonetCompatActivity() { @Composable private fun BottomNavBar( navController: NavController, - appBarTitle: MutableState, preferences: AppPreferences = get(AppPreferences::class.java) ) { val navBackStackEntry by navController.currentBackStackEntryAsState() @@ -192,11 +191,7 @@ class MainActivity : MonetCompatActivity() { selected = isSelected, onClick = { if (!isSelected) { - onBottomAppBarItemClicked( - navController = navController, - appBarTitle = appBarTitle, - item = item - ) + navController.navigateInBottomBar(item.route) } } ) @@ -204,15 +199,6 @@ class MainActivity : MonetCompatActivity() { } } - private fun onBottomAppBarItemClicked( - navController: NavController, - appBarTitle: MutableState, - item: BottomNavItem - ) { - navController.navigateInBottomBar(item.route) - appBarTitle.value = item.name - } - @Composable private fun MainContent( windowSize: WindowSizeClass, @@ -220,7 +206,7 @@ class MainActivity : MonetCompatActivity() { navController: NavHostController, fab: MutableState<@Composable () -> Unit>, topBarScrollBehaviour: TopAppBarScrollBehavior, - appBarTitle: MutableState, + appBarTitle: MutableState<@Composable () -> Unit>, appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}), mainNavStartRoute: String = BottomNavItem.SortedItems[0].route ) { @@ -251,7 +237,7 @@ class MainActivity : MonetCompatActivity() { appNavController: NavHostController, navController: NavHostController, fab: MutableState<@Composable () -> Unit>, - appBarTitle: MutableState, + appBarTitle: MutableState<@Composable () -> Unit>, appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}), mainNavStartRoute: String = BottomNavItem.SortedItems[0].route ) { @@ -271,7 +257,7 @@ class MainActivity : MonetCompatActivity() { navController: NavHostController, fab: MutableState<@Composable () -> Unit>, topBarScrollBehaviour: TopAppBarScrollBehavior, - appBarTitle: MutableState, + appBarTitle: MutableState<@Composable () -> Unit>, appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}), mainNavStartRoute: String = BottomNavItem.SortedItems[0].route, preferences: AppPreferences = get(AppPreferences::class.java) @@ -290,11 +276,7 @@ class MainActivity : MonetCompatActivity() { selected = isSelected, onClick = { if (!isSelected) { - onBottomAppBarItemClicked( - navController = navController, - appBarTitle = appBarTitle, - item = item - ) + navController.navigateInBottomBar(item.route) } } ) @@ -307,7 +289,7 @@ class MainActivity : MonetCompatActivity() { Column { TopBar( appNavController = appNavController, - title = appBarTitle, + title = appBarTitle.value, scrollBehavior = topBarScrollBehaviour, appBarActions = appBarActions ) @@ -328,7 +310,7 @@ class MainActivity : MonetCompatActivity() { appNavController: NavHostController, navController: NavHostController, fab: MutableState<@Composable () -> Unit>, - appBarTitle: MutableState, + appBarTitle: MutableState<@Composable () -> Unit>, appBarActions: MutableState Unit> = mutableStateOf({}), mainNavStartRoute: String = BottomNavItem.SortedItems[0].route ) { diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/HomePagePagingSource.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/HomePagePagingSource.kt index 06b8f00..05a7ee4 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/HomePagePagingSource.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/api/v3/model/HomePagePagingSource.kt @@ -1,9 +1,11 @@ package com.owenlejeune.tvtime.api.tmdb.api.v3.model import android.content.Context +import android.util.Log import android.widget.Toast import androidx.paging.PagingSource import androidx.paging.PagingState +import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.api.tmdb.api.v3.HomePageService import com.owenlejeune.tvtime.ui.navigation.MediaFetchFun import org.koin.core.Koin @@ -13,9 +15,14 @@ import retrofit2.Response class HomePagePagingSource( private val service: HomePageService, - private val mediaFetch: MediaFetchFun + private val mediaFetch: MediaFetchFun, + private val tag: String ): PagingSource(), KoinComponent { + companion object { + val TAG = HomePagePagingSource::class.java.simpleName + } + private val context: Context by inject() override fun getRefreshKey(state: PagingState): Int? { @@ -25,17 +32,26 @@ class HomePagePagingSource( override suspend fun load(params: LoadParams): LoadResult { return try { val nextPage = params.key ?: 1 + Log.d(TAG, "Loading $tag page $nextPage") val mediaResponse = mediaFetch.invoke(service, nextPage) if (mediaResponse.isSuccessful) { val responseBody = mediaResponse.body() val results = responseBody?.results ?: emptyList() LoadResult.Page( data = results, - prevKey = if (nextPage == 1) null else nextPage - 1, - nextKey = if (results.isEmpty() || responseBody == null) null else responseBody.page + 1 + prevKey = if (nextPage == 1) { + null + } else { + nextPage - 1 + }, + nextKey = if (results.isEmpty() || responseBody == null) { + null + } else { + responseBody.page + 1 + } ) } 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() } } catch (e: Exception) { diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/viewmodel/MediaTabViewModel.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/MediaTabViewModel.kt similarity index 65% rename from app/src/main/java/com/owenlejeune/tvtime/ui/viewmodel/MediaTabViewModel.kt rename to app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/MediaTabViewModel.kt index 61634d7..b067783 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/viewmodel/MediaTabViewModel.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/MediaTabViewModel.kt @@ -1,4 +1,4 @@ -package com.owenlejeune.tvtime.ui.viewmodel +package com.owenlejeune.tvtime.api.tmdb.viewmodel import androidx.lifecycle.ViewModel 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 kotlinx.coroutines.flow.Flow -sealed class MediaTabViewModel(service: HomePageService, mediaFetchFun: MediaFetchFun): ViewModel() { - val mediaItems: Flow> = Pager(PagingConfig(pageSize = Int.MAX_VALUE)) { - HomePagePagingSource(service = service, mediaFetch = mediaFetchFun) +sealed class MediaTabViewModel(service: HomePageService, mediaFetchFun: MediaFetchFun, tag: String): ViewModel() { + + val mediaItems: Flow> = Pager(PagingConfig(pageSize = ViewModelConstants.PAGING_SIZE)) { + HomePagePagingSource(service = service, mediaFetch = mediaFetchFun, tag = tag) }.flow.cachedIn(viewModelScope) - object PopularMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getPopular(p) }) - object TopRatedMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getTopRated(p) }) - object NowPlayingMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getNowPlaying(p) }) - object UpcomingMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getUpcoming(p) }) - object PopularTvVM: MediaTabViewModel(TvService(), { s, p -> s.getPopular(p) }) - object TopRatedTvVM: MediaTabViewModel(TvService(), { s, p -> s.getTopRated(p) }) - object AiringTodayTvVM: MediaTabViewModel(TvService(), { s, p -> s.getNowPlaying(p) }) - object OnTheAirTvVM: MediaTabViewModel(TvService(), { s, p -> s.getUpcoming(p) }) + object PopularMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getPopular(p) }, PopularMoviesVM::class.java.simpleName) + object TopRatedMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getTopRated(p) }, TopRatedMoviesVM::class.java.simpleName) + object NowPlayingMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getNowPlaying(p) }, NowPlayingMoviesVM::class.java.simpleName) + object UpcomingMoviesVM: MediaTabViewModel(MoviesService(), { s, p -> s.getUpcoming(p) }, UpcomingMoviesVM::class.java.simpleName) + object PopularTvVM: MediaTabViewModel(TvService(), { s, p -> s.getPopular(p) }, PopularTvVM::class.java.simpleName) + object TopRatedTvVM: MediaTabViewModel(TvService(), { s, p -> s.getTopRated(p) }, TopRatedTvVM::class.java.simpleName) + object AiringTodayTvVM: MediaTabViewModel(TvService(), { s, p -> s.getNowPlaying(p) }, AiringTodayTvVM::class.java.simpleName) + object OnTheAirTvVM: MediaTabViewModel(TvService(), { s, p -> s.getUpcoming(p) }, OnTheAirTvVM::class.java.simpleName) } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/viewmodel/PeopleTabViewModel.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/PeopleTabViewModel.kt similarity index 83% rename from app/src/main/java/com/owenlejeune/tvtime/ui/viewmodel/PeopleTabViewModel.kt rename to app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/PeopleTabViewModel.kt index 5b76804..14b0422 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/viewmodel/PeopleTabViewModel.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/PeopleTabViewModel.kt @@ -1,4 +1,4 @@ -package com.owenlejeune.tvtime.ui.viewmodel +package com.owenlejeune.tvtime.api.tmdb.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.Flow class PeopleTabViewModel: ViewModel() { - val popularPeople: Flow> = Pager(PagingConfig(pageSize = Int.MAX_VALUE)) { + val popularPeople: Flow> = Pager(PagingConfig(pageSize = ViewModelConstants.PAGING_SIZE)) { HomePagePeoplePagingSource() }.flow.cachedIn(viewModelScope) diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/PopularMovieViewModel.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/PopularMovieViewModel.kt deleted file mode 100644 index 3779f22..0000000 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/PopularMovieViewModel.kt +++ /dev/null @@ -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> = Pager(PagingConfig(pageSize = PopularMovieSource.MAX_PAGE)) { -// PopularMovieSource() -// }.flow.cachedIn(viewModelScope) -//} \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/viewmodel/RecommendedMediaViewModel.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/RecommendedMediaViewModel.kt similarity index 85% rename from app/src/main/java/com/owenlejeune/tvtime/ui/viewmodel/RecommendedMediaViewModel.kt rename to app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/RecommendedMediaViewModel.kt index 028f1df..445d575 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/viewmodel/RecommendedMediaViewModel.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/RecommendedMediaViewModel.kt @@ -1,4 +1,4 @@ -package com.owenlejeune.tvtime.ui.viewmodel +package com.owenlejeune.tvtime.api.tmdb.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -8,14 +8,13 @@ import androidx.paging.PagingData import androidx.paging.cachedIn 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.v4.model.RecommendedMedia import com.owenlejeune.tvtime.ui.screens.main.MediaViewType import kotlinx.coroutines.flow.Flow import org.koin.core.component.KoinComponent sealed class RecommendedMediaViewModel(mediaType: MediaViewType): ViewModel(), KoinComponent { - val mediaItems: Flow> = Pager(PagingConfig(pageSize = Int.MAX_VALUE)) { + val mediaItems: Flow> = Pager(PagingConfig(pageSize = ViewModelConstants.PAGING_SIZE)) { RecommendedMediaPagingSource(mediaType) }.flow.cachedIn(viewModelScope) diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/ViewModelConstants.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/ViewModelConstants.kt new file mode 100644 index 0000000..18e490a --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/viewmodel/ViewModelConstants.kt @@ -0,0 +1,7 @@ +package com.owenlejeune.tvtime.api.tmdb.viewmodel + +object ViewModelConstants { + + const val PAGING_SIZE = 6 + +} \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Cards.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Cards.kt index b3ab8d4..46bdc00 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Cards.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Cards.kt @@ -16,6 +16,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.FractionalThreshold 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.swipeable 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.RectangleShape import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity @@ -187,8 +190,7 @@ fun TwoLineImageTextCard( modifier: Modifier = Modifier, subtitle: String? = null, imageUrl: String? = null, - noDataImage: Int = R.drawable.placeholder, - placeholder: Int = R.drawable.placeholder, + placeholder: ImageVector = Icons.Filled.Person, titleTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant, subtitleTextColor: Color = MaterialTheme.colorScheme.onSurfaceVariant, onItemClicked: () -> Unit = {} @@ -198,7 +200,6 @@ fun TwoLineImageTextCard( width = 120.dp, onClick = onItemClicked, url = imageUrl, - noDataImage = noDataImage, placeholder = placeholder, title = title, elevation = 0.dp, diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Posters.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Posters.kt index e854169..8d0f184 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/components/Posters.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/components/Posters.kt @@ -1,16 +1,24 @@ package com.owenlejeune.tvtime.ui.components import android.util.Log -import android.widget.Toast import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.magnifier 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.Text import androidx.compose.runtime.* @@ -19,9 +27,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush 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.onGloballyPositioned -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize @@ -118,7 +128,7 @@ fun PagingPeoplePosterGrid( person?.let { PosterItem( url = TmdbUtils.getFullPersonImagePath(person.profilePath), - noDataImage = R.drawable.no_person_photo, + placeholder = Icons.Filled.Person, modifier = Modifier.padding(5.dp), onClick = { onClick(person.id) @@ -155,7 +165,7 @@ fun PeoplePosterGrid( listItems(peopleList.value) { person -> PosterItem( url = TmdbUtils.getFullPersonImagePath(person.profilePath), - noDataImage = R.drawable.no_person_photo, + placeholder = Icons.Filled.Person, modifier = Modifier.padding(5.dp), onClick = { onClick(person.id) @@ -209,6 +219,7 @@ fun PosterItem( ) } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun PosterItem( url: String?, @@ -216,59 +227,68 @@ fun PosterItem( width: Dp = POSTER_WIDTH, onClick: () -> Unit = {}, enabled: Boolean = true, - noDataImage: Int = R.drawable.placeholder, - placeholder: Int = R.drawable.placeholder, + placeholder: ImageVector = Icons.Filled.Movie, elevation: Dp = 8.dp, title: String?, overrideShowTitle: Boolean? = null, preferences: AppPreferences = get(AppPreferences::class.java) ) { + var sizeImage by remember { mutableStateOf(IntSize.Zero) } Card( - elevation = elevation, + elevation = CardDefaults.elevatedCardElevation(defaultElevation = elevation), modifier = modifier .width(width = width) - .wrapContentHeight(), - shape = RoundedCornerShape(5.dp) - ) { - Box( - modifier = Modifier.clickable( - enabled = true, + .wrapContentHeight() + .clickable( + enabled = enabled, onClick = onClick - ) + ), + shape = RoundedCornerShape(5.dp), + colors = CardDefaults.cardColors(containerColor = Color.Transparent) + ) { + 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 sizeImage by remember { mutableStateOf(IntSize.Zero) } + 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( colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.7f)), startY = sizeImage.height.toFloat() / 3f, endY = sizeImage.height.toFloat() ) - if (url != null) { - AsyncImage( - modifier = Modifier - .width(width = width) - .wrapContentHeight() - .clip(RoundedCornerShape(5.dp)) - .onGloballyPositioned { sizeImage = it.size }, - onError = { Log.d("Poster", "Error loading: $url") }, - error = rememberAsyncImagePainter(model = noDataImage), - model = url, - placeholder = rememberAsyncImagePainter(model = placeholder), - 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 - ) - } + AsyncImage( + modifier = Modifier + .width(width = width) + .wrapContentHeight() + .clip(RoundedCornerShape(5.dp)) + .onGloballyPositioned { sizeImage = it.size }, + onError = { + if (url != null) { + bgIcon = Icons.Filled.BrokenImage + Log.d("Poster", "Error loading: $url") + } + }, + model = url, + contentDescription = title, + contentScale = ContentScale.FillWidth, + onSuccess = { backgroundColor = Color.Transparent } + ) val showTitle = overrideShowTitle ?: preferences.showPosterTitles if (showTitle) { diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/MediaTabNavItem.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/MediaTabNavItem.kt index bbe1b15..65fe8cc 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/MediaTabNavItem.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/MediaTabNavItem.kt @@ -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.ui.screens.main.MediaTabContent 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 org.koin.core.component.inject import retrofit2.Response diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/Routes.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/Routes.kt index 2fc1427..617854b 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/Routes.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/Routes.kt @@ -22,7 +22,7 @@ fun MainNavGraph( appNavController: NavHostController, navController: NavHostController, fab: MutableState<@Composable () -> Unit>, - appBarTitle: MutableState, + appBarTitle: MutableState<@Composable () -> Unit>, appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}), startDestination: String = BottomNavItem.SortedItems[0].route ) { diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/AccountTab.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/AccountTab.kt index da02bb0..937ac9a 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/AccountTab.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/AccountTab.kt @@ -38,7 +38,7 @@ import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem import com.owenlejeune.tvtime.ui.navigation.ListFetchFun import com.owenlejeune.tvtime.ui.navigation.MainNavItem 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.TmdbUtils import kotlinx.coroutines.CoroutineScope @@ -50,7 +50,7 @@ import kotlin.reflect.KClass @Composable fun AccountTab( appNavController: NavHostController, - appBarTitle: MutableState, + appBarTitle: MutableState<@Composable () -> Unit>, appBarActions: MutableState<@Composable (RowScope.() -> Unit)> = mutableStateOf({}), doSignInPartTwo: Boolean = false ) { @@ -60,7 +60,7 @@ fun AccountTab( val scope = rememberCoroutineScope() 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) { AccountLoadingView() LaunchedEffect(Unit) { @@ -72,13 +72,9 @@ fun AccountTab( } else { if (currentSession?.isAuthorized == true) { val accountDetails = remember { currentSession.accountDetails } - appBarTitle.value = - stringResource( - id = R.string.account_header_title_formatted, - getAccountName(accountDetails.value) - ) + appBarTitle.value = { Text(text = stringResource(id = R.string.account_header_title_formatted, getAccountName(accountDetails.value))) } } 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 = { diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/MediaDetailView.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/MediaDetailView.kt index dd141ea..e2f3074 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/MediaDetailView.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/MediaDetailView.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Movie import androidx.compose.material.icons.filled.Send import androidx.compose.material3.* import androidx.compose.runtime.* @@ -849,7 +850,6 @@ private fun CastCrewCard(appNavController: NavController, person: Person) { else -> null }, imageUrl = TmdbUtils.getFullPersonImagePath(person), - noDataImage = R.drawable.no_person_photo, titleTextColor = MaterialTheme.colorScheme.onPrimary, subtitleTextColor = MaterialTheme.colorScheme.onSecondary, onItemClicked = { @@ -899,7 +899,8 @@ fun SimilarContentCard( appNavController.navigate( "${MainNavItem.DetailView.route}/${mediaType}/${content.id}" ) - } + }, + placeholder = Icons.Filled.Movie ) } } diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/MediaTab.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/MediaTab.kt index 8893722..ed74c03 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/MediaTab.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/MediaTab.kt @@ -1,6 +1,7 @@ package com.owenlejeune.tvtime.ui.screens.main import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState 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.MediaTabNavItem 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) @Composable fun MediaTab( - appBarTitle: MutableState, + appBarTitle: MutableState<@Composable () -> Unit>, appNavController: NavHostController, mediaType: MediaViewType, fab: MutableState<@Composable () -> Unit> ) { - appBarTitle.value = when (mediaType) { + val titleText = when (mediaType) { MediaViewType.MOVIE -> stringResource(id = R.string.nav_movies_title) MediaViewType.TV -> stringResource(id = R.string.nav_tv_title) else -> "" } + appBarTitle.value = @Composable { Text(text = titleText) } Column { SearchView( - title = appBarTitle.value, + title = titleText, appNavController = appNavController, mediaType = mediaType, fab = fab diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/PeopleDetailView.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/PeopleDetailView.kt index 007f4e1..101f013 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/PeopleDetailView.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/PeopleDetailView.kt @@ -49,7 +49,7 @@ fun PersonDetailView( val decayAnimationSpec = rememberSplineBasedDecay() val topAppBarScrollState = rememberTopAppBarScrollState() val scrollBehaviour = remember(decayAnimationSpec) { - TopAppBarDefaults.exitUntilCollapsedScrollBehavior(decayAnimationSpec, topAppBarScrollState) + TopAppBarDefaults.pinnedScrollBehavior(topAppBarScrollState) } Scaffold( @@ -58,7 +58,7 @@ fun PersonDetailView( SmallTopAppBar( scrollBehavior = scrollBehaviour, colors = TopAppBarDefaults - .largeTopAppBarColors( + .smallTopAppBarColors( scrolledContainerColor = MaterialTheme.colorScheme.background, titleContentColor = MaterialTheme.colorScheme.primary ), diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/PeopleTab.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/PeopleTab.kt index d7da9f5..f7027ba 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/PeopleTab.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/main/PeopleTab.kt @@ -14,19 +14,20 @@ import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.ui.components.PagingPeoplePosterGrid import com.owenlejeune.tvtime.ui.components.SearchView import com.owenlejeune.tvtime.ui.navigation.MainNavItem -import com.owenlejeune.tvtime.ui.viewmodel.PeopleTabViewModel +import com.owenlejeune.tvtime.api.tmdb.viewmodel.PeopleTabViewModel @Composable fun PeopleTab( - appBarTitle: MutableState, + appBarTitle: MutableState<@Composable () -> Unit>, appNavController: NavHostController, 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 { SearchView( - title = appBarTitle.value, + title = titleText, appNavController = appNavController, mediaType = MediaViewType.PERSON, fab = fab diff --git a/app/src/main/res/drawable/ic_broken_image.xml b/app/src/main/res/drawable/ic_broken_image.xml new file mode 100644 index 0000000..ad5cdbe --- /dev/null +++ b/app/src/main/res/drawable/ic_broken_image.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_rectangle.xml b/app/src/main/res/drawable/ic_rectangle.xml new file mode 100644 index 0000000..ef8b191 --- /dev/null +++ b/app/src/main/res/drawable/ic_rectangle.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d932280..511c72d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -212,4 +212,5 @@ No Recommended Movies Recommended TV No Recommended TV + No more results found \ No newline at end of file