Skip to content

Commit

Permalink
update movie list, movie detail in compose
Browse files Browse the repository at this point in the history
  • Loading branch information
quanda-0562 committed Dec 8, 2022
1 parent 70bb3fe commit d73e182
Show file tree
Hide file tree
Showing 10 changed files with 417 additions and 95 deletions.
88 changes: 55 additions & 33 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -153,39 +153,6 @@ dependencies {
implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.20")
implementation("androidx.multidex:multidex:2.0.1")

// compose
// https://developer.android.com/jetpack/compose/interop/adding
// https://developer.android.com/jetpack/compose/setup
val composeBom = platform("androidx.compose:compose-bom:2022.10.00")
implementation(composeBom)
androidTestImplementation(composeBom)
// Android Studio Preview support
implementation("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling")
// Animations
implementation("androidx.compose.animation:animation")
// Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.)
implementation("androidx.compose.foundation:foundation")
// Material Design
implementation("androidx.compose.material3:material3")
// Optional - Included automatically by material, only add when you need
// the icons but not the material library (e.g. when using Material3 or a
// custom design system based on Foundation)
implementation("androidx.compose.material:material-icons-core")
// Optional - Add full set of material icons
implementation("androidx.compose.material:material-icons-extended")
// Optional - Add window size utils
implementation("androidx.compose.material3:material3-window-size-class")
// Optional - Integration with activities
implementation("androidx.activity:activity-compose:1.6.1")
// Optional - Integration with ViewModels
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1")
// Optional - Integration with LiveData
implementation("androidx.compose.runtime:runtime-livedata")
// UI Tests
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-test-manifest")

// List of KTX extensions
// https://developer.android.com/kotlin/ktx/extensions-list
implementation("androidx.core:core-ktx:1.9.0")
Expand Down Expand Up @@ -401,6 +368,61 @@ dependencies {
testImplementation(Libs.testCore)
testImplementation(Libs.archCore)
*/

// compose
// https://developer.android.com/jetpack/compose/interop/adding
// https://developer.android.com/jetpack/compose/setup
val composeBom = platform("androidx.compose:compose-bom:2022.10.00")
implementation(composeBom)
androidTestImplementation(composeBom)
// Android Studio Preview support
implementation("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling")
// Animations
implementation("androidx.compose.animation:animation")
// Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.)
implementation("androidx.compose.foundation:foundation")
// or Material Design 2
implementation("androidx.compose.material:material")
// Material Design
implementation("androidx.compose.material3:material3")
// Constraint layout
implementation("androidx.constraintlayout:constraintlayout-compose:1.0.1")
// Optional - Included automatically by material, only add when you need
// the icons but not the material library (e.g. when using Material3 or a
// custom design system based on Foundation)
// implementation("androidx.compose.material:material-icons-core")
// Optional - Add full set of material icons
implementation("androidx.compose.material:material-icons-extended")
// Optional - Add window size utils
implementation("androidx.compose.material3:material3-window-size-class")
// Optional - Integration with activities
implementation("androidx.activity:activity-compose:1.6.1")
// Optional - Integration with ViewModels
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1")
// Optional - Integration with LiveData
implementation("androidx.compose.runtime:runtime-livedata")
// UI Tests
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-test-manifest")
// navigation
implementation("androidx.navigation:navigation-compose:2.5.3")
implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
// https://github.com/skydoves/landscapist
implementation("com.github.skydoves:landscapist-bom:2.1.0")
implementation("com.github.skydoves:landscapist-glide")
implementation("com.github.skydoves:landscapist-placeholder")
// https://google.github.io/accompanist/
// https://github.com/google/accompanist
val accompanistVersion = "0.28.0"
implementation("com.google.accompanist:accompanist-systemuicontroller:$accompanistVersion")
implementation("com.google.accompanist:accompanist-pager:$accompanistVersion")
implementation("com.google.accompanist:accompanist-permissions:$accompanistVersion")
implementation("com.google.accompanist:accompanist-placeholder:$accompanistVersion")
implementation("com.google.accompanist:accompanist-navigation-animation:$accompanistVersion")
implementation("com.google.accompanist:accompanist-navigation-material:$accompanistVersion")
implementation("com.google.accompanist:accompanist-webview:$accompanistVersion")
implementation("com.google.accompanist:accompanist-adaptive:$accompanistVersion")
}

kapt {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
tools:targetApi="31">

<activity
android:name="com.example.moviedb.compose.main.ComposeActivity"
android:name="com.example.moviedb.compose.ComposeActivity"
android:configChanges="orientation|screenSize"
android:exported="true"
android:theme="@style/Theme.MyApplication.NoActionBar">
Expand Down
34 changes: 34 additions & 0 deletions app/src/main/java/com/example/moviedb/compose/ComposeActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.example.moviedb.compose

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.example.moviedb.compose.theme.ComposeAppTheme
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class ComposeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeApp()
}
}
}

