feat(ui): add evacuation maps screen and navigation; add evac button to main screen
This commit is contained in:
parent
67ae1e6643
commit
9280ce0a89
10
.idea/copilot.data.migration.agent.xml
generated
10
.idea/copilot.data.migration.agent.xml
generated
@ -12,6 +12,7 @@
|
|||||||
<set>
|
<set>
|
||||||
<option value="0c019b23-843c-4710-8469-2dd1211d89a3" />
|
<option value="0c019b23-843c-4710-8469-2dd1211d89a3" />
|
||||||
<option value="0e6a533b-db2a-4977-8228-bfeaa867eef9" />
|
<option value="0e6a533b-db2a-4977-8228-bfeaa867eef9" />
|
||||||
|
<option value="1bb82ec0-7888-48c9-b016-116821338f0e" />
|
||||||
<option value="2a8432cf-e7cb-4250-9c7c-00664ec22a28" />
|
<option value="2a8432cf-e7cb-4250-9c7c-00664ec22a28" />
|
||||||
<option value="2b41e5bf-4d90-46d8-8fc8-7b41c3f788e1" />
|
<option value="2b41e5bf-4d90-46d8-8fc8-7b41c3f788e1" />
|
||||||
<option value="34b3c4dc-eb50-43b4-98d0-2b9771161619" />
|
<option value="34b3c4dc-eb50-43b4-98d0-2b9771161619" />
|
||||||
@ -25,6 +26,7 @@
|
|||||||
<option value="52e099a9-0cd7-4716-8310-8a43635ca894" />
|
<option value="52e099a9-0cd7-4716-8310-8a43635ca894" />
|
||||||
<option value="6971fc05-6110-4b33-ac58-69628d336a48" />
|
<option value="6971fc05-6110-4b33-ac58-69628d336a48" />
|
||||||
<option value="6dffb37e-ecb0-4c89-8cbe-e38ed347c397" />
|
<option value="6dffb37e-ecb0-4c89-8cbe-e38ed347c397" />
|
||||||
|
<option value="7237b251-2008-431b-b6b3-005dea48c3bf" />
|
||||||
<option value="76858669-1b07-4bac-bc00-8448c673d06d" />
|
<option value="76858669-1b07-4bac-bc00-8448c673d06d" />
|
||||||
<option value="80ef4e51-2808-48f4-9166-bf883578c6f0" />
|
<option value="80ef4e51-2808-48f4-9166-bf883578c6f0" />
|
||||||
<option value="8589889d-a532-45c6-9ffe-0671c2d32583" />
|
<option value="8589889d-a532-45c6-9ffe-0671c2d32583" />
|
||||||
@ -39,6 +41,7 @@
|
|||||||
<option value="c759a8ff-cf83-4618-90d4-31101dea7c77" />
|
<option value="c759a8ff-cf83-4618-90d4-31101dea7c77" />
|
||||||
<option value="e2907e39-fda1-4832-99ff-fb7407d64db3" />
|
<option value="e2907e39-fda1-4832-99ff-fb7407d64db3" />
|
||||||
<option value="ea6afe5a-c296-461b-a4a7-49209b3d7937" />
|
<option value="ea6afe5a-c296-461b-a4a7-49209b3d7937" />
|
||||||
|
<option value="ec11cbcb-475d-4b28-8eb3-f71715723d6c" />
|
||||||
<option value="f607f271-4885-4c4e-91da-e2caf2abd67a" />
|
<option value="f607f271-4885-4c4e-91da-e2caf2abd67a" />
|
||||||
<option value="fe272d92-3acf-42e8-bd88-66dc00800c3f" />
|
<option value="fe272d92-3acf-42e8-bd88-66dc00800c3f" />
|
||||||
</set>
|
</set>
|
||||||
@ -49,8 +52,11 @@
|
|||||||
<pendingWorkingSetItems>
|
<pendingWorkingSetItems>
|
||||||
<entry key="bf5061b4-a1e9-4600-a273-ea8e51b2ce89">
|
<entry key="bf5061b4-a1e9-4600-a273-ea8e51b2ce89">
|
||||||
<set>
|
<set>
|
||||||
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/MainViewModel.kt" />
|
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/PanicButton.kt" />
|
||||||
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/NotificationSender.kt" />
|
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/MainScreen.kt" />
|
||||||
|
<option value="file://$PROJECT_DIR$/app/build.gradle.kts" />
|
||||||
|
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt" />
|
||||||
|
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/EvacuationMaps.kt" />
|
||||||
</set>
|
</set>
|
||||||
</entry>
|
</entry>
|
||||||
</pendingWorkingSetItems>
|
</pendingWorkingSetItems>
|
||||||
|
|||||||
@ -61,4 +61,7 @@ dependencies {
|
|||||||
implementation("com.squareup.okhttp3:okhttp:4.11.0")
|
implementation("com.squareup.okhttp3:okhttp:4.11.0")
|
||||||
implementation("androidx.compose.material3:material3:1.2.0")
|
implementation("androidx.compose.material3:material3:1.2.0")
|
||||||
implementation("androidx.compose.material:material-icons-extended:<versi-compose>")
|
implementation("androidx.compose.material:material-icons-extended:<versi-compose>")
|
||||||
|
|
||||||
|
// Navigation for Compose
|
||||||
|
implementation("androidx.navigation:navigation-compose:2.6.0")
|
||||||
}
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
package id.ac.ubharajaya.panicbutton
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EvacuationMapsScreen(navController: NavController, onBack: () -> Unit) {
|
||||||
|
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
|
||||||
|
Text(text = "Peta Jalur Evakuasi", style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(bottom = 12.dp))
|
||||||
|
|
||||||
|
// Card 1 - Selatan
|
||||||
|
Card(modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp).clickable { navController.navigate("evac_map/selatan") }, shape = RoundedCornerShape(12.dp), elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(12.dp)) {
|
||||||
|
Image(painter = painterResource(id = R.drawable.lantai_1_selatan), contentDescription = "Selatan", modifier = Modifier.size(120.dp))
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
Column {
|
||||||
|
Text(text = "Jalur Evakuasi Selatan", fontSize = 18.sp)
|
||||||
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
Text(text = "Lihat detail jalur evakuasi lantai selatan.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Card 2 - Utara
|
||||||
|
Card(modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp).clickable { navController.navigate("evac_map/utara") }, shape = RoundedCornerShape(12.dp), elevation = CardDefaults.cardElevation(defaultElevation = 6.dp)) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(12.dp)) {
|
||||||
|
Image(painter = painterResource(id = R.drawable.lantai_1_utara), contentDescription = "Utara", modifier = Modifier.size(120.dp))
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
Column {
|
||||||
|
Text(text = "Jalur Evakuasi Utara", fontSize = 18.sp)
|
||||||
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
Text(text = "Lihat detail jalur evakuasi lantai utara.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EvacuationMapDetailScreen(drawableResId: Int, onBack: () -> Unit) {
|
||||||
|
Column(modifier = Modifier.fillMaxSize().padding(12.dp)) {
|
||||||
|
Text(text = "Detail Peta", style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(bottom = 12.dp))
|
||||||
|
Image(painter = painterResource(id = drawableResId), contentDescription = "Map Detail", modifier = Modifier.fillMaxWidth().heightIn(max = 600.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,12 @@ import android.os.Bundle
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.navigation.NavType
|
||||||
|
import androidx.navigation.compose.NavHost
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.compose.navArgument
|
||||||
|
import androidx.navigation.compose.rememberNavController
|
||||||
import id.ac.ubharajaya.panicbutton.ui.theme.PanicButtonTheme
|
import id.ac.ubharajaya.panicbutton.ui.theme.PanicButtonTheme
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
@ -13,8 +19,33 @@ class MainActivity : ComponentActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContent {
|
setContent {
|
||||||
PanicButtonTheme {
|
PanicButtonTheme {
|
||||||
MainScreen(viewModel = viewModel)
|
AppNav(viewModel = viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AppNav(viewModel: MainViewModel) {
|
||||||
|
val navController = rememberNavController()
|
||||||
|
NavHost(navController = navController, startDestination = "main") {
|
||||||
|
composable("main") {
|
||||||
|
MainScreen(viewModel = viewModel, onOpenEvacMaps = { navController.navigate("evac_maps") })
|
||||||
|
}
|
||||||
|
composable("evac_maps") {
|
||||||
|
EvacuationMapsScreen(navController = navController, onBack = { navController.popBackStack() })
|
||||||
|
}
|
||||||
|
composable(
|
||||||
|
route = "evac_map/{resName}",
|
||||||
|
arguments = listOf(navArgument("resName") { type = NavType.StringType })
|
||||||
|
) { backStackEntry ->
|
||||||
|
val resName = backStackEntry.arguments?.getString("resName") ?: ""
|
||||||
|
val resId = when (resName) {
|
||||||
|
"selatan" -> R.drawable.lantai_1_selatan
|
||||||
|
"utara" -> R.drawable.lantai_1_utara
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
EvacuationMapDetailScreen(drawableResId = resId, onBack = { navController.popBackStack() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -21,15 +21,25 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.layout.IntrinsicSize
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreen(viewModel: MainViewModel) {
|
fun MainScreen(viewModel: MainViewModel, onOpenEvacMaps: () -> Unit) {
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
PanicButton(onClick = { viewModel.sendAlert() })
|
// wrap PanicButton in a Box so we can center it over the bottom bar
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(120.dp),
|
||||||
|
contentAlignment = Alignment.TopCenter
|
||||||
|
) {
|
||||||
|
PanicButton(onClick = { viewModel.sendAlert() }, buttonSize = 130.dp, shadowSize = 160.dp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
|
||||||
@ -41,9 +51,34 @@ fun MainScreen(viewModel: MainViewModel) {
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
// Emergency instruction text (darurat)
|
||||||
|
Text(
|
||||||
|
text = "JANGAN PANIK, SEGERA EVAKUASI DIRI ANDA KE TITIK KUMPUL",
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 12.dp),
|
||||||
|
maxLines = 2,
|
||||||
|
)
|
||||||
|
|
||||||
// Styled Emergency Condition Card
|
// Styled Emergency Condition Card
|
||||||
EmergencyConditionCard(viewModel)
|
EmergencyConditionCard(viewModel)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(12.dp))
|
||||||
|
|
||||||
|
// Button to open evacuation maps
|
||||||
|
Button(
|
||||||
|
onClick = onOpenEvacMaps,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(0.6f)
|
||||||
|
.height(48.dp),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary)
|
||||||
|
) {
|
||||||
|
Text(text = "Lihat Peta Evakuasi", color = MaterialTheme.colorScheme.onPrimary)
|
||||||
|
}
|
||||||
|
|
||||||
// Tambahkan spacer di luar Card agar ada jarak dari bottom bar
|
// Tambahkan spacer di luar Card agar ada jarak dari bottom bar
|
||||||
Spacer(Modifier.height(20.dp))
|
Spacer(Modifier.height(20.dp))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,9 +20,11 @@ import androidx.compose.ui.graphics.Brush
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PanicButton(onClick: () -> Unit) {
|
fun PanicButton(onClick: () -> Unit, buttonSize: Dp = 170.dp, shadowSize: Dp = 210.dp) {
|
||||||
|
val scaleFactor = buttonSize.value / 170f
|
||||||
val panicColor = Color(0xFFB71C1C)
|
val panicColor = Color(0xFFB71C1C)
|
||||||
val darkShade = Color(0xFF7F0F0F)
|
val darkShade = Color(0xFF7F0F0F)
|
||||||
val lightAccent = Color(0xFFFF8A80)
|
val lightAccent = Color(0xFFFF8A80)
|
||||||
@ -34,23 +36,23 @@ fun PanicButton(onClick: () -> Unit) {
|
|||||||
val gradient = Brush.verticalGradient(listOf(lightAccent, panicColor, darkShade))
|
val gradient = Brush.verticalGradient(listOf(lightAccent, panicColor, darkShade))
|
||||||
|
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(210.dp)
|
|
||||||
.shadow(elevation = elevationAnim, shape = CircleShape)
|
|
||||||
.background(color = Color.Black.copy(alpha = 0.12f), shape = CircleShape)
|
|
||||||
)
|
|
||||||
Box(
|
Box(
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(170.dp)
|
.size(buttonSize)
|
||||||
.scale(scaleAnim)
|
.scale(scaleAnim)
|
||||||
.shadow(elevation = elevationAnim, shape = CircleShape)
|
.shadow(elevation = elevationAnim * scaleFactor, shape = CircleShape)
|
||||||
.background(brush = gradient, shape = CircleShape)
|
.background(brush = gradient, shape = CircleShape)
|
||||||
.clickable(indication = null, interactionSource = interactionSource) { onClick() }
|
.clickable(indication = null, interactionSource = interactionSource) { onClick() }
|
||||||
) {
|
) {
|
||||||
Box(modifier = Modifier.size(70.dp).offset(x = (-24).dp, y = (-28).dp).background(color = Color.White.copy(alpha = 0.16f), shape = CircleShape))
|
// highlight bubble (scaled)
|
||||||
Text("!", color = Color.White, fontSize = 72.sp)
|
val highlightSize = 70.dp * scaleFactor
|
||||||
|
val highlightOffsetX = (-24).dp * scaleFactor
|
||||||
|
val highlightOffsetY = (-28).dp * scaleFactor
|
||||||
|
|
||||||
|
Box(modifier = Modifier.size(highlightSize).offset(x = highlightOffsetX, y = highlightOffsetY).background(color = Color.White.copy(alpha = 0.16f), shape = CircleShape))
|
||||||
|
val fontSize = (72f * scaleFactor).sp
|
||||||
|
Text("!", color = Color.White, fontSize = fontSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
app/src/main/res/drawable/lantai_1_selatan.jpg
Normal file
BIN
app/src/main/res/drawable/lantai_1_selatan.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 524 KiB |
BIN
app/src/main/res/drawable/lantai_1_utara.jpg
Normal file
BIN
app/src/main/res/drawable/lantai_1_utara.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 580 KiB |
Loading…
x
Reference in New Issue
Block a user