mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-16 08:40:53 -05:00
paging for home page people
This commit is contained in:
@@ -108,7 +108,11 @@ class OnboardingActivity: MonetCompatActivity() {
|
||||
launchActivity(MainActivity::class.java)
|
||||
}
|
||||
) {
|
||||
Text(text = "Skip")
|
||||
val skipText = if (preferences.firstLaunchTesting)
|
||||
stringResource(id = R.string.action_skip_testing)
|
||||
else
|
||||
stringResource(id = R.string.action_skip)
|
||||
Text(text = skipText)
|
||||
}
|
||||
|
||||
HorizontalPagerIndicator(
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.HomePageService
|
||||
import com.owenlejeune.tvtime.ui.navigation.MediaFetchFun
|
||||
import org.koin.core.Koin
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import retrofit2.Response
|
||||
|
||||
class HomePagePagingSource(private val service: HomePageService, private val mediaFetch: MediaFetchFun): PagingSource<Int, TmdbItem>() {
|
||||
class HomePagePagingSource(
|
||||
private val service: HomePageService,
|
||||
private val mediaFetch: MediaFetchFun
|
||||
): PagingSource<Int, TmdbItem>(), KoinComponent {
|
||||
|
||||
private val context: Context by inject()
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Int, TmdbItem>): Int? {
|
||||
return state.anchorPosition
|
||||
@@ -25,6 +35,7 @@ class HomePagePagingSource(private val service: HomePageService, private val med
|
||||
nextKey = if (results.isEmpty() || responseBody == null) null else responseBody.page + 1
|
||||
)
|
||||
} else {
|
||||
Toast.makeText(context, "No more results found", Toast.LENGTH_SHORT).show()
|
||||
LoadResult.Invalid()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
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.api.tmdb.api.v3.PeopleApi
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.PeopleService
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class HomePagePeoplePagingSource: PagingSource<Int, HomePagePerson>(), KoinComponent {
|
||||
|
||||
private val service: PeopleApi by inject()
|
||||
private val context: Context by inject()
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Int, HomePagePerson>): Int? {
|
||||
return state.anchorPosition
|
||||
}
|
||||
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, HomePagePerson> {
|
||||
return try {
|
||||
val nextPage = params.key ?: 1
|
||||
val peopleResponse = service.getPopular(page = nextPage)
|
||||
if (peopleResponse.isSuccessful) {
|
||||
val responseBody = peopleResponse.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
|
||||
)
|
||||
} else {
|
||||
Toast.makeText(context, "No more results found", Toast.LENGTH_SHORT).show()
|
||||
LoadResult.Invalid()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return LoadResult.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.owenlejeune.tvtime.extensions
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.lazy.LazyItemScope
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridItemScope
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridScope
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -27,6 +28,17 @@ fun <T: Any> LazyGridScope.listItems(
|
||||
}
|
||||
}
|
||||
|
||||
fun LazyGridScope.header(
|
||||
content: @Composable LazyGridItemScope.() -> Unit
|
||||
) {
|
||||
item(
|
||||
span = {
|
||||
GridItemSpan(maxLineSpan)
|
||||
},
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
fun <T: Any> LazyListScope.listItems(
|
||||
items: Collection<T>,
|
||||
itemContent: @Composable (value: T) -> Unit
|
||||
|
||||
@@ -129,7 +129,7 @@ class AppPreferences(context: Context) {
|
||||
get() = preferences.getBoolean(FIRST_LAUNCH, true)
|
||||
set(value) { preferences.put(FIRST_LAUNCH, value) }
|
||||
|
||||
val useV4ApiDefault: Boolean = false
|
||||
val useV4ApiDefault: Boolean = true
|
||||
var useV4Api: Boolean
|
||||
get() = preferences.getBoolean(USE_V4_API, useV4ApiDefault)
|
||||
set(value) { preferences.put(USE_V4_API, value) }
|
||||
|
||||
@@ -35,6 +35,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Person
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
|
||||
import com.owenlejeune.tvtime.extensions.dpToPx
|
||||
import com.owenlejeune.tvtime.extensions.header
|
||||
import com.owenlejeune.tvtime.extensions.lazyPagingItems
|
||||
import com.owenlejeune.tvtime.extensions.listItems
|
||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||
@@ -96,6 +97,46 @@ fun PagingPosterGrid(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PagingPeoplePosterGrid(
|
||||
lazyPagingItems: LazyPagingItems<HomePagePerson>?,
|
||||
header: @Composable () -> Unit = {},
|
||||
onClick: (id: Int) -> Unit = {}
|
||||
) {
|
||||
lazyPagingItems?.let {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Adaptive(minSize = POSTER_WIDTH),
|
||||
contentPadding = PaddingValues(8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
header {
|
||||
header()
|
||||
}
|
||||
|
||||
lazyPagingItems(lazyPagingItems) { person ->
|
||||
person?.let {
|
||||
PosterItem(
|
||||
url = TmdbUtils.getFullPersonImagePath(person.profilePath),
|
||||
noDataImage = R.drawable.no_person_photo,
|
||||
modifier = Modifier.padding(5.dp),
|
||||
onClick = {
|
||||
onClick(person.id)
|
||||
},
|
||||
contentDescription = person.name
|
||||
)
|
||||
}
|
||||
}
|
||||
lazyPagingItems.apply {
|
||||
when {
|
||||
loadState.refresh is LoadState.Loading -> {}
|
||||
loadState.append is LoadState.Loading -> {}
|
||||
loadState.append is LoadState.Error -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun PeoplePosterGrid(
|
||||
@@ -187,13 +228,14 @@ fun PosterItem(
|
||||
Card(
|
||||
elevation = elevation,
|
||||
modifier = modifier
|
||||
.size(width = width, height = height),
|
||||
.width(width = width)
|
||||
.wrapContentHeight(),
|
||||
shape = RoundedCornerShape(5.dp)
|
||||
) {
|
||||
if (url != null) {
|
||||
AsyncImage(
|
||||
modifier = Modifier
|
||||
.size(width = width, height = height)
|
||||
.width(width = width)
|
||||
.clip(RoundedCornerShape(5.dp))
|
||||
.clickable(
|
||||
onClick = onClick
|
||||
@@ -201,7 +243,7 @@ fun PosterItem(
|
||||
model = url,
|
||||
placeholder = rememberAsyncImagePainter(model = placeholder),
|
||||
contentDescription = contentDescription,
|
||||
contentScale = ContentScale.FillBounds
|
||||
contentScale = ContentScale.FillWidth
|
||||
)
|
||||
} else {
|
||||
Image(
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
package com.owenlejeune.tvtime.ui.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.HomePageService
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePagingSource
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePageResponse
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
|
||||
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
|
||||
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.utils.ResourceUtils
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.koin.core.component.inject
|
||||
import retrofit2.Response
|
||||
|
||||
@@ -94,20 +84,4 @@ private val screenContent: MediaNavComposableFun = { appNavController, mediaView
|
||||
|
||||
typealias MediaNavComposableFun = @Composable (NavHostController, MediaViewType, MediaTabNavItem) -> Unit
|
||||
|
||||
typealias MediaFetchFun = suspend (service: HomePageService, page: Int) -> Response<out HomePageResponse>
|
||||
|
||||
sealed class MediaTabViewModel(service: HomePageService, mediaFetchFun: MediaFetchFun): ViewModel() {
|
||||
val mediaItems: Flow<PagingData<TmdbItem>> = Pager(PagingConfig(pageSize = Int.MAX_VALUE)) {
|
||||
HomePagePagingSource(service = service, mediaFetch = mediaFetchFun)
|
||||
}.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) })
|
||||
|
||||
}
|
||||
typealias MediaFetchFun = suspend (service: HomePageService, page: Int) -> Response<out HomePageResponse>
|
||||
@@ -1,14 +1,9 @@
|
||||
package com.owenlejeune.tvtime.ui.screens.main
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
@@ -17,24 +12,12 @@ import com.google.accompanist.pager.HorizontalPager
|
||||
import com.google.accompanist.pager.PagerState
|
||||
import com.google.accompanist.pager.rememberPagerState
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.HomePageService
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService
|
||||
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||
import com.owenlejeune.tvtime.ui.components.PagingPosterGrid
|
||||
import com.owenlejeune.tvtime.ui.components.PosterGrid
|
||||
import com.owenlejeune.tvtime.ui.components.SearchBar
|
||||
import com.owenlejeune.tvtime.ui.components.SearchView
|
||||
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
||||
import com.owenlejeune.tvtime.ui.navigation.MediaFetchFun
|
||||
import com.owenlejeune.tvtime.ui.navigation.MediaTabNavItem
|
||||
import com.owenlejeune.tvtime.ui.navigation.MediaTabViewModel
|
||||
import com.owenlejeune.tvtime.ui.screens.main.tabs.top.Tabs
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.java.KoinJavaComponent.get
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.MediaTabViewModel
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
package com.owenlejeune.tvtime.ui.screens.main
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.PeopleService
|
||||
import com.owenlejeune.tvtime.ui.components.PeoplePosterGrid
|
||||
import com.owenlejeune.tvtime.ui.components.PagingPeoplePosterGrid
|
||||
import com.owenlejeune.tvtime.ui.components.SearchView
|
||||
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.owenlejeune.tvtime.ui.viewmodel.PeopleTabViewModel
|
||||
|
||||
@Composable
|
||||
fun PeopleTab(
|
||||
@@ -23,8 +24,6 @@ fun PeopleTab(
|
||||
) {
|
||||
appBarTitle.value = stringResource(id = R.string.nav_people_title)
|
||||
|
||||
val service = PeopleService()
|
||||
|
||||
Column {
|
||||
SearchView(
|
||||
title = appBarTitle.value,
|
||||
@@ -33,16 +32,15 @@ fun PeopleTab(
|
||||
fab = fab
|
||||
)
|
||||
|
||||
PeoplePosterGrid(
|
||||
fetchPeople = { peopleList ->
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val response = service.getPopular()
|
||||
if (response.isSuccessful) {
|
||||
withContext(Dispatchers.Main) {
|
||||
peopleList.value = response.body()?.results ?: emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
val peopleList = PeopleTabViewModel().popularPeople.collectAsLazyPagingItems()
|
||||
|
||||
PagingPeoplePosterGrid(
|
||||
lazyPagingItems = peopleList,
|
||||
header = {
|
||||
Text(
|
||||
text = stringResource(R.string.popular_today_header),
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
},
|
||||
onClick = { id ->
|
||||
appNavController.navigate(
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.owenlejeune.tvtime.ui.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.HomePageService
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePagingSource
|
||||
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<PagingData<TmdbItem>> = Pager(PagingConfig(pageSize = Int.MAX_VALUE)) {
|
||||
HomePagePagingSource(service = service, mediaFetch = mediaFetchFun)
|
||||
}.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) })
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.owenlejeune.tvtime.ui.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.HomePagePeoplePagingSource
|
||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePerson
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class PeopleTabViewModel: ViewModel() {
|
||||
|
||||
val popularPeople: Flow<PagingData<HomePagePerson>> = Pager(PagingConfig(pageSize = Int.MAX_VALUE)) {
|
||||
HomePagePeoplePagingSource()
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
|
||||
}
|
||||
@@ -148,4 +148,7 @@
|
||||
<!-- search results -->
|
||||
<string name="search_result_tv_series">TV Series</string>
|
||||
<string name="no_search_results">No search results found</string>
|
||||
<string name="action_skip">Skip</string>
|
||||
<string name="action_skip_testing">Skip (testing)</string>
|
||||
<string name="popular_today_header">Popular Today</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user