@Composable
fun ComposeApp() {
ComposeAppTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainNavigation()
}
}
}
53 changes: 53 additions & 0 deletions app/src/main/java/com/example/moviedb/compose/Navigation.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.example.moviedb.compose

import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.example.moviedb.compose.ui.detail.DetailScreen
import com.example.moviedb.compose.ui.home.HomeScreen
import okio.Path.Companion.toPath

@Composable
fun MainNavigation() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Route.MAIN) {
composable(route = Route.MAIN) {
HomeScreen(navController = navController)
}
composable(
route = Route.MOVIE_DETAIL,
arguments = listOf(navArgument(Route.Param.MOVIE_ID) {
type = NavType.Companion.StringType
})
) { backStackEntry ->
DetailScreen(
navController = navController,
movieId = backStackEntry.arguments?.getString(Route.Param.MOVIE_ID)
)
}
}
}

object Route {
const val MAIN = "main"
const val MOVIE_DETAIL = "movieDetail/{${Param.MOVIE_ID}}"

object Param {
const val MOVIE_ID = "movieId"

fun toPath(param: String) = "{${param}}"
}
}

fun NavController.toMovieDetail(movieId: String?) {
navigate(
route = Route.MOVIE_DETAIL.replace(
Route.Param.toPath(Route.Param.MOVIE_ID),
movieId ?: ""
)
)
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.example.moviedb.compose.ui.detail

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Error
import androidx.compose.material.icons.filled.Image
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
import com.example.moviedb.R
import com.example.moviedb.data.model.Movie
import com.example.moviedb.data.repository.UserRepository
import com.example.moviedb.ui.base.BaseViewModel
import com.skydoves.landscapist.ImageOptions
import com.skydoves.landscapist.components.rememberImageComponent
import com.skydoves.landscapist.glide.GlideImage
import com.skydoves.landscapist.placeholder.placeholder.PlaceholderPlugin
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

@Composable
fun DetailScreen(
navController: NavController,
movieId: String?,
) {
val viewModel: DetailViewModel = hiltViewModel()
viewModel.getMovieDetail(movieId = movieId)
val movie by viewModel.movie.collectAsState()
MovieDetailBody(movie = movie, onClickBack = { navController.popBackStack() })
}

@Composable
fun MovieDetailBody(
movie: Movie?,
onClickBack: () -> Unit
) {
if (movie != null) {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
) {
Box(modifier = Modifier.fillMaxWidth()) {
GlideImage(
imageModel = { movie.getFullBackdropPath() ?: "" },
modifier = Modifier.fillMaxWidth(),
component = rememberImageComponent {
+PlaceholderPlugin.Loading(Icons.Filled.Image)
+PlaceholderPlugin.Failure(Icons.Filled.Error)
},
imageOptions = ImageOptions(),
)
Image(
painterResource(R.drawable.ic_arrow_back_white_24dp),
contentDescription = "",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.clickable {
onClickBack.invoke()
}
.padding(12.dp),
)
}
Text(
movie.title ?: "",
color = Color.White,
modifier = Modifier.padding(16.dp),
fontSize = 20.sp,
)
Text(movie.releaseDate ?: "", color = Color.White)
Text(movie.overview ?: "", color = Color.White)
}
} else {

}
}

@HiltViewModel
class DetailViewModel @Inject constructor(
private val userRepository: UserRepository
) : BaseViewModel() {
private val _movie = MutableStateFlow<Movie?>(null)
val movie: StateFlow<Movie?> = _movie

fun getMovieDetail(movieId: String?) {
if (movieId.isNullOrBlank()) return
viewModelScope.launch {
try {
_movie.value = userRepository.getMovieById(movieId)
} catch (e: Throwable) {
onError(e)
}
}
}
}
Loading

0 comments on commit d73e182

Please sign in to comment.