Skip to content
Merged
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
17 changes: 17 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.{kt,kts}]
max_line_length = 120
ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^
ktlint_standard_no-wildcard-imports = enabled
ktlint_standard_filename = enabled
ktlint_standard_trailing-comma-on-call-site = disabled
ktlint_standard_trailing-comma-on-declaration-site = disabled
29 changes: 29 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,37 @@ on:
branches: [ main ]

jobs:
ktlint:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Run ktlint
run: ./gradlew ktlintCheck

build:
runs-on: ubuntu-latest
needs: ktlint

steps:
- uses: actions/checkout@v4
Expand Down
71 changes: 40 additions & 31 deletions app/src/main/java/com/argahutama/submission/made/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import androidx.appcompat.app.AppCompatDelegate
import com.argahutama.submission.core.base.BaseApp
import com.argahutama.submission.core.di.dbModule
import com.argahutama.submission.core.di.networkModule
import com.argahutama.submission.core.di.repositoryModule
import com.argahutama.submission.core.navigation.NavigationDirection
import com.argahutama.submission.made.di.useCaseModule
import com.argahutama.submission.made.di.viewModelModule
import androidx.appcompat.app.AppCompatDelegate
import com.pluto.Pluto
import com.pluto.plugins.network.PlutoNetworkPlugin
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down Expand Up @@ -46,44 +46,53 @@ class App : BaseApp() {
}
}

