Skip to content

Commit

Permalink
Merge branch 'master' into troubleshooting/paging_adapter_scrolling_bug
Browse files Browse the repository at this point in the history
# Conflicts:
#	app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt
#	app/src/main/res/layout/item_screening.xml
  • Loading branch information
aseoane-simtlix committed Sep 21, 2021
2 parents 6289469 + c624ca2 commit ffeca9a
Show file tree
Hide file tree
Showing 35 changed files with 1,102 additions and 81 deletions.
68 changes: 56 additions & 12 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,84 @@ name: TMDB CI

on:
push:
branches: [ master ]
branches: [ master, feature/github_actions ]
pull_request:
branches: [ master ]
branches: [ master, feature/github_actions ]

jobs:
test:
name: Run Unit Tests
runs-on: ubuntu-18.04
runs-on: ubuntu-latest #Each job runs in a runner environment specified
steps:
- uses: actions/checkout@v1
- name: set up JDK 1.8
uses: actions/setup-java@v1
- uses: actions/checkout@v2.3.3
- name: set up JDK 11
uses: actions/setup-java@v2
with:
java-version: 1.8
java-version: '11'
distribution: 'adopt'
cache: gradle
- name: Unit tests
run: bash ./gradlew test --stacktrace
- name: Unit tests results
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v2.2.3
with:
name: unit-tests-results
path: app/build/reports/tests/testDebugUnitTest/index.html

lint:
name: Lint Check
runs-on: ubuntu-latest
steps:
- uses: actions/[email protected]
- name: set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
cache: gradle
- name: Lint debug flavor
run: bash ./gradlew lintDebug --stacktrace
- name: Lint results
uses: actions/[email protected]
with:
name: lint-result
path: app/build/reports/lint-results-debug.html

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v2.3.3
- name: set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
cache: gradle

- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build
run: bash ./gradlew assembleDebug --stacktrace

