From 1611ebf3e0e9139ba439f35c3ca68fe102934326 Mon Sep 17 00:00:00 2001 From: Emir Demirli Date: Sun, 2 Oct 2022 16:44:37 +0300 Subject: [PATCH] actors-paging impl. --- .../data/mapper/ActorMapper.kt | 3 +- .../data/paging/ActorPagingSource.kt | 40 ++++++++++++++ .../data/repository/AppRepositoryImpl.kt | 21 ++++---- .../toprated10films/domain/model/Actor.kt | 7 +++ .../domain/repository/AppRepository.kt | 5 +- .../domain/use_cases/GetActors.kt | 6 +-- .../toprated10films/ui/model/Actor.kt | 35 ------------ .../ui/presentation/actor/ActorScreen.kt | 53 ++++++++++--------- .../ui/presentation/actor/ActorViewModel.kt | 24 +++------ .../actor/components/ActorCard.kt | 2 +- .../actor/components/LazyGridScopeExt.kt | 52 ++++++++++++++++++ .../selection/SelectionViewModel.kt | 1 + .../selection/components/PopularCard.kt | 2 + 13 files changed, 157 insertions(+), 94 deletions(-) create mode 100644 app/src/main/java/com/commandiron/toprated10films/data/paging/ActorPagingSource.kt create mode 100644 app/src/main/java/com/commandiron/toprated10films/domain/model/Actor.kt delete mode 100644 app/src/main/java/com/commandiron/toprated10films/ui/model/Actor.kt create mode 100644 app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/components/LazyGridScopeExt.kt diff --git a/app/src/main/java/com/commandiron/toprated10films/data/mapper/ActorMapper.kt b/app/src/main/java/com/commandiron/toprated10films/data/mapper/ActorMapper.kt index dedc72d..3be0fd4 100644 --- a/app/src/main/java/com/commandiron/toprated10films/data/mapper/ActorMapper.kt +++ b/app/src/main/java/com/commandiron/toprated10films/data/mapper/ActorMapper.kt @@ -1,10 +1,11 @@ package com.commandiron.toprated10films.data.mapper import com.commandiron.toprated10films.data.model.movie_db_actor.MovieDbActor -import com.commandiron.toprated10films.ui.model.Actor +import com.commandiron.toprated10films.domain.model.Actor fun MovieDbActor.toActor(): Actor { return Actor( + id = id, name = name, imageUrl = "https://image.tmdb.org/t/p/original/$profile_path" ) diff --git a/app/src/main/java/com/commandiron/toprated10films/data/paging/ActorPagingSource.kt b/app/src/main/java/com/commandiron/toprated10films/data/paging/ActorPagingSource.kt new file mode 100644 index 0000000..1aa8981 --- /dev/null +++ b/app/src/main/java/com/commandiron/toprated10films/data/paging/ActorPagingSource.kt @@ -0,0 +1,40 @@ +package com.commandiron.toprated10films.data.paging + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.commandiron.toprated10films.data.mapper.toActor +import com.commandiron.toprated10films.data.remote.MovieApi +import com.commandiron.toprated10films.domain.model.Actor + +class ActorPagingSource( + private val api: MovieApi +) : PagingSource() { + + override suspend fun load(params: LoadParams): LoadResult { + val currentPage = params.key ?: 1 + return try { + val response = api.getActors(currentPage) + val endOfPaginationReached = response.page == response.total_pages + if (!endOfPaginationReached) { + LoadResult.Page( + data = response.movieDbActors.map { it.toActor() }, + prevKey = if (currentPage == 1) null else currentPage - 1, + nextKey = currentPage + 1 + ) + } else { + LoadResult.Page( + data = emptyList(), + prevKey = null, + nextKey = null + ) + } + } catch (e: Exception) { + LoadResult.Error(e) + } + } + + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/commandiron/toprated10films/data/repository/AppRepositoryImpl.kt b/app/src/main/java/com/commandiron/toprated10films/data/repository/AppRepositoryImpl.kt index bf0eb3b..3a70276 100644 --- a/app/src/main/java/com/commandiron/toprated10films/data/repository/AppRepositoryImpl.kt +++ b/app/src/main/java/com/commandiron/toprated10films/data/repository/AppRepositoryImpl.kt @@ -1,12 +1,15 @@ package com.commandiron.toprated10films.data.repository +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData import com.commandiron.toprated10films.data.local.AppDao -import com.commandiron.toprated10films.data.mapper.toActor import com.commandiron.toprated10films.data.mapper.toGenre +import com.commandiron.toprated10films.data.paging.ActorPagingSource import com.commandiron.toprated10films.data.remote.MovieApi import com.commandiron.toprated10films.domain.model.Genre import com.commandiron.toprated10films.domain.repository.AppRepository -import com.commandiron.toprated10films.ui.model.Actor +import com.commandiron.toprated10films.domain.model.Actor import com.commandiron.toprated10films.util.Response import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow @@ -37,14 +40,10 @@ class AppRepositoryImpl( dao.insertGenre(*genres.toTypedArray()) } - override suspend fun getActors(page: Int): Flow>> = flow { - emit(Response.Loading) - try { - val actors: List = api.getActors(page).movieDbActors.map { it.toActor() } - emit(Response.Success(actors)) - }catch (e: Exception) { - emit(Response.Error(e.message ?: "AN_ERROR_OCCURRED")) - e.printStackTrace() - } + override suspend fun getActors(): Flow> { + return Pager( + config = PagingConfig(pageSize = 20), + pagingSourceFactory = { ActorPagingSource(api) } + ).flow } } \ No newline at end of file diff --git a/app/src/main/java/com/commandiron/toprated10films/domain/model/Actor.kt b/app/src/main/java/com/commandiron/toprated10films/domain/model/Actor.kt new file mode 100644 index 0000000..1bc7d07 --- /dev/null +++ b/app/src/main/java/com/commandiron/toprated10films/domain/model/Actor.kt @@ -0,0 +1,7 @@ +package com.commandiron.toprated10films.domain.model + +data class Actor( + val id: Int, + val name: String, + val imageUrl: String +) \ No newline at end of file diff --git a/app/src/main/java/com/commandiron/toprated10films/domain/repository/AppRepository.kt b/app/src/main/java/com/commandiron/toprated10films/domain/repository/AppRepository.kt index 59fcaa9..d10e740 100644 --- a/app/src/main/java/com/commandiron/toprated10films/domain/repository/AppRepository.kt +++ b/app/src/main/java/com/commandiron/toprated10films/domain/repository/AppRepository.kt @@ -1,12 +1,13 @@ package com.commandiron.toprated10films.domain.repository +import androidx.paging.PagingData import com.commandiron.toprated10films.domain.model.Genre -import com.commandiron.toprated10films.ui.model.Actor +import com.commandiron.toprated10films.domain.model.Actor import com.commandiron.toprated10films.util.Response import kotlinx.coroutines.flow.Flow interface AppRepository { suspend fun getGenres(): Flow>> suspend fun saveGenres(genres: List) - suspend fun getActors(page: Int): Flow>> + suspend fun getActors(): Flow> } \ No newline at end of file diff --git a/app/src/main/java/com/commandiron/toprated10films/domain/use_cases/GetActors.kt b/app/src/main/java/com/commandiron/toprated10films/domain/use_cases/GetActors.kt index 766bf49..c966d91 100644 --- a/app/src/main/java/com/commandiron/toprated10films/domain/use_cases/GetActors.kt +++ b/app/src/main/java/com/commandiron/toprated10films/domain/use_cases/GetActors.kt @@ -1,12 +1,12 @@ package com.commandiron.toprated10films.domain.use_cases +import androidx.paging.PagingData import com.commandiron.toprated10films.domain.repository.AppRepository -import com.commandiron.toprated10films.ui.model.Actor -import com.commandiron.toprated10films.util.Response +import com.commandiron.toprated10films.domain.model.Actor import kotlinx.coroutines.flow.Flow class GetActors( private val repository: AppRepository ) { - suspend operator fun invoke(page: Int): Flow>> = repository.getActors(page) + suspend operator fun invoke(): Flow> = repository.getActors() } \ No newline at end of file diff --git a/app/src/main/java/com/commandiron/toprated10films/ui/model/Actor.kt b/app/src/main/java/com/commandiron/toprated10films/ui/model/Actor.kt deleted file mode 100644 index 940b453..0000000 --- a/app/src/main/java/com/commandiron/toprated10films/ui/model/Actor.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.commandiron.toprated10films.ui.model - -data class Actor( - val name: String, - val imageUrl: String -) { - companion object { - val defaultActorList = listOf( - Actor( - name = "Brad Pitt", - imageUrl = "https://m.media-amazon.com/images/M/MV5BMjA1MjE2MTQ2MV5BMl5BanBnXkFtZTcwMjE5MDY0Nw@@._V1_.jpg" - ), - Actor( - name = "Brad Pitt", - imageUrl = "https://m.media-amazon.com/images/M/MV5BMjA1MjE2MTQ2MV5BMl5BanBnXkFtZTcwMjE5MDY0Nw@@._V1_.jpg" - ), - Actor( - name = "Brad Pitt", - imageUrl = "https://m.media-amazon.com/images/M/MV5BMjA1MjE2MTQ2MV5BMl5BanBnXkFtZTcwMjE5MDY0Nw@@._V1_.jpg" - ), - Actor( - name = "Brad Pitt", - imageUrl = "https://m.media-amazon.com/images/M/MV5BMjA1MjE2MTQ2MV5BMl5BanBnXkFtZTcwMjE5MDY0Nw@@._V1_.jpg" - ), - Actor( - name = "Brad Pitt", - imageUrl = "https://m.media-amazon.com/images/M/MV5BMjA1MjE2MTQ2MV5BMl5BanBnXkFtZTcwMjE5MDY0Nw@@._V1_.jpg" - ), - Actor( - name = "Brad Pitt", - imageUrl = "https://m.media-amazon.com/images/M/MV5BMjA1MjE2MTQ2MV5BMl5BanBnXkFtZTcwMjE5MDY0Nw@@._V1_.jpg" - ), - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/ActorScreen.kt b/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/ActorScreen.kt index 166d2d8..ea16936 100644 --- a/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/ActorScreen.kt +++ b/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/ActorScreen.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -20,7 +19,9 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.text.style.TextAlign import androidx.hilt.navigation.compose.hiltViewModel +import androidx.paging.compose.collectAsLazyPagingItems import com.commandiron.toprated10films.ui.presentation.actor.components.ActorCard +import com.commandiron.toprated10films.ui.presentation.actor.components.items import com.commandiron.toprated10films.ui.presentation.components.SearchTextField import com.commandiron.toprated10films.ui.theme.spacing @@ -31,7 +32,7 @@ fun ActorScreen( onClick: (actorName: String) -> Unit ) { val searchText = viewModel.searchText.collectAsState().value - val actors = viewModel.actors.collectAsState().value + val actors = viewModel.actors.collectAsLazyPagingItems() val isLoading = viewModel.isLoading.collectAsState().value val keyboardController = LocalSoftwareKeyboardController.current val focusManager = LocalFocusManager.current @@ -94,28 +95,32 @@ fun ActorScreen( horizontal = MaterialTheme.spacing.spaceMedium ) ){ - items(actors) { actor -> - ActorCard( - modifier = Modifier - .padding(MaterialTheme.spacing.spaceExtraSmall) - .clip(MaterialTheme.shapes.medium) - .aspectRatio(0.75f) - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null - ) { - if (isImeVisible) { - focusManager.clearFocus() - keyboardController?.hide() - } else { - onClick(actor.name) - } - }, - actor = actor - ) - } - item { - Spacer(Modifier.height(MaterialTheme.spacing.spaceXXXLarge)) + items( + items = actors, + key = { actor -> + actor.id + } + ) { actor -> + actor?.let { + ActorCard( + modifier = Modifier + .padding(MaterialTheme.spacing.spaceExtraSmall) + .clip(MaterialTheme.shapes.medium) + .aspectRatio(0.75f) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { + if (isImeVisible) { + focusManager.clearFocus() + keyboardController?.hide() + } else { + onClick(it.name) + } + }, + actor = it + ) + } } } } diff --git a/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/ActorViewModel.kt b/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/ActorViewModel.kt index 993ca42..4db6824 100644 --- a/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/ActorViewModel.kt +++ b/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/ActorViewModel.kt @@ -2,9 +2,10 @@ package com.commandiron.toprated10films.ui.presentation.actor import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData +import androidx.paging.cachedIn import com.commandiron.toprated10films.domain.use_cases.UseCases -import com.commandiron.toprated10films.ui.model.Actor.Companion.defaultActorList -import com.commandiron.toprated10films.util.Response +import com.commandiron.toprated10films.domain.model.Actor import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -16,8 +17,8 @@ class ActorViewModel @Inject constructor( private val useCases: UseCases ): ViewModel() { - private val _actors = MutableStateFlow(defaultActorList) - val actors = _actors.asStateFlow() + private val _actors = MutableStateFlow>(PagingData.empty()) + val actors = _actors private val _searchText = MutableStateFlow("") val searchText = _searchText.asStateFlow() @@ -27,19 +28,8 @@ class ActorViewModel @Inject constructor( init { viewModelScope.launch { - useCases.getActors(2).collect { response -> - when(response) { - is Response.Error -> { - - } - Response.Loading -> { - - } - is Response.Success -> { - println(response.data) - _actors.value = response.data - } - } + useCases.getActors().cachedIn(viewModelScope).collect { response -> + actors.value = response } } } diff --git a/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/components/ActorCard.kt b/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/components/ActorCard.kt index d91f7cd..4815a22 100644 --- a/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/components/ActorCard.kt +++ b/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/components/ActorCard.kt @@ -13,7 +13,7 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow -import com.commandiron.toprated10films.ui.model.Actor +import com.commandiron.toprated10films.domain.model.Actor import com.commandiron.toprated10films.ui.presentation.components.CustomAsyncImage import com.commandiron.toprated10films.ui.presentation.components.TopTenSticker import com.commandiron.toprated10films.ui.theme.spacing diff --git a/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/components/LazyGridScopeExt.kt b/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/components/LazyGridScopeExt.kt new file mode 100644 index 0000000..a50ee93 --- /dev/null +++ b/app/src/main/java/com/commandiron/toprated10films/ui/presentation/actor/components/LazyGridScopeExt.kt @@ -0,0 +1,52 @@ +package com.commandiron.toprated10films.ui.presentation.actor.components + +import android.annotation.SuppressLint +import android.os.Parcel +import android.os.Parcelable +import androidx.compose.foundation.lazy.grid.LazyGridItemScope +import androidx.compose.foundation.lazy.grid.LazyGridScope +import androidx.compose.runtime.Composable +import androidx.paging.compose.LazyPagingItems + +fun LazyGridScope.items( + items: LazyPagingItems, + key: ((item: T) -> Any)? = null, + itemContent: @Composable LazyGridItemScope.(item: T?) -> Unit +) { + items( + count = items.itemCount, + key = if (key == null) null else { index -> + val item = items.peek(index) + if (item == null) { + PagingPlaceholderKey(index) + } else { + key(item) + } + } + ) { index -> + itemContent(items[index]) + } +} + +@SuppressLint("BanParcelableUsage") +private data class PagingPlaceholderKey(private val index: Int) : Parcelable { + override fun writeToParcel(parcel: Parcel, flags: Int) { + parcel.writeInt(index) + } + + override fun describeContents(): Int { + return 0 + } + + companion object { + @Suppress("unused") + @JvmField + val CREATOR: Parcelable.Creator = + object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel) = + PagingPlaceholderKey(parcel.readInt()) + + override fun newArray(size: Int) = arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/commandiron/toprated10films/ui/presentation/selection/SelectionViewModel.kt b/app/src/main/java/com/commandiron/toprated10films/ui/presentation/selection/SelectionViewModel.kt index d7b1226..7a6aa36 100644 --- a/app/src/main/java/com/commandiron/toprated10films/ui/presentation/selection/SelectionViewModel.kt +++ b/app/src/main/java/com/commandiron/toprated10films/ui/presentation/selection/SelectionViewModel.kt @@ -15,4 +15,5 @@ class SelectionViewModel @Inject constructor( private val _populars = MutableStateFlow(defaultPopularList) val populars = _populars.asStateFlow() + } \ No newline at end of file diff --git a/app/src/main/java/com/commandiron/toprated10films/ui/presentation/selection/components/PopularCard.kt b/app/src/main/java/com/commandiron/toprated10films/ui/presentation/selection/components/PopularCard.kt index 11e9636..0948c8d 100644 --- a/app/src/main/java/com/commandiron/toprated10films/ui/presentation/selection/components/PopularCard.kt +++ b/app/src/main/java/com/commandiron/toprated10films/ui/presentation/selection/components/PopularCard.kt @@ -6,6 +6,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier +import com.commandiron.toprated10films.domain.model.Actor import com.commandiron.toprated10films.domain.model.Genre import com.commandiron.toprated10films.ui.model.* import com.commandiron.toprated10films.ui.presentation.actor.components.ActorCard @@ -24,6 +25,7 @@ fun PopularCard( Category.ByActor -> { ActorCard( actor = Actor( + id = 0, name = popular.title, imageUrl = popular.imageUrl ?: "" ),