Skip to content

Commit

Permalink
Add Pagination for Search and fix ui bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
hadi-norouzi committed Jul 21, 2024
1 parent c188ce3 commit 30b06af
Show file tree
Hide file tree
Showing 18 changed files with 204 additions and 188 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import io.filmtime.data.model.Person
import io.filmtime.data.model.SearchResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ interface TmdbMoviesRemoteSource {
suspend fun getSimilar(movieId: Int): Result<List<VideoThumbnail>, GeneralError>

suspend fun getCollection(collectionId: Int): Result<MovieCollection, GeneralError>

suspend fun searchMovie(query: String): Result<List<VideoThumbnail>, GeneralError>

companion object {
const val PAGE_SIZE = 20 // TMDB API default page size
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import javax.inject.Inject
internal class TmdbMoviesRemoteSourceImpl @Inject constructor(
private val tmdbMoviesService: TmdbMoviesService,
private val tmdbCollectionService: TmdbCollectionService,
private val tmdbSearchRemoteSource: TmdbSearchRemoteSource,
) : TmdbMoviesRemoteSource {

override suspend fun trendingMovies(page: Int): Result<List<VideoThumbnail>, GeneralError> =
Expand Down Expand Up @@ -99,11 +100,6 @@ internal class TmdbMoviesRemoteSourceImpl @Inject constructor(
is NetworkResponse.UnknownError -> Result.Failure(GeneralError.UnknownError(result.error))
}

override suspend fun searchMovie(query: String): Result<List<VideoThumbnail>, GeneralError> =
getMovieList {
tmdbMoviesService.searchMovies(query)
}

override suspend fun upcomingMovies(
page: Int,
): Result<List<VideoThumbnail>, GeneralError> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import io.filmtime.data.model.SearchResult.Video

interface TmdbSearchRemoteSource {

suspend fun searchMovies(query: String): Result<List<Video>, GeneralError>
suspend fun searchMovies(page: Int, query: String): Result<List<Video>, GeneralError>

suspend fun searchTvShows(query: String): Result<List<TvShow>, GeneralError>
suspend fun searchTvShows(page: Int, query: String): Result<List<TvShow>, GeneralError>

suspend fun searchAll(query: String): Result<List<SearchResult>, GeneralError>
suspend fun searchAll(page: Int, query: String): Result<List<SearchResult>, GeneralError>
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import javax.inject.Inject
class TmdbSearchRemoteSourceImpl @Inject constructor(
private val tmdbSearchService: TmdbSearchService,
) : TmdbSearchRemoteSource {
override suspend fun searchMovies(query: String): Result<List<SearchResult.Video>, GeneralError> =
when (val result = tmdbSearchService.searchMovies(query)) {
override suspend fun searchMovies(page: Int, query: String): Result<List<SearchResult.Video>, GeneralError> =
when (val result = tmdbSearchService.searchMovies(page, query)) {
is NetworkResponse.Success -> {
val videoListResponse = result.body?.results ?: emptyList()
Result.Success(
Expand All @@ -33,8 +33,8 @@ class TmdbSearchRemoteSourceImpl @Inject constructor(
is NetworkResponse.UnknownError -> Result.Failure(GeneralError.UnknownError(result.error))
}

override suspend fun searchTvShows(query: String): Result<List<SearchResult.TvShow>, GeneralError> =
when (val result = tmdbSearchService.searchTvShows(query)) {
override suspend fun searchTvShows(page: Int, query: String): Result<List<SearchResult.TvShow>, GeneralError> =
when (val result = tmdbSearchService.searchTvShows(page, query)) {
is NetworkResponse.Success -> {
val videoListResponse = result.body?.results ?: emptyList()
Result.Success(
Expand All @@ -53,8 +53,8 @@ class TmdbSearchRemoteSourceImpl @Inject constructor(
is NetworkResponse.UnknownError -> Result.Failure(GeneralError.UnknownError(result.error))
}

override suspend fun searchAll(query: String): Result<List<SearchResult>, GeneralError> =
when (val result = tmdbSearchService.searchMulti(query)) {
override suspend fun searchAll(page: Int, query: String): Result<List<SearchResult>, GeneralError> =
when (val result = tmdbSearchService.searchMulti(page, query)) {
is NetworkResponse.Success -> {
val videoListResponse = result.body?.results ?: emptyList()
val mappedData: List<SearchResult> = videoListResponse.map {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ interface TmdbSearchService {

@GET("search/tv")
suspend fun searchTvShows(
@Query("page") page: Int,
@Query("query") query: String,
): NetworkResponse<TmdbShowListResponse, TmdbErrorResponse>

@GET("search/movie")
suspend fun searchMovies(
@Query("page") page: Int,
@Query("query") query: String,
): NetworkResponse<TmdbVideoListResponse, TmdbErrorResponse>

@GET("search/multi")
suspend fun searchMulti(
@Query("page") page: Int,
@Query("query") query: String,
): NetworkResponse<TmdbSearchListResponse, TmdbErrorResponse>
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ interface TmdbMovieRepository {
suspend fun getSimilar(movieId: Int): Result<List<VideoThumbnail>, GeneralError>

suspend fun getCollection(collectionId: Int): Result<MovieCollection, GeneralError>

suspend fun searchMovies(query: String): Result<List<VideoThumbnail>, GeneralError>
}

enum class MovieListType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,4 @@ internal class TmdbMovieRepositoryImpl @Inject constructor(

override suspend fun getCollection(collectionId: Int): Result<MovieCollection, GeneralError> =
tmdbMoviesRemoteSource.getCollection(collectionId)

override suspend fun searchMovies(query: String): Result<List<VideoThumbnail>, GeneralError> =
tmdbMoviesRemoteSource.searchMovie(query)
}
2 changes: 2 additions & 0 deletions data/tmdb-search/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ android {
dependencies {

implementation(project(":data:api:tmdb"))

api(libs.paging.runtime)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.filmtime.data.tmdb.search

import androidx.paging.PagingSource
import androidx.paging.PagingState
import io.filmtime.data.api.tmdb.TmdbMoviesRemoteSource
import io.filmtime.data.api.tmdb.TmdbSearchRemoteSource
import io.filmtime.data.model.GeneralError
import io.filmtime.data.model.Result
import io.filmtime.data.model.Result.Failure
import io.filmtime.data.model.Result.Success
import io.filmtime.data.model.SearchResult
import io.filmtime.data.model.SearchType
import io.filmtime.data.model.SearchType.All
import io.filmtime.data.model.SearchType.Movie
import io.filmtime.data.model.SearchType.Show
import io.filmtime.data.model.toThrowable

class SearchPagingSource constructor(
private val tmdbSearchRemoteSource: TmdbSearchRemoteSource,
private val searchType: SearchType,
private val query: String,
) : PagingSource<Int, SearchResult>() {
override fun getRefreshKey(state: PagingState<Int, SearchResult>): Int = STARTING_PAGE_INDEX

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, SearchResult> = try {
val page = params.key ?: STARTING_PAGE_INDEX
when (val response = search(page, query)) {
is Success -> {
val result = response.data
LoadResult.Page(
data = result,
prevKey = if (page == STARTING_PAGE_INDEX) null else page - 1,
nextKey = if (result.size < PAGE_SIZE) null else page + 1,
)
}

is Failure -> LoadResult.Error(response.error.toThrowable())
}
} catch (e: Exception) {
LoadResult.Error(e)
}

private suspend fun search(page: Int, query: String): Result<List<SearchResult>, GeneralError> =
when (searchType) {
All -> tmdbSearchRemoteSource.searchAll(page, query)
Movie -> tmdbSearchRemoteSource.searchMovies(page, query)
Show -> tmdbSearchRemoteSource.searchTvShows(page, query)
}

companion object {
private const val STARTING_PAGE_INDEX = 1
private const val PAGE_SIZE = TmdbMoviesRemoteSource.PAGE_SIZE
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package io.filmtime.data.tmdb.search

import io.filmtime.data.model.GeneralError
import io.filmtime.data.model.Result
import androidx.paging.PagingData
import io.filmtime.data.model.SearchResult
import io.filmtime.data.model.SearchResult.TvShow
import io.filmtime.data.model.SearchType
import kotlinx.coroutines.flow.Flow

interface TmdbSearchRepository {
suspend fun searchMovies(query: String): Result<List<SearchResult.Video>, GeneralError>

suspend fun searchTvShows(query: String): Result<List<TvShow>, GeneralError>

suspend fun searchAll(query: String): Result<List<SearchResult>, GeneralError>
fun streamSearch(searchType: SearchType, query: String): Flow<PagingData<SearchResult>>
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
package io.filmtime.data.tmdb.search

import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import io.filmtime.data.api.tmdb.TmdbMoviesRemoteSource
import io.filmtime.data.api.tmdb.TmdbSearchRemoteSource
import io.filmtime.data.model.GeneralError
import io.filmtime.data.model.Result
import io.filmtime.data.model.SearchResult
import io.filmtime.data.model.SearchResult.TvShow
import io.filmtime.data.model.SearchResult.Video
import io.filmtime.data.model.SearchType
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

internal class TmdbSearchRepositoryImpl @Inject constructor(
private val tmdbDataSource: TmdbSearchRemoteSource,
) : TmdbSearchRepository {
override suspend fun searchMovies(query: String): Result<List<Video>, GeneralError> =
tmdbDataSource.searchMovies(query)

override suspend fun searchTvShows(query: String): Result<List<TvShow>, GeneralError> =
tmdbDataSource.searchTvShows(query)

override suspend fun searchAll(query: String): Result<List<SearchResult>, GeneralError> =
tmdbDataSource.searchAll(query)
override fun streamSearch(searchType: SearchType, query: String): Flow<PagingData<SearchResult>> =
Pager(
config = PagingConfig(
pageSize = TmdbMoviesRemoteSource.PAGE_SIZE,
enablePlaceholders = false,
),
pagingSourceFactory = {
SearchPagingSource(
tmdbSearchRemoteSource = tmdbDataSource,
searchType = searchType,
query = query,
)
},
).flow
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package io.filmtime.domain.tmdb.search

import io.filmtime.data.model.GeneralError
import io.filmtime.data.model.Result
import androidx.paging.PagingData
import io.filmtime.data.model.SearchResult
import io.filmtime.data.model.SearchType
import kotlinx.coroutines.flow.Flow

interface SearchTmdbUseCase {
suspend operator fun invoke(query: String, type: SearchType): Result<List<SearchResult>, GeneralError>
operator fun invoke(query: String, type: SearchType): Flow<PagingData<SearchResult>>
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
package io.filmtime.domain.tmdb.search.impl

import io.filmtime.data.model.GeneralError
import io.filmtime.data.model.Result
import androidx.paging.PagingData
import io.filmtime.data.model.SearchResult
import io.filmtime.data.model.SearchType
import io.filmtime.data.model.SearchType.All
import io.filmtime.data.model.SearchType.Movie
import io.filmtime.data.model.SearchType.Show
import io.filmtime.data.tmdb.search.TmdbSearchRepository
import io.filmtime.domain.tmdb.search.SearchTmdbUseCase
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

internal class SearchTmdbUseCaseImpl @Inject constructor(
private val tmdbSearchRepository: TmdbSearchRepository,
) : SearchTmdbUseCase {

override suspend fun invoke(
override fun invoke(
query: String,
type: SearchType,
): Result<List<SearchResult>, GeneralError> = when (type) {
All -> tmdbSearchRepository.searchAll(query)
Movie -> tmdbSearchRepository.searchMovies(query)
Show -> tmdbSearchRepository.searchTvShows(query)
}
): Flow<PagingData<SearchResult>> =
tmdbSearchRepository.streamSearch(type, query)
}
2 changes: 2 additions & 0 deletions feature/search/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ android {
dependencies {
implementation(project(":data:model"))
implementation(project(":domain:tmdb-search"))

api(libs.paging.compose)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package io.filmtime.feature.search

import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.navigation
import io.filmtime.core.ui.common.DestinationRoute
import io.filmtime.core.ui.navigation.DestinationRoute

val GRAPH_SEARCH_ROUTE = DestinationRoute("search_graph_route")
private const val ROUTE_SEARCH_SCREEN = "search"
Expand All @@ -19,8 +20,10 @@ fun NavGraphBuilder.searchGraph(
) {
composable("${GRAPH_SEARCH_ROUTE.route}/$ROUTE_SEARCH_SCREEN") {
SearchScreen(
viewModel = hiltViewModel(),
onMovieClick = { onMovieClick(GRAPH_SEARCH_ROUTE, it) },
onShowClick = { onShowClick(GRAPH_SEARCH_ROUTE, it) },
onPersonClick = {},
)
}
nestedGraphs(GRAPH_SEARCH_ROUTE)
Expand Down
Loading

0 comments on commit 30b06af

Please sign in to comment.