Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ enum class TaskVariance {
* while COMPOSE means that MoviesComposeFragment is used instead.
*/
object TaskConstants {
val TASK_VARIANCE = TaskVariance.XML
val TASK_VARIANCE = TaskVariance.COMPOSE
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,23 @@ import app.bettermetesttask.datamovies.database.entities.MovieEntity
import kotlinx.coroutines.flow.Flow

@Dao
interface MoviesDao{
interface MoviesDao {

@Query("SELECT * FROM MoviesTable")
suspend fun selectMovies(): List<MovieEntity>

@Query("SELECT * FROM MoviesTable")
fun selectMoviesFlow(): Flow<List<MovieEntity>>

@Query("SELECT * FROM MoviesTable WHERE id = :id")
suspend fun selectMovieById(id: Int): List<MovieEntity>

@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertMovie(movie: MovieEntity)

@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertMovies(movies: List<MovieEntity>)

@Update
suspend fun updateMovie(movie: MovieEntity)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ import app.bettermetesttask.domaincore.utils.Result
import app.bettermetesttask.domainmovies.entries.Movie
import app.bettermetesttask.domainmovies.repository.MoviesRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class MoviesRepositoryImpl @Inject constructor(
private val localStore: MoviesLocalStore,
private val restStore: MoviesRestStore,
private val mapper: MoviesMapper
) : MoviesRepository {

private val restStore = MoviesRestStore()
override suspend fun getMoviesRest(): List<Movie> {
return restStore.getMovies()
}

override suspend fun getMovies(): Result<List<Movie>> {
TODO("Not yet implemented")
override fun getMoviesDAO(): Flow<List<Movie>> {
return localStore.getMoviesFlow().map { entity ->
entity.map { mapper.mapFromLocal(it) }
}
}

override suspend fun getMovie(id: Int): Result<Movie> {
Expand All @@ -35,4 +41,8 @@ class MoviesRepositoryImpl @Inject constructor(
override suspend fun removeMovieFromFavorites(movieId: Int) {
localStore.dislikeMovie(movieId)
}

override suspend fun addMoviesToDao(movies: List<Movie>) {
localStore.insertMovies(movies.map { mapper.mapToLocal(it) })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import app.bettermetesttask.datamovies.database.dao.MoviesDao
import app.bettermetesttask.datamovies.database.entities.LikedMovieEntity
import app.bettermetesttask.datamovies.database.entities.MovieEntity
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import javax.inject.Inject

Expand All @@ -20,6 +19,14 @@ class MoviesLocalStore @Inject constructor(
return moviesDao.selectMovies()
}

fun getMoviesFlow(): Flow<List<MovieEntity>> {
return moviesDao.selectMoviesFlow()
}

suspend fun insertMovies(movies: List<MovieEntity>) {
moviesDao.insertMovies(movies)
}

suspend fun getMovie(id: Int): MovieEntity {
return moviesDao.selectMovieById(id).first()
}
Expand All @@ -33,6 +40,7 @@ class MoviesLocalStore @Inject constructor(
}

fun observeLikedMoviesIds(): Flow<List<Int>> {
return moviesDao.selectLikedEntries().map { movieIdsFlow -> movieIdsFlow.map { it.movieId } }
return moviesDao.selectLikedEntries()
.map { movieIdsFlow -> movieIdsFlow.map { it.movieId } }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package app.bettermetesttask.domainmovies.interactors

import app.bettermetesttask.domainmovies.entries.Movie
import app.bettermetesttask.domainmovies.repository.MoviesRepository
import javax.inject.Inject

class AddMoviesToDaoUseCase @Inject constructor(
private val repository: MoviesRepository
) {

suspend operator fun invoke(movies: List<Movie>) {
repository.addMoviesToDao(movies)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package app.bettermetesttask.domainmovies.interactors

import app.bettermetesttask.domainmovies.entries.Movie
import app.bettermetesttask.domainmovies.repository.MoviesRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import javax.inject.Inject

class GetMoviesRestUseCase @Inject constructor(
private val repository: MoviesRepository
) {

operator fun invoke(): Flow<List<Movie>> {
return flow {
try {
val result = repository.getMoviesRest()
emit(result)
} catch (e: Exception) {
error(e)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,22 @@
package app.bettermetesttask.domainmovies.interactors

import app.bettermetesttask.domaincore.utils.Result
import app.bettermetesttask.domainmovies.entries.Movie
import app.bettermetesttask.domainmovies.repository.MoviesRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.combineTransform
import javax.inject.Inject

class ObserveMoviesUseCase @Inject constructor(
private val repository: MoviesRepository
) {

suspend operator fun invoke(): Flow<Result<List<Movie>>> {
return when (val result = repository.getMovies()) {
is Result.Success -> {
repository.observeLikedMovieIds()
.map { likedMoviesIds ->
val movies = result.data.map {
if (likedMoviesIds.contains(it.id)) {
it.copy(liked = true)
} else {
it
}
}
Result.Success(movies)
}
operator fun invoke(): Flow<List<Movie>> {
return repository.getMoviesDAO()
.combineTransform(repository.observeLikedMovieIds()) { movies, like ->
val x = movies.map { movie ->
movie.copy(liked = like.contains(movie.id))
}
emit(x)
}
is Result.Error -> {
flowOf(result)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import kotlinx.coroutines.flow.Flow

interface MoviesRepository {

suspend fun getMovies(): Result<List<Movie>>
suspend fun getMoviesRest(): List<Movie>

fun getMoviesDAO(): Flow<List<Movie>>

suspend fun getMovie(id: Int): Result<Movie>

fun observeLikedMovieIds(): Flow<List<Int>>

suspend fun addMovieToFavorites(movieId: Int)

suspend fun addMoviesToDao(movies: List<Movie>)

suspend fun removeMovieFromFavorites(movieId: Int)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@ sealed class MoviesState {

object Loading : MoviesState()

data class Loaded(val movies: List<Movie>) : MoviesState()
data class Loaded(
val movies: List<Movie>,
val showInfo: Boolean = false,
val movieInfo: Movie? = null
) : MoviesState()
}
Original file line number Diff line number Diff line change
@@ -1,55 +1,101 @@
package app.bettermetesttask.movies.sections

import androidx.lifecycle.ViewModel
import app.bettermetesttask.domaincore.utils.Result
import androidx.lifecycle.viewModelScope
import app.bettermetesttask.domainmovies.entries.Movie
import app.bettermetesttask.domainmovies.interactors.AddMovieToFavoritesUseCase
import app.bettermetesttask.domainmovies.interactors.AddMoviesToDaoUseCase
import app.bettermetesttask.domainmovies.interactors.GetMoviesRestUseCase
import app.bettermetesttask.domainmovies.interactors.ObserveMoviesUseCase
import app.bettermetesttask.domainmovies.interactors.RemoveMovieFromFavoritesUseCase
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject

class MoviesViewModel @Inject constructor(
private val observeMoviesUseCase: ObserveMoviesUseCase,
private val likeMovieUseCase: AddMovieToFavoritesUseCase,
private val dislikeMovieUseCase: RemoveMovieFromFavoritesUseCase,
private val addMoviesToDaoUseCase: AddMoviesToDaoUseCase,
private val getMoviesRestUseCase: GetMoviesRestUseCase,
private val adapter: MoviesAdapter
) : ViewModel() {

private val moviesMutableFlow: MutableStateFlow<MoviesState> = MutableStateFlow(MoviesState.Initial)
private val moviesMutableFlow: MutableStateFlow<MoviesState> =
MutableStateFlow(MoviesState.Initial)

val moviesStateFlow: StateFlow<MoviesState>
get() = moviesMutableFlow.asStateFlow()

fun loadMovies() {
GlobalScope.launch {
observeMoviesUseCase()
.collect { result ->
if (result is Result.Success) {
moviesMutableFlow.emit(MoviesState.Loaded(result.data))
adapter.submitList(result.data)
}
getMoviesDao()
getAndSaveMovies()
}

private fun getMoviesDao() {
observeMoviesUseCase.invoke()
.catch {
Timber.i("Test error")
}.onEach { result ->
Timber.i("Test loadMovies result:$result")
val current = moviesMutableFlow.first() as? MoviesState.Loaded
val newState = current?.copy(movies = result)
if (newState == null) {
moviesMutableFlow.emit(MoviesState.Loaded(result))
} else {
moviesMutableFlow.emit(newState)
}
}

adapter.submitList(result)
}
.flowOn(Dispatchers.IO)
.launchIn(viewModelScope)
}

private fun getAndSaveMovies() {
getMoviesRestUseCase.invoke()
.catch {
Timber.i("Test error")
}.onEach {
addMoviesToDaoUseCase.invoke(it)
}
.flowOn(Dispatchers.IO)
.launchIn(viewModelScope)
}

fun likeMovie(movie: Movie) {
GlobalScope.launch {
if (movie.liked) {
viewModelScope.launch {
if (!movie.liked) {
likeMovieUseCase(movie.id)
} else {
dislikeMovieUseCase(movie.id)
}
}
}

fun dismissMovieDetails() {
viewModelScope.launch {
val current = moviesMutableFlow.first() as? MoviesState.Loaded
val newState = current?.copy(showInfo = false, movieInfo = null)
newState?.let { moviesMutableFlow.emit(it) }
}
}

fun openMovieDetails(movie: Movie) {
// TODO: todo todo todo todo
Timber.i("Click xxx:$movie")
viewModelScope.launch {
val current = moviesMutableFlow.first() as? MoviesState.Loaded
val newState = current?.copy(showInfo = true, movieInfo = movie)
newState?.let { moviesMutableFlow.emit(it) }
}
}
}
Loading