override fun navigateTo(context: Context, direction: NavigationDirection) {
override fun navigateTo(
context: Context,
direction: NavigationDirection
) {
Intent(context, navigationMapper[direction::class.java])
.apply { direction.extras.forEach { putExtra(it) } }
.also { context.startActivity(it) }
}

override fun navigateTo(activity: Activity, direction: NavigationDirection, requestCode: Int) {
override fun navigateTo(
activity: Activity,
direction: NavigationDirection,
requestCode: Int
) {
Intent(activity, navigationMapper[direction::class.java])
.apply { direction.extras.forEach { putExtra(it) } }
.also { activity.startActivityForResult(it, requestCode) }
}

private fun Intent.putExtra(it: Map.Entry<String, Any?>) = when (val value = it.value) {
is Int -> putExtra(it.key, value)
is Long -> putExtra(it.key, value)
is CharSequence -> putExtra(it.key, value)
is String -> putExtra(it.key, value)
is Float -> putExtra(it.key, value)
is Double -> putExtra(it.key, value)
is Char -> putExtra(it.key, value)
is Short -> putExtra(it.key, value)
is Boolean -> putExtra(it.key, value)
is Serializable -> putExtra(it.key, value)
is Bundle -> putExtra(it.key, value)
is Parcelable -> putExtra(it.key, value)
is Array<*> -> when {
value.isArrayOf<CharSequence>() -> putExtra(it.key, value)
value.isArrayOf<String>() -> putExtra(it.key, value)
value.isArrayOf<Parcelable>() -> putExtra(it.key, value)
else -> throw Exception("Intent extra ${it.key} has wrong type ${value.javaClass.name}")
private fun Intent.putExtra(it: Map.Entry<String, Any?>) =
when (val value = it.value) {
is Int -> putExtra(it.key, value)
is Long -> putExtra(it.key, value)
is String -> putExtra(it.key, value)
is CharSequence -> putExtra(it.key, value)
is Float -> putExtra(it.key, value)
is Double -> putExtra(it.key, value)
is Char -> putExtra(it.key, value)
is Short -> putExtra(it.key, value)
is Boolean -> putExtra(it.key, value)
is Serializable -> putExtra(it.key, value)
is Bundle -> putExtra(it.key, value)
is Parcelable -> putExtra(it.key, value)
is Array<*> ->
when {
value.isArrayOf<CharSequence>() -> putExtra(it.key, value)
value.isArrayOf<String>() -> putExtra(it.key, value)
value.isArrayOf<Parcelable>() -> putExtra(it.key, value)
else -> throw Exception("Intent extra ${it.key} has wrong type ${value.javaClass.name}")
}
is IntArray -> putExtra(it.key, value)
is LongArray -> putExtra(it.key, value)
is FloatArray -> putExtra(it.key, value)
is DoubleArray -> putExtra(it.key, value)
is CharArray -> putExtra(it.key, value)
is ShortArray -> putExtra(it.key, value)
is BooleanArray -> putExtra(it.key, value)
else -> throw Exception("Intent extra ${it.key} has wrong type ${value?.javaClass?.name}")
}
is IntArray -> putExtra(it.key, value)
is LongArray -> putExtra(it.key, value)
is FloatArray -> putExtra(it.key, value)
is DoubleArray -> putExtra(it.key, value)
is CharArray -> putExtra(it.key, value)
is ShortArray -> putExtra(it.key, value)
is BooleanArray -> putExtra(it.key, value)
else -> throw Exception("Intent extra ${it.key} has wrong type ${value?.javaClass?.name}")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import kotlinx.coroutines.FlowPreview

@ExperimentalCoroutinesApi
@FlowPreview
val navigationMapper = mapOf(
NavigationDirection.Main::class.java to MainActivity::class.java,
NavigationDirection.Detail::class.java to DetailActivity::class.java
)
val navigationMapper =
mapOf(
NavigationDirection.Main::class.java to MainActivity::class.java,
NavigationDirection.Detail::class.java to DetailActivity::class.java
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ package com.argahutama.submission.made.detail

import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.core.content.IntentCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import com.argahutama.submission.core.base.BaseActivity
import com.argahutama.submission.core.domain.model.Movie
import androidx.core.content.IntentCompat
import com.argahutama.submission.core.navigation.Extra
import com.argahutama.submission.core.util.GlideListener
import com.argahutama.submission.core.R as CoreR
import com.argahutama.submission.made.R
import com.argahutama.submission.made.databinding.ActivityDetailBinding
import com.bumptech.glide.Glide
import org.koin.androidx.viewmodel.ext.android.viewModel
import com.argahutama.submission.core.R as CoreR

class DetailActivity : BaseActivity() {
private var movie: Movie? = null
Expand All @@ -27,68 +27,79 @@ class DetailActivity : BaseActivity() {
applyInsets()
}

override fun initView() = with(binding) {
ctvTitleDetail.text = movie?.title.orEmpty()
ctvDate.text = movie?.releaseDate
ctvOverview.text = movie?.overview
ctvPopularity.text = getString(
R.string.popularity_detail,
movie?.popularity.toString(),
movie?.voteCount.toString(),
movie?.voteAverage.toString()
)
ctvRating.text = movie?.voteAverage.toString()
override fun initView() =
with(binding) {
ctvTitleDetail.text = movie?.title.orEmpty()
ctvDate.text = movie?.releaseDate
ctvOverview.text = movie?.overview
ctvPopularity.text =
getString(
R.string.popularity_detail,
movie?.popularity.toString(),
movie?.voteCount.toString(),
movie?.voteAverage.toString()
)
ctvRating.text = movie?.voteAverage.toString()

Glide.with(this@DetailActivity)
.load(getString(CoreR.string.base_image_url, movie?.posterPath))
.listener(GlideListener(ivPosterTopBar, shimmerivPosterTopBar))
.into(ivPosterTopBar)
ivPosterTopBar.tag = movie?.posterPath.orEmpty()
Glide.with(this@DetailActivity)
.load(getString(CoreR.string.base_image_url, movie?.posterPath))
.listener(GlideListener(ivPosterTopBar, shimmerivPosterTopBar))
.into(ivPosterTopBar)
ivPosterTopBar.tag = movie?.posterPath.orEmpty()

Glide.with(this@DetailActivity)
.load(getString(CoreR.string.base_image_url, movie?.posterPath))
.listener(GlideListener(sivSubPoster, shimmerSubPoster))
.into(sivSubPoster)
sivSubPoster.tag = movie?.posterPath
}
Glide.with(this@DetailActivity)
.load(getString(CoreR.string.base_image_url, movie?.posterPath))
.listener(GlideListener(sivSubPoster, shimmerSubPoster))
.into(sivSubPoster)
sivSubPoster.tag = movie?.posterPath
}

override fun initAction() = with(binding) {
setFavorite(movie?.favorite!!, true)
sivFavorite.setOnClickListener { setFavorite(!movie?.favorite!!) }
ivBackButton.setOnClickListener { finish() }
}
override fun initAction() =
with(binding) {
setFavorite(movie?.favorite!!, true)
sivFavorite.setOnClickListener { setFavorite(!movie?.favorite!!) }
ivBackButton.setOnClickListener { finish() }
}

private fun loadArgs() {
movie = IntentCompat.getParcelableExtra(intent, Extra.MOVIE, Movie::class.java)
}

private fun applyInsets() = with(binding) {
ViewCompat.setOnApplyWindowInsetsListener(ivBackButton) { v, insets ->
val statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top
val margin16dp = (16 * v.resources.displayMetrics.density).toInt()
(v.layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin = statusBarHeight + margin16dp
v.requestLayout()
insets
}
ViewCompat.setOnApplyWindowInsetsListener(nestedScrollView) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.updatePadding(bottom = systemBars.bottom)
insets
private fun applyInsets() =
with(binding) {
ViewCompat.setOnApplyWindowInsetsListener(ivBackButton) { v, insets ->
val statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top
val margin16dp = (16 * v.resources.displayMetrics.density).toInt()
(v.layoutParams as? ViewGroup.MarginLayoutParams)?.topMargin = statusBarHeight + margin16dp
v.requestLayout()
insets
}
ViewCompat.setOnApplyWindowInsetsListener(nestedScrollView) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.updatePadding(bottom = systemBars.bottom)
insets
}
}
}

private fun ActivityDetailBinding.setFavorite(newState: Boolean, isInitial: Boolean = false) {
private fun ActivityDetailBinding.setFavorite(
newState: Boolean,
isInitial: Boolean = false
) {
if (!isInitial) {
movie?.favorite = newState
val message = if (newState) R.string.set_favorite else R.string.set_unfavorite
showSnackbar(getString(message))
if (movie != null) viewModel.setFavoriteMovie(movie!!, newState)
}

if (newState) sivFavorite.setImageDrawable(
ContextCompat.getDrawable(this@DetailActivity, R.drawable.ic_favorite_selected)
) else sivFavorite.setImageDrawable(
ContextCompat.getDrawable(this@DetailActivity, R.drawable.ic_favorite_unselected)
)
if (newState) {
sivFavorite.setImageDrawable(
ContextCompat.getDrawable(this@DetailActivity, R.drawable.ic_favorite_selected)
)
} else {
sivFavorite.setImageDrawable(
ContextCompat.getDrawable(this@DetailActivity, R.drawable.ic_favorite_unselected)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import com.argahutama.submission.core.base.BaseViewModel
import com.argahutama.submission.core.domain.model.Movie
import com.argahutama.submission.core.domain.usecase.MovieUseCase

class DetailViewModel(private val movieUseCase: MovieUseCase): BaseViewModel() {
fun setFavoriteMovie(movie: Movie, newStatus: Boolean) =
movieUseCase.setMovieFavorite(movie, newStatus)
}
class DetailViewModel(private val movieUseCase: MovieUseCase) : BaseViewModel() {
fun setFavoriteMovie(
movie: Movie,
newStatus: Boolean
) = movieUseCase.setMovieFavorite(movie, newStatus)
}
21 changes: 11 additions & 10 deletions app/src/main/java/com/argahutama/submission/made/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ import kotlinx.coroutines.FlowPreview
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module


val useCaseModule = module {
factory<MovieUseCase> { MovieInteractor(get()) }
}
val useCaseModule =
module {
factory<MovieUseCase> { MovieInteractor(get()) }
}

@ExperimentalCoroutinesApi
@FlowPreview
val viewModelModule = module {
viewModel { MovieViewModel(get()) }
viewModel { TvShowViewModel(get()) }
viewModel { DetailViewModel(get()) }
viewModel { MainViewModel(get()) }
}
val viewModelModule =
module {
viewModel { MovieViewModel(get()) }
viewModel { TvShowViewModel(get()) }
viewModel { DetailViewModel(get()) }
viewModel { MainViewModel(get()) }
}
Loading
Loading