Skip to content

Commit

Permalink
Merge pull request #3 from gy6543721/develop
Browse files Browse the repository at this point in the history
[Release] 1.3.0
  • Loading branch information
gy6543721 authored Feb 17, 2024
2 parents e0573d6 + e4ff301 commit 6dab9cf
Show file tree
Hide file tree
Showing 29 changed files with 341 additions and 166 deletions.
23 changes: 9 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# MovieDB Compose
<img src="https://github.com/gy6543721/MovieDatabase/blob/main/app/src/main/res/mipmap-xxxhdpi/ic_moviedatabase_launcher.png" height="200"/>

TMDB Android App written in Kotlin and Compose.
Now support English, Japanese, Traditional Chinese, Simplified Chinese and Cantonese, will support more languages in the future.

[Google Play Store](https://play.google.com/store/apps/details?id=levilin.moviedatabase)
[Demo Video](https://youtu.be/fixcJBaKpcE)
<table>
Expand Down Expand Up @@ -28,20 +33,10 @@
</tr>
</table>

Coding Challenge

## Intro

A client has tasked us to build an app where their customers are able to search for movies, see details for a given movie, favorite that movie, see all favorited movies.
The app will mainly use themoviedb.org's API and will put the media front and center for the user.

## Requirements

1) Make an API key following the steps here: https://developers.themoviedb.org/3/getting-started/introduction
2) Film search (using this API endpoint **GET** https://api.themoviedb.org/3/search/movie?api_key={apikey}&query={search_query}), display the films in whatever UI you want.
3) The user should be able to see details of the film (endpoint **GET** https://api.themoviedb.org/3/movie/{movie_id}?api_key={apikey})
4) The user should be able to favorite and unfavorite a movie (use whatever local storage that you think make sense)
5) The user should be able to see a list of favorite movies (and from there go to the detail movie screen)
## API
- API Documentation: https://developers.themoviedb.org/3/getting-started/introduction
- Film search API endpoint: **GET** https://api.themoviedb.org/3/search/movie?api_key={apikey}&query={search_query}
- Film details API endpoint: **GET** https://api.themoviedb.org/3/movie/{movie_id}?api_key={apikey}

# Main Libraries Used
* Compose
Expand Down
82 changes: 43 additions & 39 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
plugins {
id "com.android.application"
id "org.jetbrains.kotlin.android"
id "com.google.devtools.ksp"
id "dagger.hilt.android.plugin"
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
id("dagger.hilt.android.plugin")
}

