ETS PBB I - Fitra Agung Diassyah Putra

ETS PPB I - Redesign Aplikasi CGV Cinemas

Nama          : Fitra Agung Diassyah Putra
NRP            : 5025201072
Github        : Link Github
Youtube      Link Youtube    : 

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


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


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 {
}
)
}
}
}
view raw gistfile1.txt hosted with ❤ by GitHub

home.kt


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())
}
view raw gistfile1.txt hosted with ❤ by GitHub

seat.kt


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,
)
}
}
view raw gistfile1.txt hosted with ❤ by GitHub

Komentar

Postingan populer dari blog ini