Skip to content

Commit

Permalink
[Add] Implement movie details screen
Browse files Browse the repository at this point in the history
  • Loading branch information
daolq3012 authored and daolq-2712 committed Jan 24, 2024
1 parent a5c2e75 commit b1db788
Show file tree
Hide file tree
Showing 19 changed files with 344 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions app/src/main/java/com/sun/android/base/BaseFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ abstract class BaseFragment<VB : ViewBinding>(private val inflate: FragmentInfla

protected abstract fun initData()

protected abstract fun bindData()

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = inflate.invoke(inflater, container, false)
return binding.root
Expand All @@ -31,6 +33,7 @@ abstract class BaseFragment<VB : ViewBinding>(private val inflate: FragmentInfla
super.onViewCreated(view, savedInstanceState)
initView()
initData()
bindData()
}

override fun onDestroyView() {
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/sun/android/data/MovieRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ import com.sun.android.utils.DataResult

interface MovieRepository {
suspend fun getMovies(): DataResult<List<Movie>>

suspend fun getDetailMovies(movieId: Int): DataResult<Movie>
}
3 changes: 3 additions & 0 deletions app/src/main/java/com/sun/android/data/model/Movie.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import kotlinx.android.parcel.Parcelize

@Parcelize
data class Movie(
@SerializedName("id")
@Expose
var id: Int = -1,
@SerializedName("backdrop_path")
@Expose
var backDropImage: String = "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ class MovieRepositoryImpl constructor(
override suspend fun getMovies() = withResultContext {
remote.getMovies().data
}

override suspend fun getDetailMovies(movieId: Int) = withResultContext {
remote.getMovieDetail(movieId = movieId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ interface MovieDataSource {
*/
interface Remote {
suspend fun getMovies(): BaseResponse<List<Movie>>

suspend fun getMovieDetail(movieId: Int): Movie
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import com.sun.android.utils.Constant

class MovieRemoteImpl(private val apiService: ApiService) : MovieDataSource.Remote {
override suspend fun getMovies(): BaseResponse<List<Movie>> {
return apiService.getTopRateMovies(Constant.BASE_API_KEY)
return apiService.getTopRateMovies(apiKey = Constant.BASE_API_KEY)
}

override suspend fun getMovieDetail(movieId: Int): Movie {
return apiService.getMovieDetails(movieId = movieId, apiKey = Constant.BASE_API_KEY)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.sun.android.data.source.remote.api
import com.sun.android.data.model.Movie
import com.sun.android.data.source.remote.api.response.BaseResponse
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query

interface ApiService {
Expand All @@ -11,4 +12,10 @@ interface ApiService {
@Query("api_key") apiKey: String?,
@Query("page") page: Int = 1
): BaseResponse<List<Movie>>

@GET("movie/{movieId}")
suspend fun getMovieDetails(
@Path("movieId") movieId: Int,
@Query("api_key") apiKey: String?
): Movie
}
2 changes: 2 additions & 0 deletions app/src/main/java/com/sun/android/di/ViewModelModule.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.sun.android.di

import com.sun.android.ui.MainViewModel
import com.sun.android.ui.detail.MovieDetailViewModel
import com.sun.android.ui.listmovie.MoviesViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.core.module.Module
Expand All @@ -9,4 +10,5 @@ import org.koin.dsl.module
val ViewModelModule: Module = module {
viewModel { MainViewModel() }
viewModel { MoviesViewModel(get()) }
viewModel { MovieDetailViewModel(get()) }
}
2 changes: 1 addition & 1 deletion app/src/main/java/com/sun/android/ui/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ package com.sun.android.ui

import com.sun.android.base.BaseViewModel

class MainViewModel() : BaseViewModel()
class MainViewModel : BaseViewModel()
45 changes: 45 additions & 0 deletions app/src/main/java/com/sun/android/ui/detail/DetailFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.sun.android.ui.detail

import androidx.core.os.bundleOf
import androidx.lifecycle.Observer
import com.sun.android.base.BaseFragment
import com.sun.android.databinding.FragmentDetailBinding
import com.sun.android.utils.extension.goBackFragment
import com.sun.android.utils.extension.loadImageCircleWithUrl
import com.sun.android.utils.extension.loadImageWithUrl
import org.koin.androidx.viewmodel.ext.android.viewModel

class DetailFragment : BaseFragment<FragmentDetailBinding>(FragmentDetailBinding::inflate) {

override val viewModel: MovieDetailViewModel by viewModel()

override fun initView() {
binding.buttonImageBack.setOnClickListener { goBackFragment() }
}

override fun initData() {
arguments?.run {
val mMovieId = getInt(ARGUMENT_MOVIE_ID, -1)
viewModel.requestMovieDetails(mMovieId)
}
}

override fun bindData() {
viewModel.movie.observe(viewLifecycleOwner, Observer {
binding.imageBackDrop.loadImageWithUrl(it.backDropImage)
binding.imageMovie.loadImageCircleWithUrl(it.urlImage)
binding.textTitle.text = it.title
binding.textDescription.text = it.overView
binding.textRatting.text = it.vote.toString()
binding.textTotalReview.text = it.voteCount.toString()
})
}

companion object {
private const val ARGUMENT_MOVIE_ID = "ARGUMENT_MOVIE_ID"

fun newInstance(movieId: Int) = DetailFragment().apply {
arguments = bundleOf(ARGUMENT_MOVIE_ID to movieId)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.sun.android.ui.detail

import com.sun.android.base.BaseViewModel
import com.sun.android.data.MovieRepository
import com.sun.android.data.model.Movie
import com.sun.android.utils.livedata.SingleLiveData

class MovieDetailViewModel(private val movieRepository: MovieRepository) : BaseViewModel() {
val movie = SingleLiveData<Movie>()

fun requestMovieDetails(movieId: Int) {
launchTaskSync(onRequest = {
movieRepository.getDetailMovies(movieId)
},
onSuccess = {
movie.value = it
})
}
}
12 changes: 11 additions & 1 deletion app/src/main/java/com/sun/android/ui/listmovie/MoviesFragment.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.sun.android.ui.listmovie

import androidx.lifecycle.Observer
import com.sun.android.R
import com.sun.android.base.BaseFragment
import com.sun.android.data.model.Movie
import com.sun.android.databinding.MoviesFragmentBinding
import com.sun.android.ui.detail.DetailFragment
import com.sun.android.ui.listmovie.adapter.MoviesAdapter
import com.sun.android.utils.extension.addFragment
import com.sun.android.utils.extension.notNull
import com.sun.android.utils.recycler.OnItemRecyclerViewClickListener
import org.koin.androidx.viewmodel.ext.android.viewModel

Expand All @@ -24,11 +28,17 @@ class MoviesFragment : BaseFragment<MoviesFragmentBinding>(MoviesFragmentBinding

override fun initData() {
viewModel.requestTopRateMovies()
}

override fun bindData() {
viewModel.movies.observe(this, Observer { movies ->
mMovieAdapter.updateData(movies)
})
}

override fun onItemClick(item: Movie?) {
override fun onItemClick(item: Movie?) {
item.notNull {
addFragment(R.id.layoutContainer, DetailFragment.newInstance(it.id), true)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.sun.android.utils.extension

import androidx.annotation.IdRes
import androidx.fragment.app.Fragment
import com.sun.android.R

fun Fragment.addFragment(
@IdRes containerId: Int,
fragment: Fragment,
addToBackStack: Boolean = false,
tag: String? = fragment::class.java.simpleName
) {
activity?.supportFragmentManager?.apply {
beginTransaction().apply {
if (addToBackStack) {
addToBackStack(tag)
}
setCustomAnimations(
R.anim.slide_in_right,
R.anim.slide_in_right,
R.anim.slide_out_right,
R.anim.slide_out_right
)
add(containerId, fragment, tag)
}.commit()
}
}

fun Fragment.replaceFragment(
@IdRes containerId: Int,
fragment: Fragment,
addToBackStack: Boolean,
tag: String? = fragment::class.java.simpleName
) {
activity?.supportFragmentManager?.apply {
beginTransaction().apply {
if (addToBackStack) {
addToBackStack(tag)
}
replace(containerId, fragment, tag)
}.commit()
}
}

fun Fragment.goBackFragment(): Boolean {
activity?.supportFragmentManager?.notNull {
val isShowPreviousPage = it.backStackEntryCount > 0
if (isShowPreviousPage) {
it.popBackStackImmediate()
}
return isShowPreviousPage
}
return false
}
10 changes: 10 additions & 0 deletions app/src/main/res/anim/slide_in_right.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

<translate
android:duration="300"
android:fromXDelta="100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>
10 changes: 10 additions & 0 deletions app/src/main/res/anim/slide_out_right.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

<translate
android:duration="300"
android:fromXDelta="0%"
android:fromYDelta="0%"
android:toXDelta="100%"
android:toYDelta="0%" />
</set>
11 changes: 11 additions & 0 deletions app/src/main/res/drawable/ic_back.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:autoMirrored="true"
android:tint="#FFFFFF"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
</vector>
Loading

0 comments on commit b1db788

Please sign in to comment.