ETS PBB I - Fitra Agung Diassyah Putra
ETS PPB I - Redesign Aplikasi CGV Cinemas
Nama : Fitra Agung Diassyah Putra
NRP : 5025201072
Dokumentasi Redesign Aplikasi
Halaman Login Awal
Halaman Home dari Aplikasi
Halaman Pemilihan Kursi
Berikut adalah code yang dibuat pada ETS kali ini
Main Activity.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.movieapp | |
import androidx.activity.ComponentActivity | |
import androidx.activity.compose.setContent | |
import android.os.Bundle | |
import androidx.compose.foundation.layout.fillMaxSize | |
import androidx.compose.material3.Surface | |
import androidx.compose.runtime.Composable | |
import androidx.compose.ui.Modifier | |
import androidx.navigation.compose.NavHost | |
import androidx.navigation.compose.composable | |
import androidx.navigation.compose.rememberNavController | |
class MainActivity : ComponentActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContent { | |
MyApp() | |
} | |
} | |
} | |
@Composable | |
fun MyApp() { | |
val navController = rememberNavController() | |
Surface(modifier = Modifier.fillMaxSize()) { | |
NavHost(navController = navController, startDestination = "login") { | |
composable("login") { LoginScreen(navController) } | |
composable("home") { HomeScreen(navController) } | |
composable("seat") { SeatSelectorScreen(navController) } | |
} | |
} | |
} |
LoginScreen.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.movieapp | |
import android.util.Log | |
import androidx.compose.foundation.Image | |
import androidx.compose.foundation.clickable | |
import androidx.compose.foundation.layout.Arrangement | |
import androidx.compose.foundation.layout.Column | |
import androidx.compose.foundation.layout.Row | |
import androidx.compose.foundation.layout.Spacer | |
import androidx.compose.foundation.layout.fillMaxSize | |
import androidx.compose.foundation.layout.fillMaxWidth | |
import androidx.compose.foundation.layout.height | |
import androidx.compose.foundation.layout.padding | |
import androidx.compose.foundation.layout.size | |
import androidx.compose.material.icons.Icons | |
import androidx.compose.material.icons.filled.Add | |
import androidx.compose.material.icons.filled.Close | |
import androidx.compose.material3.Button | |
import androidx.compose.material3.Icon | |
import androidx.compose.material3.IconButton | |
import androidx.compose.material3.OutlinedTextField | |
import androidx.compose.material3.Text | |
//import androidx.compose.material3.TextButton | |
//import androidx.compose.material3.TextField | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.mutableStateOf | |
import androidx.compose.runtime.remember | |
import androidx.compose.runtime.setValue | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.res.painterResource | |
//import androidx.compose.ui.text.font.Font | |
import androidx.compose.ui.text.font.FontWeight | |
import androidx.compose.ui.text.input.PasswordVisualTransformation | |
import androidx.compose.ui.text.input.VisualTransformation | |
//import androidx.compose.ui.tooling.preview.Preview | |
import androidx.compose.ui.unit.dp | |
import androidx.compose.ui.unit.sp | |
import androidx.navigation.NavHostController | |
@Composable | |
fun LoginScreen(navController: NavHostController){ | |
var email by remember { | |
mutableStateOf("") | |
} | |
var password by remember { | |
mutableStateOf("") | |
} | |
var passwordVisible by remember { mutableStateOf(false) } | |
Column( | |
modifier = Modifier.fillMaxSize(), | |
verticalArrangement = Arrangement.Center, | |
horizontalAlignment = Alignment.CenterHorizontally | |
) { | |
Image(painter = painterResource(id = R.drawable.cgv), contentDescription = "Login Image", | |
modifier = Modifier.size(300.dp)) | |
Text(text = "WELCOME", fontSize = 28.sp, fontWeight = FontWeight.Bold) | |
Spacer(modifier = Modifier.height(4.dp)) | |
Row { | |
Text(text = "Login to CGV Cinemas") | |
} | |
Spacer(modifier = Modifier.height(16.dp)) | |
OutlinedTextField(value = email, onValueChange = {email = it}, label = { | |
Text(text = "Email") | |
}) | |
Spacer(modifier = Modifier.height(16.dp)) | |
OutlinedTextField( | |
value = password, | |
onValueChange = { password = it }, | |
label = { Text(text = "Password") }, | |
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), | |
trailingIcon = { | |
IconButton(onClick = { passwordVisible = !passwordVisible }) { | |
Icon( | |
imageVector = if (passwordVisible) Icons.Filled.Add else Icons.Filled.Close, | |
contentDescription = if (passwordVisible) "Hide password" else "Show password" | |
) | |
} | |
} | |
) | |
Spacer(modifier = Modifier.height(16.dp)) | |
Button( | |
onClick = { | |
Log.i("Credential", "Email : $email Password : $email") | |
navController.navigate("home") | |
}, | |
modifier = Modifier.size(width = 200.dp, height = 60.dp) | |
) { | |
Text(text = "LOGIN") | |
} | |
Spacer(modifier = Modifier.height(16.dp)) | |
Text(text = "Forgot Password ?", modifier = Modifier.clickable {}) | |
Spacer(modifier = Modifier.height(32.dp)) | |
Text(text = "Or Sign in With") | |
Row (modifier = Modifier | |
.fillMaxWidth() | |
.padding(40.dp), | |
horizontalArrangement = Arrangement.SpaceEvenly | |
){ | |
Image(painter = painterResource(id = R.drawable.login2), | |
contentDescription = "login 1", | |
modifier = Modifier | |
.size(60.dp) | |
.clickable { | |
} | |
) | |
Image(painter = painterResource(id = R.drawable.login4), | |
contentDescription = "login 2", | |
modifier = Modifier | |
.size(60.dp) | |
.clickable { | |
} | |
) | |
Image(painter = painterResource(id = R.drawable.login5), | |
contentDescription = "login 3", | |
modifier = Modifier | |
.size(60.dp) | |
.clickable { | |
} | |
) | |
} | |
} | |
} |
home.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.movieapp | |
import androidx.compose.foundation.Image | |
import androidx.compose.foundation.background | |
import androidx.compose.foundation.clickable | |
import androidx.compose.foundation.layout.* | |
import androidx.compose.foundation.lazy.LazyRow | |
import androidx.compose.foundation.lazy.itemsIndexed | |
import androidx.compose.material3.Text | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.rememberCoroutineScope | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.layout.ContentScale | |
import androidx.compose.ui.res.painterResource | |
import androidx.compose.ui.tooling.preview.Preview | |
import androidx.compose.ui.unit.dp | |
import androidx.compose.ui.unit.sp | |
import androidx.navigation.NavHostController | |
import androidx.navigation.compose.rememberNavController | |
import kotlinx.coroutines.delay | |
import androidx.compose.foundation.layout.fillMaxSize | |
import androidx.compose.foundation.shape.RoundedCornerShape | |
import androidx.compose.material3.MaterialTheme | |
import androidx.compose.runtime.* | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.draw.clip | |
@Composable | |
fun HomeScreen(navController: NavHostController) { | |
Column( | |
modifier = Modifier.fillMaxSize(), | |
verticalArrangement = Arrangement.Top, | |
horizontalAlignment = Alignment.CenterHorizontally | |
) { | |
// Header Section | |
HeaderSection() | |
// Banner Section | |
BannerSection() | |
Text( | |
text = "Now Playing", | |
modifier = Modifier | |
.background(Color.Black.copy(alpha = 0.7f)) | |
.fillMaxWidth() | |
.padding(8.dp), | |
color = Color.White, | |
) | |
// Movie Poster Section | |
MoviePosterSection(navController) | |
} | |
} | |
@Composable | |
fun HeaderSection() { | |
Row( | |
modifier = Modifier | |
.fillMaxWidth() | |
.padding(16.dp), | |
horizontalArrangement = Arrangement.SpaceBetween, | |
verticalAlignment = Alignment.CenterVertically | |
) { | |
Image( | |
painter = painterResource(id = R.drawable.cgv), | |
contentDescription = "CGV Logo", | |
modifier = Modifier.size(50.dp) | |
) | |
Box { | |
Text(text = "Welcome to CGV", color = Color.Black, fontSize = 20.sp) | |
} | |
} | |
} | |
@Composable | |
fun BannerSection() { | |
val images = listOf( | |
R.drawable.ad1, | |
R.drawable.ad2, | |
R.drawable.ad3 | |
) | |
var currentIndex by remember { mutableStateOf(0) } | |
val coroutineScope = rememberCoroutineScope() | |
LaunchedEffect(Unit) { | |
while (true) { | |
delay(5000) | |
currentIndex = (currentIndex + 1) % images.size | |
} | |
} | |
val currentImage = images[currentIndex] | |
Box( | |
modifier = Modifier | |
.fillMaxWidth() | |
.aspectRatio(16 / 9f) | |
) { | |
Image( | |
painter = painterResource(id = currentImage), | |
contentDescription = "Banner Image", | |
contentScale = ContentScale.Crop, | |
modifier = Modifier.fillMaxSize() | |
) | |
} | |
} | |
@Composable | |
fun MoviePosterSection(navController: NavHostController) { | |
val moviePosters = listOf( | |
PosterFilm(namaFilm = "Film 1", posterResId = R.drawable.movie1), | |
PosterFilm(namaFilm = "Film 2", posterResId = R.drawable.movie3), | |
PosterFilm(namaFilm = "Film 3", posterResId = R.drawable.movie4), | |
) | |
LazyRow( | |
modifier = Modifier | |
.fillMaxWidth() | |
.padding(vertical = 16.dp), | |
horizontalArrangement = Arrangement.spacedBy(8.dp) | |
) { | |
itemsIndexed(moviePosters) { index, posterFilm -> | |
if (index == 0) { | |
ClickableMoviePoster(posterFilm = posterFilm, navController = navController) | |
} else { | |
NonClickableMoviePoster(posterFilm = posterFilm) | |
} | |
} | |
} | |
} | |
@Composable | |
fun ClickableMoviePoster(posterFilm: PosterFilm, navController: NavHostController) { | |
Box( | |
modifier = Modifier | |
.size(width = 250.dp, height = 400.dp) | |
.clickable { | |
navController.navigate("seat") | |
}, | |
contentAlignment = Alignment.Center | |
) { | |
Image( | |
painter = painterResource(id = posterFilm.posterResId), | |
contentDescription = posterFilm.namaFilm, | |
contentScale = ContentScale.Crop, | |
modifier = Modifier.fillMaxSize() | |
) | |
} | |
} | |
@Composable | |
fun NonClickableMoviePoster(posterFilm: PosterFilm) { | |
Box( | |
modifier = Modifier | |
.size(width = 250.dp, height = 400.dp), | |
contentAlignment = Alignment.Center | |
){ | |
Image( | |
painter = painterResource(id = posterFilm.posterResId), | |
contentDescription = posterFilm.namaFilm, | |
contentScale = ContentScale.Crop, | |
modifier = Modifier.fillMaxSize() | |
) | |
} | |
} | |
data class PosterFilm(val namaFilm: String, val posterResId: Int) | |
@Preview | |
@Composable | |
fun PreviewHomeScreen() { | |
HomeScreen(navController = rememberNavController()) | |
} |
seat.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.movieapp | |
import androidx.compose.foundation.* | |
import androidx.compose.foundation.layout.* | |
import androidx.compose.foundation.shape.CircleShape | |
import androidx.compose.foundation.shape.RoundedCornerShape | |
import androidx.compose.material.* | |
import androidx.compose.material.icons.Icons | |
import androidx.compose.material.icons.filled.ArrowBack | |
import androidx.compose.material3.Button | |
import androidx.compose.material3.ButtonDefaults | |
import androidx.compose.material3.IconButton | |
import androidx.compose.material3.MaterialTheme | |
import androidx.compose.material3.Scaffold | |
import androidx.compose.material3.Surface | |
import androidx.compose.material3.Text | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.mutableStateListOf | |
import androidx.compose.runtime.mutableStateOf | |
import androidx.compose.runtime.remember | |
import androidx.compose.ui.Alignment | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.draw.clip | |
import androidx.compose.ui.graphics.Color | |
import androidx.compose.ui.unit.dp | |
import androidx.navigation.NavController | |
import java.time.LocalDate | |
import java.time.Month | |
import java.time.format.TextStyle | |
import java.util.* | |
@Composable | |
fun SeatSelectorScreen( | |
navController: NavController, | |
) { | |
val today = LocalDate.now() | |
val dateScrollState = rememberScrollState() | |
val timeScrollState = rememberScrollState() | |
val selectedSeat = remember { | |
mutableStateListOf<String>() | |
} | |
val selectedDate = remember { | |
mutableStateOf<LocalDate?>(null) | |
} | |
val selectedTime = remember { | |
mutableStateOf<String?>(null) | |
} | |
Scaffold( | |
// backgroundColor = Color.LightGray | |
) { padding -> | |
Column( | |
modifier = Modifier | |
.padding(padding) | |
.fillMaxSize() | |
) { | |
Row( | |
modifier = Modifier.padding( | |
horizontal = 16.dp, vertical = 8.dp | |
), | |
verticalAlignment = Alignment.CenterVertically, | |
) { | |
IconButton(onClick = { | |
navController.popBackStack() | |
}) { | |
// Icon(Icons.Default.ArrowBack, contentDescription = "Back Button") | |
} | |
Spacer(modifier = Modifier.width(16.dp)) | |
Text(text = "Select Seat") | |
} | |
Box( | |
modifier = Modifier | |
.align(Alignment.CenterHorizontally) | |
.padding(bottom = 48.dp, top = 8.dp) | |
.background(color = Color.Yellow) | |
.fillMaxWidth(0.5f), | |
contentAlignment = Alignment.Center, | |
) { | |
Text( | |
text = "Screen" | |
) | |
} | |
/// seat mapping | |
for (i in 1..6) { | |
Row(modifier = Modifier.align(Alignment.CenterHorizontally)) { | |
for (j in 1..8) { | |
val seatNumber = "${(64 + i).toChar()}$j" | |
SeatComp( | |
isEnabled = i != 6, | |
isSelected = selectedSeat.contains(seatNumber), | |
seatNumber = seatNumber | |
) { selected, seat -> | |
if (selected) { | |
selectedSeat.remove(seat) | |
} else { | |
selectedSeat.add(seat) | |
} | |
} | |
if (j != 8) Spacer(modifier = Modifier.width(if (j == 4) 16.dp else 8.dp)) | |
} | |
} | |
Spacer(modifier = Modifier.height(8.dp)) | |
} | |
Spacer(modifier = Modifier.height(24.dp)) | |
/// indicator | |
Row( | |
modifier = Modifier.align(Alignment.CenterHorizontally), | |
verticalAlignment = Alignment.CenterVertically | |
) { | |
SeatComp(isEnabled = false) | |
Spacer(modifier = Modifier.width(4.dp)) | |
Text( | |
"Reserved", | |
) | |
Spacer(modifier = Modifier.width(16.dp)) | |
SeatComp(isEnabled = true, isSelected = true) | |
Spacer(modifier = Modifier.width(4.dp)) | |
Text( | |
"Selected", | |
) | |
Spacer(modifier = Modifier.width(16.dp)) | |
SeatComp(isEnabled = true, isSelected = false) | |
Spacer(modifier = Modifier.width(4.dp)) | |
Text( | |
"Available", | |
) | |
} | |
Spacer(modifier = Modifier.height(24.dp)) | |
Surface( | |
modifier = Modifier | |
.weight(1f) | |
.fillMaxWidth(), | |
color = Color.White, | |
shape = RoundedCornerShape(topStart = 32.dp, topEnd = 32.dp) | |
) { | |
Column( | |
modifier = Modifier | |
.fillMaxSize() | |
.padding(16.dp), | |
horizontalAlignment = Alignment.CenterHorizontally, | |
verticalArrangement = Arrangement.SpaceBetween, | |
) { | |
Text( | |
text = "Select Seat", | |
) | |
Row( | |
modifier = Modifier.horizontalScroll(dateScrollState), | |
horizontalArrangement = Arrangement.spacedBy(8.dp) | |
) { | |
for (i in 0..14) { | |
val date = today.plusDays(i.toLong()) | |
DateComp( | |
date = date, isSelected = selectedDate.value == date | |
) { | |
selectedDate.value = it | |
} | |
} | |
} | |
Row( | |
modifier = Modifier.horizontalScroll(timeScrollState), | |
horizontalArrangement = Arrangement.spacedBy(8.dp) | |
) { | |
for (i in 10..22 step 2) { | |
val time = "$i:00" | |
TimeComp( | |
time = time, isSelected = selectedTime.value == time | |
) { | |
selectedTime.value = it | |
} | |
} | |
} | |
Row( | |
modifier = Modifier.fillMaxWidth(), | |
horizontalArrangement = Arrangement.SpaceBetween, | |
verticalAlignment = Alignment.CenterVertically, | |
) { | |
Column( | |
verticalArrangement = Arrangement.spacedBy(8.dp), | |
) { | |
Text( | |
text = "Total Price", | |
) | |
Text( | |
text = "\$${selectedSeat.size * 10}", | |
) | |
} | |
Button( | |
modifier = Modifier | |
.wrapContentWidth() | |
.height(56.dp), | |
colors = ButtonDefaults.buttonColors( | |
), | |
shape = RoundedCornerShape(32.dp), | |
onClick = {}, | |
) { | |
Text("Continue") | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
@Composable | |
fun TimeComp( | |
time: String, | |
isSelected: Boolean = false, | |
onClick: (String) -> Unit = {}, | |
) { | |
val color = when { | |
isSelected -> Color.Yellow | |
else -> Color.Yellow.copy(alpha = 0.15f) | |
} | |
Surface( | |
modifier = Modifier | |
.wrapContentSize() | |
.clip(RoundedCornerShape(16.dp)) | |
.clickable { | |
onClick(time) | |
}, shape = RoundedCornerShape(16.dp), color | |
= color | |
) { | |
Text( | |
text = time, | |
modifier = Modifier.padding(12.dp), | |
) | |
} | |
} | |
@Composable | |
fun DateComp( | |
date: LocalDate, | |
isSelected: Boolean = false, | |
onClick: (LocalDate) -> Unit = {}, | |
) { | |
val color = when { | |
isSelected -> Color.Yellow | |
else -> Color.Yellow.copy(alpha = 0.15f) | |
} | |
val textBg = when { | |
isSelected -> Color.White | |
else -> Color.Transparent | |
} | |
Surface( | |
modifier = Modifier | |
.wrapContentSize() | |
.clip(RoundedCornerShape(16.dp)) | |
.clickable { | |
onClick(date) | |
}, shape = RoundedCornerShape(16.dp), color = color | |
) { | |
Column( | |
modifier = Modifier.padding(12.dp), | |
horizontalAlignment = Alignment.CenterHorizontally, | |
verticalArrangement = Arrangement.spacedBy(8.dp) | |
) { | |
Text( | |
text = date.month.getDisplayName(TextStyle.SHORT, Locale.getDefault()), | |
) | |
Box( | |
modifier = Modifier | |
.clip(CircleShape) | |
.background(textBg) | |
.padding(4.dp), | |
) { | |
Text( | |
text = date.dayOfMonth.toString(), | |
) | |
} | |
} | |
} | |
} | |
@Composable | |
fun SeatComp( | |
isEnabled: Boolean = false, | |
isSelected: Boolean = false, | |
seatNumber: String = "", | |
onClick: (Boolean, String) -> Unit = { _, _ -> }, | |
) { | |
val seatColor = when { | |
!isEnabled -> Color.Gray | |
isSelected -> Color.Yellow | |
else -> Color.White | |
} | |
val textColor = when { | |
isSelected -> Color.White | |
else -> Color.Black | |
} | |
Box(modifier = Modifier | |
.size(32.dp) | |
.border(width = 1.dp, color = Color.Gray, shape = RoundedCornerShape(8.dp)) | |
.clip(RoundedCornerShape(8.dp)) | |
.background(color = seatColor) | |
.clickable { | |
onClick(isSelected, seatNumber); | |
} | |
.padding(8.dp), contentAlignment = Alignment.Center) { | |
Text( | |
seatNumber, | |
) | |
} | |
} |
Komentar
Posting Komentar