- name: Upload APK
uses: actions/[email protected]
with:
name: app
path: app/build/outputs/apk/debug/*.apk

- name: Send mail
if: always()
uses: dawidd6/action-send-mail@v2
with:
# mail server settings
server_address: smtp.gmail.com
server_port: 465
# user credentials
username: ${{ secrets.EMAIL_USERNAME }}
password: ${{ secrets.EMAIL_PASSWORD }}
# email subject
subject: ${{ github.job }} job of ${{ github.repository }} has ${{ job.status }}
# email body as text
body: ${{ github.job }} job in worflow ${{ github.workflow }} of ${{ github.repository }} has ${{ job.status }}
# comma-separated string, send email to
to: [email protected]
# from email name
from: TMDB Team! GitHub Actions!
9 changes: 9 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@ plugins {
id 'dagger.hilt.android.plugin'
id 'androidx.navigation.safeargs.kotlin'
id 'kotlin-parcelize'
id "org.sonarqube" version "3.3"
}

android {
compileSdk 30
compileOptions {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
// Sets Java compatibility to Java 8
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

def _major
def _minor
Expand Down Expand Up @@ -96,6 +104,7 @@ dependencies {
implementation "com.github.bumptech.glide:glide:$glideVersion"
implementation "androidx.palette:palette-ktx:$paletteVersion"
implementation "com.jakewharton.timber:timber:$timberVersion"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation "androidx.room:room-ktx:$roomVersion"

testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.intive.tmdbandroid.datasource

import androidx.paging.PagingSource
import com.intive.tmdbandroid.entity.ResultTVShowsEntity
import androidx.paging.PagingState
import com.intive.tmdbandroid.datasource.network.Service
import com.intive.tmdbandroid.model.TVShow
import kotlinx.coroutines.flow.collect

class TVShowSearchSource(private val service: Service, private val query: String) : PagingSource<Int, TVShow>() {
companion object {
const val DEFAULT_PAGE_INDEX = 1
}

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TVShow> {
return try {
val pageNumber = params.key ?: DEFAULT_PAGE_INDEX
lateinit var response: ResultTVShowsEntity
service.getTvShowByTitle(query, pageNumber).collect { response = it }

val prevKey = if (pageNumber > DEFAULT_PAGE_INDEX) pageNumber - 1 else null
val nextKey = if (response.TVShows.isNotEmpty()) pageNumber + 1 else null

LoadResult.Page(
data = response.toTVShowList(),
prevKey = prevKey,
nextKey = nextKey
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}

override fun getRefreshKey(state: PagingState<Int, TVShow>): Int? {
return state.anchorPosition?.let {
state.closestPageToPosition(it)?.prevKey?.plus(1)
?: state.closestPageToPosition(it)?.nextKey?.minus(1)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import com.intive.tmdbandroid.model.converter.CreatedByConverter
import com.intive.tmdbandroid.model.converter.GenreConverter

@Database(entities = [(TVShowORMEntity::class)], version = 1, exportSchema = false)
@TypeConverters(CreatedByConverter::class,GenreConverter::class)
@TypeConverters(CreatedByConverter::class, GenreConverter::class)
abstract class LocalStorage : RoomDatabase() {
abstract fun tvShowDao(): Dao
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ interface ApiClient {
@GET("tv/{tv_id}")
suspend fun getTVShowByID(@Path("tv_id") tvShowID: Int,
@Query("api_key") apiKey: String) : TVShow

@GET("search/tv")
suspend fun getTVShowByName(@Query("api_key") apiKey: String,
@Query("query") query: String,
@Query("page") page: Int) : ResultTVShowsEntity
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,21 @@ import kotlinx.coroutines.flow.flow
class Service {
private val retrofit = RetrofitHelper.getRetrofit()

fun getPaginatedPopularTVShows(page: Int) : Flow<ResultTVShowsEntity> {
fun getPaginatedPopularTVShows(page: Int): Flow<ResultTVShowsEntity> {
return flow {
emit(retrofit.create(ApiClient::class.java).getPaginatedPopularTVShows(BuildConfig.API_KEY, page))
}
}

fun getTVShowByID(tvShowID: Int) : Flow<TVShow> {
fun getTVShowByID(tvShowID: Int): Flow<TVShow> {
return flow {
emit(retrofit.create(ApiClient::class.java).getTVShowByID(tvShowID, BuildConfig.API_KEY))
}
}
}

fun getTvShowByTitle(tvShowTitle: String, page: Int): Flow<ResultTVShowsEntity> {
return flow {
emit(retrofit.create(ApiClient::class.java).getTVShowByName(BuildConfig.API_KEY, tvShowTitle, page))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import com.intive.tmdbandroid.details.viewmodel.DetailsViewModel
import com.intive.tmdbandroid.model.TVShow
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.*

Expand All @@ -47,8 +49,8 @@ class DetailFragment : Fragment() {
): View {
val binding = FragmentDetailBinding.inflate(inflater, container, false)

collectDataFromViewModel(binding)
setupToolbar(binding)
collectTVShowDetailFromViewModel(binding)
collectWatchlistDataFromViewModel(binding)

return binding.root
}
Expand All @@ -65,7 +67,7 @@ class DetailFragment : Fragment() {
Glide.get(requireContext()).clearMemory()
}

private fun collectDataFromViewModel(binding: FragmentDetailBinding) {
private fun collectTVShowDetailFromViewModel(binding: FragmentDetailBinding) {
binding.coordinatorContainerDetail.visibility = View.INVISIBLE
lifecycleScope.launchWhenCreated {
viewModel.uiState.collect { state ->
Expand All @@ -89,6 +91,30 @@ class DetailFragment : Fragment() {
}
}

private fun collectWatchlistDataFromViewModel(binding: FragmentDetailBinding) {
lifecycleScope.launch {
viewModel.watchlistUIState.collectLatest {
when (it) {
is State.Success -> {
binding.layoutErrorDetail.errorContainer.visibility = View.GONE
binding.layoutLoadingDetail.progressBar.visibility = View.GONE
selectOrUnselectWatchlistFav(binding, it.data)
isSaveOnWatchlist = it.data
}
State.Error -> {
binding.layoutLoadingDetail.progressBar.visibility = View.GONE
binding.layoutErrorDetail.errorContainer.visibility = View.VISIBLE
binding.coordinatorContainerDetail.visibility = View.VISIBLE
}
State.Loading -> {
binding.layoutErrorDetail.errorContainer.visibility = View.GONE
binding.layoutLoadingDetail.progressBar.visibility = View.VISIBLE
}
}
}
}
}

private fun setupUI(binding: FragmentDetailBinding, tvShow: TVShow) {

setImages(binding, tvShow)
Expand All @@ -97,6 +123,8 @@ class DetailFragment : Fragment() {

setPercentageToCircularPercentage(binding, tvShow.vote_average)

setupToolbar(binding, tvShow)

binding.toolbar.title = tvShow.name

binding.statusDetailTextView.text = tvShow.status
Expand Down Expand Up @@ -126,6 +154,8 @@ class DetailFragment : Fragment() {

binding.overviewDetailTextView.text = tvShow.overview
binding.coordinatorContainerDetail.visibility = View.VISIBLE

tvShowId?.let { viewModel.existAsFavorite(it) }
}

private fun setPercentageToCircularPercentage(
Expand All @@ -139,18 +169,23 @@ class DetailFragment : Fragment() {
val context = binding.root.context

when {
percentage < 25 -> binding.circularPercentage.progressTintList = ContextCompat.getColorStateList(context, R.color.red)
percentage < 45 -> binding.circularPercentage.progressTintList = ContextCompat.getColorStateList(context, R.color.orange)
percentage < 75 -> binding.circularPercentage.progressTintList = ContextCompat.getColorStateList(context, R.color.yellow)
else -> binding.circularPercentage.progressTintList = ContextCompat.getColorStateList(context, R.color.green)
percentage < 25 -> binding.circularPercentage.progressTintList =
ContextCompat.getColorStateList(context, R.color.red)
percentage < 45 -> binding.circularPercentage.progressTintList =
ContextCompat.getColorStateList(context, R.color.orange)
percentage < 75 -> binding.circularPercentage.progressTintList =
ContextCompat.getColorStateList(context, R.color.yellow)
else -> binding.circularPercentage.progressTintList =
ContextCompat.getColorStateList(context, R.color.green)
}
binding.screeningPopularity.text = resources.getString(R.string.popularity, percentage)
}

private fun setDate(binding: FragmentDetailBinding, firstAirDate: String) {
try {
val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(firstAirDate)
val stringDate = date?.let { SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(it) }
val stringDate =
date?.let { SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(it) }
binding.firstAirDateDetailTextView.text = stringDate
} catch (e: Exception) {
binding.firstAirDateDetailTextView.text = ""
Expand All @@ -176,15 +211,19 @@ class DetailFragment : Fragment() {
.into(binding.backgroundImageToolbarLayout)
}

private fun setupToolbar(binding: FragmentDetailBinding) {
private fun setupToolbar(binding: FragmentDetailBinding, tvShow: TVShow) {
val navController = findNavController()
val appBarConfiguration = AppBarConfiguration(navController.graph)
val toolbar = binding.toolbar
toolbar.inflateMenu(R.menu.watchlist_favorite_detail_fragment)
toolbar.setOnMenuItemClickListener {
when(it.itemId) {
when (it.itemId) {
R.id.ic_heart_watchlist -> {
selectOrUnselectWatchlistFav(binding)
if (!isSaveOnWatchlist) {
viewModel.addToWatchlist(tvShow.toTVShowORMEntity())
} else {
viewModel.deleteFromWatchlist(tvShow.toTVShowORMEntity())
}
true
}
else -> false
Expand All @@ -202,13 +241,14 @@ class DetailFragment : Fragment() {
})
}

private fun selectOrUnselectWatchlistFav(binding: FragmentDetailBinding) {
isSaveOnWatchlist = !isSaveOnWatchlist
private fun selectOrUnselectWatchlistFav(binding: FragmentDetailBinding, isFav: Boolean) {
val watchlistItem = binding.toolbar.menu.findItem(R.id.ic_heart_watchlist)
if (isSaveOnWatchlist){
watchlistItem.icon = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_heart_selected)
}else {
watchlistItem.icon = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_heart_unselected)
}
if (isFav) {
watchlistItem.icon =
AppCompatResources.getDrawable(requireContext(), R.drawable.ic_heart_selected)
} else watchlistItem.icon =
AppCompatResources.getDrawable(requireContext(), R.drawable.ic_heart_unselected)

}

}
Loading

0 comments on commit ffeca9a

Please sign in to comment.