android {
Expand All @@ -13,8 +13,8 @@ android {
applicationId "levilin.moviedatabase"
minSdk 26
targetSdk 34
versionCode 6
versionName "1.2.2"
versionCode 7
versionName "1.3.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand All @@ -25,7 +25,11 @@ android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig signingConfigs.debug
}
}
compileOptions {
Expand All @@ -43,7 +47,7 @@ android {
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
lint {
Expand All @@ -53,51 +57,51 @@ android {

dependencies {

implementation "androidx.core:core-ktx:1.12.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.7.0"
implementation "androidx.activity:activity-compose:1.8.2"
implementation "androidx.compose.ui:ui:$compose_ui_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
implementation "androidx.compose.material:material:1.6.1"
testImplementation "junit:junit:4.13.2"
androidTestImplementation "androidx.test.ext:junit:1.1.5"
androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_ui_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_ui_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_ui_version"
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.activity:activity-compose:1.8.2")
implementation("androidx.compose.ui:ui:$compose_ui_version")
implementation("androidx.compose.ui:ui-tooling-preview:$compose_ui_version")
implementation("androidx.compose.material:material:1.6.1")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_ui_version")
debugImplementation("androidx.compose.ui:ui-tooling:$compose_ui_version")
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_ui_version")

// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version")

// Dagger - Hilt
implementation "com.google.dagger:hilt-android:$hilt_version"
implementation "androidx.hilt:hilt-navigation-compose:1.1.0"
ksp "com.google.dagger:hilt-android-compiler:$hilt_version"
implementation("com.google.dagger:hilt-android:$hilt_version")
implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
ksp("com.google.dagger:hilt-android-compiler:$hilt_version")

// Room components
implementation "androidx.room:room-runtime:$room_version"
ksp "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
androidTestImplementation "androidx.room:room-testing:$room_version"
implementation("androidx.room:room-runtime:$room_version")
ksp("androidx.room:room-compiler:$room_version")
implementation("androidx.room:room-ktx:$room_version")
androidTestImplementation("androidx.room:room-testing:$room_version")

// Compose Navigation
implementation "androidx.navigation:navigation-compose:$nav_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0"
implementation("androidx.navigation:navigation-compose:$nav_version")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")

// Retrofit
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation("com.squareup.retrofit2:retrofit:$retrofit_version")
implementation("com.squareup.retrofit2:converter-gson:$retrofit_version")

// Gson
implementation "com.google.code.gson:gson:$gson_version"
implementation("com.google.code.gson:gson:$gson_version")

// OKHttp
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version"
testImplementation "com.squareup.okhttp3:mockwebserver:$okhttp_version"
implementation("com.squareup.okhttp3:okhttp:$okhttp_version")
implementation("com.squareup.okhttp3:logging-interceptor:$okhttp_version")
testImplementation("com.squareup.okhttp3:mockwebserver:$okhttp_version")

// Coil
implementation "io.coil-kt:coil:$coil_version"
implementation "io.coil-kt:coil-compose:$coil_version"
implementation("io.coil-kt:coil:$coil_version")
implementation("io.coil-kt:coil-compose:$coil_version")
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ abstract class LocalDataSource : RoomDatabase() {
abstract fun localDataSourceDAO(): LocalDataSourceDAO

companion object {

@Volatile
private lateinit var instance: LocalDataSource
private const val name = ConstantValue.DATABASE_FILE_NAME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import javax.inject.Inject

@ViewModelScoped
class LocalRepository @Inject constructor(private val localDataSourceDAO: LocalDataSourceDAO) {

val getAllItems: Flow<List<MovieResult>> = localDataSourceDAO.getAllItems()

suspend fun insertItem(movieResult: MovieResult) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,4 @@ class RemoteDataSource @Inject constructor(private val moviesAPI: MoviesAPI) {
suspend fun getMovieDetail(id: String, queries: Map<String, String>): Response<MovieDetail> {
return moviesAPI.getMovieDetail(id = id, queries = queries)
}

}
12 changes: 3 additions & 9 deletions app/src/main/java/levilin/moviedatabase/ui/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.Scaffold
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.currentBackStackEntryAsState
Expand All @@ -21,14 +18,11 @@ import dagger.hilt.android.AndroidEntryPoint
import levilin.moviedatabase.ui.navigation.BottomNavView
import levilin.moviedatabase.ui.navigation.NavGraphView
import levilin.moviedatabase.ui.theme.MovieDatabaseTheme
import levilin.moviedatabase.viewmodel.SharedViewModel
import levilin.moviedatabase.viewmodel.MovieDatabaseViewModel

@ExperimentalMaterialApi
@ExperimentalFoundationApi
@ExperimentalComposeUiApi
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
private val sharedViewModel: SharedViewModel by viewModels()
private val movieDatabaseViewModel: MovieDatabaseViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -52,7 +46,7 @@ class MainActivity : ComponentActivity() {
paddingValue = PaddingValues(0.dp)
}
}
NavGraphView(navController = navController, sharedViewModel = sharedViewModel, modifier = Modifier.padding(paddingValues = paddingValue))
NavGraphView(navController = navController, movieDatabaseViewModel = movieDatabaseViewModel, modifier = Modifier.padding(paddingValues = paddingValue))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.res.stringResource
import levilin.moviedatabase.model.list.MovieResult
import levilin.moviedatabase.ui.theme.favouriteButtonColor
import levilin.moviedatabase.viewmodel.SharedViewModel
import levilin.moviedatabase.viewmodel.MovieDatabaseViewModel
import levilin.moviedatabase.R

@Composable
fun FavoriteButton(modifier: Modifier = Modifier, entry: MovieResult, viewModel: SharedViewModel) {
fun FavoriteButton(modifier: Modifier = Modifier, entry: MovieResult, viewModel: MovieDatabaseViewModel) {
var isFavorite by remember { mutableStateOf(false) }
isFavorite = viewModel.checkFavorite(input = entry)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import levilin.moviedatabase.viewmodel.SharedViewModel
import levilin.moviedatabase.viewmodel.MovieDatabaseViewModel

@Composable
fun FavoriteList(navController: NavController, viewModel: SharedViewModel = hiltViewModel()) {
fun FavoriteList(navController: NavController, viewModel: MovieDatabaseViewModel = hiltViewModel()) {
val favoriteList by remember { mutableStateOf(value = viewModel.favoriteList) }

LazyColumn(contentPadding = PaddingValues(16.dp)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import levilin.moviedatabase.model.list.MovieResult
import levilin.moviedatabase.viewmodel.SharedViewModel
import levilin.moviedatabase.viewmodel.MovieDatabaseViewModel

@Composable
fun ListRow(rowIndex: Int, entries: List<MovieResult>, navController: NavController, viewModel: SharedViewModel = hiltViewModel()) {
fun ListRow(rowIndex: Int, entries: List<MovieResult>, navController: NavController, viewModel: MovieDatabaseViewModel = hiltViewModel()) {
Column {
Row {
MovieCard(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import coil.request.ImageRequest
import levilin.moviedatabase.model.list.MovieResult
import levilin.moviedatabase.ui.theme.screenBackgroundColor
import levilin.moviedatabase.utility.ConstantValue
import levilin.moviedatabase.viewmodel.SharedViewModel
import levilin.moviedatabase.viewmodel.MovieDatabaseViewModel

@Composable
fun MovieCard(modifier: Modifier = Modifier, entry: MovieResult, viewModel: SharedViewModel = hiltViewModel(), navController: NavController) {
fun MovieCard(modifier: Modifier = Modifier, entry: MovieResult, viewModel: MovieDatabaseViewModel = hiltViewModel(), navController: NavController) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import levilin.moviedatabase.viewmodel.SharedViewModel
import levilin.moviedatabase.viewmodel.MovieDatabaseViewModel

@Composable
fun MovieList(navController: NavController, viewModel: SharedViewModel = hiltViewModel()) {
fun MovieList(navController: NavController, viewModel: MovieDatabaseViewModel = hiltViewModel()) {
val moviesList by remember { mutableStateOf(value = viewModel.movieList) }
val loadingErrorMessage by remember { mutableStateOf(value = viewModel.errorMovieListMessage) }
val isLoading by remember { mutableStateOf(value = viewModel.isMovieListLoading) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.shadow
Expand All @@ -34,11 +33,10 @@ import levilin.moviedatabase.ui.theme.buttonIconColor
import levilin.moviedatabase.ui.theme.screenTextColor
import levilin.moviedatabase.ui.theme.searchBarBorderColor
import levilin.moviedatabase.utility.ConstantValue
import levilin.moviedatabase.viewmodel.SharedViewModel
import levilin.moviedatabase.viewmodel.MovieDatabaseViewModel

@ExperimentalComposeUiApi
@Composable
fun SearchBar(modifier: Modifier = Modifier, hint: String = "", viewModel: SharedViewModel, onSearch: (String) -> Unit = {}) {
fun SearchBar(modifier: Modifier = Modifier, hint: String = "", viewModel: MovieDatabaseViewModel, onSearch: (String) -> Unit = {}) {
// Focus Control
val focusManager = LocalFocusManager.current
val keyboardController = LocalSoftwareKeyboardController.current
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.compose.currentBackStackEntryAsState
Expand Down Expand Up @@ -35,8 +36,18 @@ fun BottomNavView(navController: NavController, bottomBarState: MutableState<Boo

items.forEach { item ->
BottomNavigationItem(
icon = { Icon(imageVector = item.icon, contentDescription = item.title) },
label = { Text(text = item.title, fontSize = 9.sp) },
icon = {
Icon(
imageVector = item.icon,
contentDescription = stringResource(id = item.titleStringID)
)
},
label = {
Text(
text = stringResource(id = item.titleStringID),
fontSize = 10.sp
)
},
selectedContentColor = MaterialTheme.colors.buttonIconColor,
unselectedContentColor = MaterialTheme.colors.buttonIconColor.copy(0.4f),
alwaysShowLabel = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package levilin.moviedatabase.ui.navigation

import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.runtime.Composable
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.NavType
Expand All @@ -12,21 +11,20 @@ import androidx.navigation.navArgument
import levilin.moviedatabase.ui.screen.FavoriteListScreen
import levilin.moviedatabase.ui.screen.MovieDetailScreen
import levilin.moviedatabase.ui.screen.MovieListScreen
import levilin.moviedatabase.viewmodel.SharedViewModel
import levilin.moviedatabase.viewmodel.MovieDatabaseViewModel

@ExperimentalComposeUiApi
@Composable
fun NavGraphView(navController: NavHostController, sharedViewModel: SharedViewModel, modifier: Modifier) {
fun NavGraphView(navController: NavHostController, movieDatabaseViewModel: MovieDatabaseViewModel, modifier: Modifier) {
NavHost(
navController = navController,
startDestination = "movie_list_screen",
modifier = modifier.systemBarsPadding()
) {
composable("movie_list_screen") {
MovieListScreen(navController = navController, viewModel = sharedViewModel)
MovieListScreen(navController = navController, viewModel = movieDatabaseViewModel)
}
composable("favorite_list_screen") {
FavoriteListScreen(navController = navController, viewModel = sharedViewModel)
FavoriteListScreen(navController = navController, viewModel = movieDatabaseViewModel)
}
composable(
"movie_detail_screen/{id}",
Expand All @@ -38,7 +36,7 @@ fun NavGraphView(navController: NavHostController, sharedViewModel: SharedViewMo
) {
MovieDetailScreen(
navController = navController,
viewModel = sharedViewModel
viewModel = movieDatabaseViewModel
)
}

Expand Down
Loading

0 comments on commit 6dab9cf

Please sign in to comment.