Skip to content

Commit

Permalink
add search feature for favourites
Browse files Browse the repository at this point in the history
  • Loading branch information
stslex committed Feb 3, 2024
1 parent f0f86cf commit cc15f80
Show file tree
Hide file tree
Showing 12 changed files with 199 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class MockFilmClientImpl : FilmClient {
}

private val lokiMovieFilm = MovieNetwork(
id = randomUuid(),
id = "b2cb4f71-1bf1-43fa-b047-4378f77d1286",
title = "Локи",
description = "Сразу же после кражи Тессеракта в фильме «Мстители: Финал» (2019), альтернативная версия Локи попадает в «Управление временны́ми изменениями» (УВИ), бюрократическую организацию, которая существует вне пространства и времени. Богу обмана предстоит ответить за свои преступления против времени и перед ним встаёт выбор: подвергнуться стиранию из реальности или помочь УВИ в борьбе с большей угрозой.",
poster = "https://pico.kartinka.shop/poster/item/big/72418.jpg",
Expand All @@ -66,7 +66,7 @@ private val lokiMovieFilm = MovieNetwork(
private val lokiFilm = lokiMovieFilm.toFilmItemNetwork()

private val infiniteWarMovie = MovieNetwork(
id = randomUuid(),
id = "33fa9d73-a722-4efd-b95c-0dd6af25e6af",
title = "Мстители: Война бесконечности",
description = "Пока Мстители и их союзники продолжают защищать мир от различных опасностей, с которыми не смог бы справиться один супергерой, новая угроза возникает из космоса: Танос. Межгалактический тиран преследует цель собрать все шесть Камней Бесконечности - артефакты невероятной силы, с помощью которых можно менять реальность по своему желанию. Всё, с чем Мстители сталкивались ранее, вело к этому моменту - судьба Земли никогда ещё не была столь неопределённой.",
poster = "https://pico.kartinka.shop/poster/item/big/34114.jpg",
Expand All @@ -84,7 +84,7 @@ private val infiniteWarMovie = MovieNetwork(
private val infiniteWar = infiniteWarMovie.toFilmItemNetwork()

private val rhinoFilmMovie = MovieNetwork(
id = randomUuid(),
id = "261fc394-e9cd-484e-bcd1-33152b7a0ba9",
title = "Вольт",
description = "Вольт — собака-полицейский, звезда телесериала, в котором он сражается с преступниками и спасает мир. Но когда камеры отключаются, Вольт не понимает, что происходит, и думает, что всё, что его окружает, настоящее. Когда его хозяйка Пенни похищают, Вольт отправляется в реальное путешествие, чтобы спасти её. На помощь ему приходят два необычных спутника — кот Мистер и хомяк Ролли.",
poster = "https://pico.kartinka.shop/poster/item/big/2570.jpg",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class MockProfileClientImpl : ProfileClient {

override suspend fun getFavourites(
uuid: String,
query: String,
page: Int,
pageSize: Int
): UserFavouriteResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface ProfileClient {

suspend fun getFavourites(
uuid: String,
query: String,
page: Int,
pageSize: Int
): UserFavouriteResponse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ class ProfileClientImpl(

override suspend fun getFavourites(
uuid: String,
query: String,
page: Int,
pageSize: Int
): UserFavouriteResponse = client.request {
get("$HOST/favourites") {
parameter("uuid", uuid)
parameter("query", query)
parameter("page", page)
parameter("page_size", pageSize)
}.body()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.stslex.feature.favourite.data.repository

import com.stslex.feature.favourite.data.model.FavouriteDataModel
import kotlinx.coroutines.flow.Flow

interface FavouriteRepository {

fun getFavourites(
suspend fun getFavourites(
uuid: String,
query: String,
page: Int,
pageSize: Int
): Flow<List<FavouriteDataModel>>
): List<FavouriteDataModel>

suspend fun addFavourite(model: FavouriteDataModel)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,27 @@ package com.stslex.feature.favourite.data.repository
import com.stslex.core.network.clients.profile.client.ProfileClient
import com.stslex.feature.favourite.data.model.FavouriteDataModel
import com.stslex.feature.favourite.data.model.toData
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

class FavouriteRepositoryImpl(
private val client: ProfileClient
) : FavouriteRepository {

override fun getFavourites(
override suspend fun getFavourites(
uuid: String,
query: String,
page: Int,
pageSize: Int
): Flow<List<FavouriteDataModel>> = flow {
val result = client
.getFavourites(
uuid = uuid,
page = page,
pageSize = pageSize
)
.result.map { result -> result.toData() }
emit(result)
}
): List<FavouriteDataModel> = client
.getFavourites(
uuid = uuid,
query = query,
page = page,
pageSize = pageSize
)
.result
.map { result ->
result.toData()
}

override suspend fun addFavourite(model: FavouriteDataModel) {
client.addFavourite(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package com.stslex.feature.favourite.domain.interactor

import com.stslex.feature.favourite.domain.model.FavouriteDomainModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow

interface FavouriteInteractor {

fun getFavourites(
val favourites: SharedFlow<List<FavouriteDomainModel>>

suspend fun getFavourites(
uuid: String,
query: String,
page: Int,
pageSize: Int
): Flow<List<FavouriteDomainModel>>
)

suspend fun setFavourite(model: FavouriteDomainModel)
}

Original file line number Diff line number Diff line change
@@ -1,28 +1,85 @@
package com.stslex.feature.favourite.domain.interactor

import com.stslex.core.network.utils.currentTimeMs
import com.stslex.feature.favourite.data.repository.FavouriteRepository
import com.stslex.feature.favourite.domain.model.FavouriteDomainModel
import com.stslex.feature.favourite.domain.model.toData
import com.stslex.feature.favourite.domain.model.toDomain
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import kotlin.coroutines.coroutineContext

class FavouriteInteractorImpl(
private val repository: FavouriteRepository
) : FavouriteInteractor {

override fun getFavourites(
private val _favourites = MutableSharedFlow<List<FavouriteDomainModel>>()
override val favourites: SharedFlow<List<FavouriteDomainModel>> = _favourites.asSharedFlow()

private var favouritesJob: Job? = null
private var favouritesNextPageJob: Job? = null
private var lastRequestTime = 0L

override suspend fun getFavourites(
uuid: String,
query: String,
page: Int,
pageSize: Int
): Flow<List<FavouriteDomainModel>> = repository
.getFavourites(
) {
if (lastRequestTime + REQUEST_DELAY > currentTimeMs) {
favouritesNextPageJob = getFavouritesJob(
uuid = uuid,
query = query,
page = page,
pageSize = pageSize,
start = CoroutineStart.LAZY
)
}
favouritesJob = getFavouritesJob(
uuid = uuid,
query = query,
page = page,
pageSize = pageSize
)
.map { favourites ->
favourites.map { favourite -> favourite.toDomain() }
}

private suspend fun getFavouritesJob(
uuid: String,
query: String,
page: Int,
pageSize: Int,
start: CoroutineStart = CoroutineStart.DEFAULT
): Job = CoroutineScope(coroutineContext)
.launch(
start = start
) {
favouritesJob = favouritesNextPageJob
favouritesNextPageJob = null
val items = repository
.getFavourites(
uuid = uuid,
query = query,
page = page,
pageSize = pageSize
)
.map { favourite ->
favourite.toDomain()
}
val screenItems = favourites
.replayCache
.lastOrNull()
.orEmpty()
.plus(items)
_favourites.emit(screenItems)
}.apply {
invokeOnCompletion {
favouritesNextPageJob?.start()
}
}

override suspend fun setFavourite(model: FavouriteDomainModel) {
Expand All @@ -32,4 +89,8 @@ class FavouriteInteractorImpl(
repository.removeFavourite(model.uuid)
}
}

companion object {
private const val REQUEST_DELAY = 500L
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ internal fun FavouriteScreen(
is FavouriteScreenState.Content -> FavouriteScreenContent(
state = state.screen,
items = state.data,
query = state.query,
onItemClick = { uuid ->
onAction(Action.ItemClick(uuid))
},
onLikeClick = { uuid ->
onAction(Action.LikeClick(uuid))
},
onSearch = { query ->
onAction(Action.InputSearch(query))
}
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.stslex.feature.favourite.ui.components.content

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.TextField
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.stslex.core.ui.theme.AppDimension
import com.stslex.feature.favourite.ui.model.FavouriteModel
Expand All @@ -17,43 +20,58 @@ import kotlinx.collections.immutable.ImmutableList
internal fun FavouriteScreenContent(
state: FavouriteScreenState.Content,
items: ImmutableList<FavouriteModel>,
query: String,
onSearch: (String) -> Unit,
onItemClick: (String) -> Unit,
onLikeClick: (String) -> Unit,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier.fillMaxSize(),
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
items(
count = items.size,
key = { index -> items[index].uuid },
contentType = { _ -> "content" }
) { index ->
Spacer(modifier = Modifier.height(AppDimension.Padding.medium))
items.getOrNull(index)?.let { item ->
FavouriteScreenContentItem(
item = item,
onItemClick = onItemClick,
onLikeClick = onLikeClick
)
}
}
item(
key = "bottom_padding",
contentType = { "bottom_padding" }
Column {
TextField(
value = query,
onValueChange = onSearch,
modifier = Modifier
.padding(AppDimension.Padding.medium)
.fillMaxWidth()
.padding(AppDimension.Padding.medium)
)
LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
Spacer(modifier = Modifier.height(AppDimension.Padding.medium))
items(
count = items.size,
key = { index -> items[index].uuid },
contentType = { _ -> "content" }
) { index ->
Spacer(modifier = Modifier.height(AppDimension.Padding.medium))
items.getOrNull(index)?.let { item ->
FavouriteScreenContentItem(
item = item,
onItemClick = onItemClick,
onLikeClick = onLikeClick
)
}
}
if (state is FavouriteScreenState.Content.Loading) {
item {
FavouriteScreenContentLoading(
modifier = Modifier
.padding(AppDimension.Padding.medium)
.fillMaxWidth()
)
}
}
item(
key = "bottom_padding",
contentType = { "bottom_padding" }
) {
Spacer(modifier = Modifier.height(AppDimension.Padding.medium))
}
}
}

if (state is FavouriteScreenState.Content.Loading) {
FavouriteScreenContentLoading(
modifier = Modifier.align(Alignment.Center)
)
}
}
}

Loading

0 comments on commit cc15f80

Please sign in to comment.