Compare commits

..

5 Commits

30 changed files with 92 additions and 1426 deletions

View File

@ -1,123 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@ -1,126 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AgentMigrationStateService">
<option name="migrationStatus" value="IN_PROGRESS" />
<option name="pendingSessionIds">
<option value="05f02dcc-4a62-4a3b-a6bb-d4764cbd1bab" />
<option value="1aa6c162-aaff-428f-bfae-3b53f8ce3f76" />
<option value="1e2fee64-523c-4038-ad08-03d8f8196cb1" />
<option value="46f36349-a2e4-46bf-b85e-8ee46252660a" />
<option value="4e8befa7-c7cb-4874-9ac5-4b69e81d19b8" />
<option value="63e1cd83-39a1-4de9-83e4-a7d978cb9048" />
<option value="82cd9415-28a8-49b5-841d-6178af5419de" />
<option value="9e78818c-2d23-4dc2-8dd8-c82d3ade6f9d" />
<option value="a1f1bc1b-b1d4-4655-9677-4653b213cc14" />
<option value="ad5bcc95-675a-45d4-8896-b1645cb788b9" />
<option value="b944cd09-1573-42de-840e-06d91ce8729c" />
<option value="bf5061b4-a1e9-4600-a273-ea8e51b2ce89" />
<option value="d23e68d4-6558-4c12-a57e-d937ea639547" />
<option value="dd38c49b-b13d-4206-8c24-bcf78317eec5" />
<option value="e6a32dc0-5413-49cf-ad92-9fb87982ed75" />
</option>
<option name="pendingTurns">
<map>
<entry key="1aa6c162-aaff-428f-bfae-3b53f8ce3f76">
<value>
<set>
<option value="cbcc2ea0-3963-436d-be88-f495313b8d20" />
<option value="c00b235a-a5a8-4158-baf4-7a3ca74345b0" />
<option value="16815d5b-8699-467d-9929-397e12de1084" />
<option value="f6f34f7d-39e2-4634-a01d-82dae28320aa" />
<option value="43c8a7ee-b78d-4cf3-824e-8d0c98692b00" />
<option value="eab6d8f0-3fdf-46bc-ab06-c8eb1243fe00" />
<option value="207107b2-1903-41f3-a6d3-30ee3505d45b" />
<option value="73b11a61-7794-4de6-bbdf-5f37c67fd8f0" />
</set>
</value>
</entry>
<entry key="63e1cd83-39a1-4de9-83e4-a7d978cb9048">
<value>
<set>
<option value="17ec376a-38fd-48ac-a4e1-52e9f7df3100" />
<option value="3ac3800e-363e-4148-86b3-0d4f3b0a8b62" />
<option value="44c8c4f4-3bbe-4fb8-88e4-58bdb184dad0" />
<option value="484561a1-1c08-44cd-bcd3-a53895fc3851" />
<option value="5460190d-c8ff-4194-9316-a6298c7cb54b" />
<option value="a0c23b11-ba1e-4275-a3e4-38b3c8fdbe00" />
<option value="a20448f7-57cd-4a40-89b1-460451412588" />
<option value="c640b33a-4bc6-4ee0-98ed-6a71e7270331" />
<option value="f099f72f-0866-4d16-85e4-8323be39e45f" />
</set>
</value>
</entry>
<entry key="9e78818c-2d23-4dc2-8dd8-c82d3ade6f9d">
<value>
<set>
<option value="3a14eb41-53f1-4190-a7f8-24c23cb882e8" />
<option value="4a2475a2-98ab-4ae0-b689-cbc2da702b83" />
<option value="72c7c9c6-ecb5-444b-86de-559ea9d893e5" />
<option value="a58fb4e2-20bd-480e-9eff-a5b9dc57c1b4" />
<option value="f43c2e1c-5a6c-4deb-9fbf-5e7694c69f6d" />
<option value="faff10ea-f875-4de1-adc0-6095c7c3e718" />
</set>
</value>
</entry>
<entry key="bf5061b4-a1e9-4600-a273-ea8e51b2ce89">
<value>
<set>
<option value="056c3bd2-6151-4564-b839-d7d6f53d4342" />
<option value="0c019b23-843c-4710-8469-2dd1211d89a3" />
<option value="0e6a533b-db2a-4977-8228-bfeaa867eef9" />
<option value="1bb82ec0-7888-48c9-b016-116821338f0e" />
<option value="26bdd760-c8b3-419a-9b6b-a194651aed3d" />
<option value="2a8432cf-e7cb-4250-9c7c-00664ec22a28" />
<option value="2b41e5bf-4d90-46d8-8fc8-7b41c3f788e1" />
<option value="34b3c4dc-eb50-43b4-98d0-2b9771161619" />
<option value="38d23208-212e-48d5-84ea-03d6cf590d51" />
<option value="39a0d9c4-a68f-4050-bb14-eda17dc695db" />
<option value="44cb5a83-abdc-41a2-95fa-283ec339fd2d" />
<option value="44d2412a-3471-4972-8b90-5cad4c5a00e3" />
<option value="461c1e05-de5d-4220-af67-2f29e576b9ea" />
<option value="4f53b359-f3df-449a-ab3e-d112d5df446a" />
<option value="51c4a9e4-545f-4c67-8fee-80b3de88d2be" />
<option value="520750f7-1625-4419-beb3-c4ecf7d9dc2b" />
<option value="52e099a9-0cd7-4716-8310-8a43635ca894" />
<option value="6971fc05-6110-4b33-ac58-69628d336a48" />
<option value="6dffb37e-ecb0-4c89-8cbe-e38ed347c397" />
<option value="7216273c-1271-4ea2-88d8-177e139316f1" />
<option value="7237b251-2008-431b-b6b3-005dea48c3bf" />
<option value="72c65d6c-d369-4e6e-ad89-52749cab36a6" />
<option value="76858669-1b07-4bac-bc00-8448c673d06d" />
<option value="80ef4e51-2808-48f4-9166-bf883578c6f0" />
<option value="8589889d-a532-45c6-9ffe-0671c2d32583" />
<option value="8861f660-2aea-480d-a773-4e1f58fe9ac0" />
<option value="8fab86d7-b6d2-4ce8-9174-172bd49c7906" />
<option value="94d90579-6a6d-44a9-8b93-de694c0c38ef" />
<option value="9523ff40-4e2b-4c96-b52e-69093f946698" />
<option value="9ebc3bf3-3f18-425d-a4fb-c87a6a121c9a" />
<option value="aaf8bf1e-ce4a-48ab-a65e-6da5790fc566" />
<option value="ac68227b-a347-492a-8990-f2313a4c4838" />
<option value="b32841ec-424b-46e4-af25-b31a5ca4ae51" />
<option value="ba4f0602-d62b-4a9c-b86f-ee0dce40374d" />
<option value="c759a8ff-cf83-4618-90d4-31101dea7c77" />
<option value="e2907e39-fda1-4832-99ff-fb7407d64db3" />
<option value="ea6afe5a-c296-461b-a4a7-49209b3d7937" />
<option value="ec11cbcb-475d-4b28-8eb3-f71715723d6c" />
<option value="ee1de16e-76b5-4384-8ed5-b2e3f4e3c2cb" />
<option value="f607f271-4885-4c4e-91da-e2caf2abd67a" />
<option value="fe272d92-3acf-42e8-bd88-66dc00800c3f" />
</set>
</value>
</entry>
</map>
</option>
<pendingWorkingSetItems>
<entry key="1aa6c162-aaff-428f-bfae-3b53f8ce3f76">
<set>
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/MainScreen.kt" />
<option value="file://$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/EvacuationMaps.kt" />
</set>
</entry>
<entry key="63e1cd83-39a1-4de9-83e4-a7d978cb9048" />
<entry key="9e78818c-2d23-4dc2-8dd8-c82d3ade6f9d" />
<entry key="bf5061b4-a1e9-4600-a273-ea8e51b2ce89" />
</pendingWorkingSetItems>
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AskMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Ask2AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EditMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

2
.idea/vcs.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -31,4 +31,6 @@ Repository ini digunakan untuk praktikum perkuliahan pemrograman mobile.
- 20. Muhammad Yusron Amrullah (202310715060)
- 21. Muhammad Fadillah (202310715213)
- 22. Hadi Guna Prakoso (202310715312)
- 23. Muhammad Rafi (202310715191)
- 23. Muhammad Rafi (202310715191)
- 24. Muhammad Rafly Al-Fathir (202310715043)
- 25. Jeremia Sebastian Marpaung (202310715096)

View File

@ -50,8 +50,6 @@ dependencies {
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
// Foundation (for verticalScroll, rememberScrollState, etc.)
implementation("androidx.compose.foundation:foundation:1.5.0")
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
@ -61,15 +59,6 @@ dependencies {
debugImplementation(libs.androidx.compose.ui.test.manifest)
//praktikum 1
implementation("com.squareup.okhttp3:okhttp:4.11.0")
implementation("androidx.compose.material3:material3:1.2.0")
implementation("androidx.compose.material:material-icons-extended:<versi-compose>")
implementation("androidx.compose.material3:material3:1.1.1")
// Navigation for Compose
implementation("androidx.navigation:navigation-compose:2.6.0")
// AndroidX SplashScreen (required for installSplashScreen API)
implementation("androidx.core:core-splashscreen:1.0.1")
// Play Services Location for GPS (FusedLocationProviderClient)
implementation("com.google.android.gms:play-services-location:21.0.1")
}

View File

@ -2,39 +2,26 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@drawable/logo"
android:roundIcon="@drawable/logo"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.PanicButton"
android:label="@string/app_name">
android:theme="@style/Theme.PanicButton">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.App.Starting">
android:label="@string/app_name"
android:theme="@style/Theme.PanicButton">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- Evacuation map activities -->
<activity
android:name=".EvacuationMapsActivity"
android:exported="false"
android:label="Peta Evakuasi" />
<activity
android:name=".EvacuationMapDetailActivity"
android:exported="false"
android:label="Detail Peta" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@ -1,34 +0,0 @@
package id.ac.ubharajaya.panicbutton
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
class EvacuationMapDetailActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val which = intent.getStringExtra("which") ?: "selatan"
val resId = if (which == "selatan") R.drawable.lantai_1_selatan else R.drawable.lantai_1_utara
setContent {
MaterialTheme {
EvacuationMapDetailContent(resId)
}
}
}
}
@Composable
fun EvacuationMapDetailContent(drawableResId: Int) {
Column(modifier = Modifier.fillMaxSize().padding(12.dp)) {
Text(text = "Detail Peta", modifier = Modifier.padding(bottom = 12.dp))
Image(painter = painterResource(id = drawableResId), contentDescription = "Map Detail", modifier = Modifier.fillMaxWidth().heightIn(max = 800.dp))
}
}

View File

@ -1,170 +0,0 @@
package id.ac.ubharajaya.panicbutton
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.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
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.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
// --- Constants for better readability ---
val CardElevation = 8.dp
val CornerRadius = 16.dp
val PaddingDefault = 16.dp
val ImageSize = 100.dp
// ====================================================================
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EvacuationMapsScreen(navController: NavController, onBack: () -> Unit) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Peta Jalur Evakuasi") },
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Kembali")
}
}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(horizontal = PaddingDefault)
) {
// Deskripsi Singkat
Text(
text = "Pilih area peta jalur evakuasi yang ingin Anda lihat.",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(bottom = PaddingDefault)
)
Spacer(modifier = Modifier.height(8.dp))
// Card 1 - Selatan
EvacuationMapCard(
title = "Jalur Evakuasi Selatan",
description = "Lihat detail jalur evakuasi lantai selatan.",
drawableId = R.drawable.lantai_1_selatan,
onClick = { navController.navigate("evac_map/selatan") }
)
// Card 2 - Utara
EvacuationMapCard(
title = "Jalur Evakuasi Utara",
description = "Lihat detail jalur evakuasi lantai utara.",
drawableId = R.drawable.lantai_1_utara,
onClick = { navController.navigate("evac_map/utara") }
)
}
}
}
@Composable
fun EvacuationMapCard(
title: String,
description: String,
drawableId: Int,
onClick: () -> Unit
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 12.dp)
.clickable(onClick = onClick),
shape = RoundedCornerShape(CornerRadius),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh
),
elevation = CardDefaults.cardElevation(defaultElevation = CardElevation)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(PaddingDefault)
) {
// Gambar dengan shape dan padding
Image(
painter = painterResource(id = drawableId),
contentDescription = title,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(ImageSize)
.clip(RoundedCornerShape(12.dp))
.background(MaterialTheme.colorScheme.primaryContainer)
)
Spacer(modifier = Modifier.width(PaddingDefault))
// Kolom Teks
Column(modifier = Modifier.weight(1f)) {
Text(
text = title,
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = description,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
// ====================================================================
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EvacuationMapDetailScreen(drawableResId: Int, onBack: () -> Unit) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Detail Peta") },
navigationIcon = {
IconButton(onClick = onBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Kembali")
}
}
)
}
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
// Gambar Detail Peta
Image(
painter = painterResource(id = drawableResId),
contentDescription = "Map Detail",
contentScale = ContentScale.Fit, // Gunakan Fit untuk memastikan peta terlihat keseluruhan
modifier = Modifier
.fillMaxWidth()
.padding(PaddingDefault)
.clip(RoundedCornerShape(CornerRadius))
.background(Color.LightGray) // Warna latar belakang untuk gambar
)
}
}
}

View File

@ -1,68 +0,0 @@
package id.ac.ubharajaya.panicbutton
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
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
class EvacuationMapsActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
EvacuationMapsContent(onSelect = { which ->
val intent = Intent(this, EvacuationMapDetailActivity::class.java)
intent.putExtra("which", which)
startActivity(intent)
})
}
}
}
}
@Composable
fun EvacuationMapsContent(onSelect: (String) -> Unit) {
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
Text(text = "Peta Jalur Evakuasi", style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(bottom = 12.dp))
// Selatan
Card(modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp).clickable { onSelect("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.")
}
}
}
// Utara
Card(modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp).clickable { onSelect("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.")
}
}
}
}
}

View File

@ -1,69 +1,52 @@
package id.ac.ubharajaya.panicbutton
import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider
import androidx.compose.material3.MaterialTheme
import com.google.android.gms.location.LocationServices
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import id.ac.ubharajaya.panicbutton.ui.theme.PanicButtonTheme
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody
class MainActivity : ComponentActivity() {
private lateinit var viewModel: MainViewModel
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
if (::viewModel.isInitialized) viewModel.dialogMessage = "Izin lokasi diberikan. Tekan kembali untuk menyertakan koordinat."
} else {
if (::viewModel.isInitialized) viewModel.dialogMessage = "Izin lokasi ditolak. Laporan akan dikirim tanpa koordinat."
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
val fusedClient = LocationServices.getFusedLocationProviderClient(this)
val openEvacMaps = {
startActivity(Intent(this, EvacuationMapsActivity::class.java))
}
@SuppressLint("MissingPermission")
fun handleSendAlert() {
// Check permission
val fineGranted = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
val coarseGranted = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
if (!fineGranted && !coarseGranted) {
requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
viewModel.dialogMessage = "Izin lokasi dibutuhkan untuk menyertakan koordinat. Silakan tekan lagi setelah mengizinkan lokasi."
return
}
fusedClient.lastLocation
.addOnSuccessListener { loc ->
if (loc != null) {
viewModel.sendAlert(loc.latitude, loc.longitude)
} else {
viewModel.sendAlert()
}
}
.addOnFailureListener {
viewModel.sendAlert()
}
}
enableEdgeToEdge()
setContent {
MaterialTheme {
MainScreen(viewModel = viewModel, onOpenEvacMaps = openEvacMaps, onSendAlert = ::handleSendAlert)
PanicButtonTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
PanicButtonTheme {
Greeting("Android")
}
}

View File

@ -1,484 +0,0 @@
package id.ac.ubharajaya.panicbutton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.unit.dp
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.text.style.TextAlign
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen(viewModel: MainViewModel, onOpenEvacMaps: () -> Unit, onSendAlert: () -> Unit) {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
// Put the emergency box in the topBar so Scaffold will position it below the status bar automatically
Box(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp, vertical = 8.dp),
contentAlignment = Alignment.TopCenter
) {
Text(
text = "JANGAN PANIK, SEGERA EVAKUASI DIRI ANDA KE TITIK KUMPUL!!",
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold),
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.background(
MaterialTheme.colorScheme.error.copy(alpha = 0.2f),
shape = RoundedCornerShape(8.dp)
)
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.error,
shape = RoundedCornerShape(8.dp)
)
.padding(12.dp),
maxLines = 2
)
}
}
) { padding -> // Inset padding yang disediakan oleh Scaffold
// Use the Scaffold padding for content so it's laid out under the topBar correctly
val contentPaddingModifier = Modifier
.fillMaxSize()
.padding(padding)
Column(
modifier = contentPaddingModifier
.verticalScroll(rememberScrollState())
.padding(horizontal = 20.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(12.dp))
// Main card and other content
EmergencyConditionCard(viewModel)
Spacer(modifier = Modifier.height(12.dp))
Button(
onClick = onOpenEvacMaps,
modifier = Modifier
.fillMaxWidth(0.6f)
.height(48.dp),
shape = RoundedCornerShape(12.dp)
) {
Text(text = "Lihat Peta Evakuasi")
}
Spacer(modifier = Modifier.height(20.dp))
// Move PanicButton into scrollable content so it scrolls with the page
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp),
contentAlignment = Alignment.Center
) {
// keep same size and shadow as before
PanicButton(onClick = { onSendAlert() }, buttonSize = 130.dp, shadowSize = 160.dp)
}
// create some extra space below button for comfortable scrolling
Spacer(modifier = Modifier.height(40.dp))
}
// Dialog notifikasi
viewModel.dialogMessage
?.takeIf { it.isNotBlank() }
?.let { msg ->
AlertDialog(
onDismissRequest = { viewModel.clearDialog() },
confirmButton = {
TextButton(onClick = { viewModel.clearDialog() }) { Text("OK") }
},
title = { Text("🚨 Notifikasi", style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold)) },
text = { Text(msg) }
)
}
}
}
// =======================================================
// EmergencyConditionCard
// =======================================================
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun EmergencyConditionCard(viewModel: MainViewModel) {
var expanded by remember { mutableStateOf(false) }
Card(
shape = RoundedCornerShape(24.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
),
modifier = Modifier
.fillMaxWidth()
.shadow(
elevation = 4.dp,
shape = RoundedCornerShape(24.dp),
clip = true
)
) {
Column(
modifier = Modifier
.padding(24.dp)
) {
// Header dengan gradient background
Row(
modifier = Modifier
.fillMaxWidth()
.background(
brush = Brush.linearGradient(
colors = listOf(
MaterialTheme.colorScheme.primary.copy(alpha = 0.1f),
MaterialTheme.colorScheme.secondary.copy(alpha = 0.05f)
)
),
shape = RoundedCornerShape(16.dp)
)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
// Icon dengan background
Box(
modifier = Modifier
.size(40.dp)
.background(
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.15f),
shape = RoundedCornerShape(12.dp)
),
contentAlignment = Alignment.Center
) {
Text(
text = "🚨",
style = MaterialTheme.typography.titleLarge
)
}
Column {
Text(
text = "Jenis Kondisi Darurat",
style = MaterialTheme.typography.titleLarge.copy(
fontWeight = FontWeight.Bold
),
color = MaterialTheme.colorScheme.primary
)
Text(
text = "Pilih satu atau lebih kondisi",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(top = 2.dp)
)
}
}
Spacer(Modifier.height(24.dp))
// Custom Styled Dropdown
Box(
modifier = Modifier
.fillMaxWidth()
.background(
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f),
shape = RoundedCornerShape(16.dp)
)
.border(
width = 1.dp,
color = if (expanded)
MaterialTheme.colorScheme.primary.copy(alpha = 0.5f)
else
MaterialTheme.colorScheme.outline.copy(alpha = 0.3f),
shape = RoundedCornerShape(16.dp)
)
.clickable { expanded = !expanded }
.padding(horizontal = 18.dp, vertical = 16.dp)
) {
Column {
// Selected items preview
val selectedItems = viewModel.options.filter { viewModel.isChecked(it.label) }
if (selectedItems.isEmpty()) {
Text(
"Pilih kondisi darurat...",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
} else {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
"${selectedItems.size} kondisi terpilih",
style = MaterialTheme.typography.bodySmall.copy(
fontWeight = FontWeight.Medium
),
color = MaterialTheme.colorScheme.primary
)
// Show first 2 selected items as chips
selectedItems.take(2).forEach { opt ->
SelectedChip(option = opt, viewModel = viewModel)
}
if (selectedItems.size > 2) {
Text(
"+${selectedItems.size - 2} lainnya",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
// Dropdown Menu
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(16.dp)
)
) {
Column(
modifier = Modifier
.width(320.dp)
.padding(vertical = 8.dp)
.heightIn(max = 280.dp)
.verticalScroll(rememberScrollState())
) {
viewModel.options.forEach { opt ->
EmergencyOptionItem(
option = opt,
isChecked = viewModel.isChecked(opt.label),
onCheckedChange = { checked ->
viewModel.setChecked(opt.label, checked)
}
)
}
}
}
}
// Dropdown arrow
Icon(
imageVector = if (expanded)
Icons.Filled.KeyboardArrowUp
else
Icons.Filled.KeyboardArrowDown,
contentDescription = if (expanded) "Tutup" else "Buka",
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.align(Alignment.CenterEnd)
)
}
Spacer(Modifier.height(20.dp))
// Extra note input dengan styling yang lebih baik
Column {
Text(
"Catatan Tambahan",
style = MaterialTheme.typography.bodyMedium.copy(
fontWeight = FontWeight.Medium
),
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(bottom = 8.dp, start = 4.dp)
)
OutlinedTextField(
value = viewModel.otherNote,
onValueChange = { viewModel.otherNote = it },
placeholder = {
Text(
"Tambahkan catatan jika diperlukan...",
color = MaterialTheme.colorScheme.onSurfaceVariant
)
},
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 56.dp, max = 120.dp),
shape = RoundedCornerShape(14.dp),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = MaterialTheme.colorScheme.primary,
unfocusedBorderColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.4f),
cursorColor = MaterialTheme.colorScheme.primary,
focusedTextColor = MaterialTheme.colorScheme.onSurface,
unfocusedTextColor = MaterialTheme.colorScheme.onSurface
),
textStyle = MaterialTheme.typography.bodyMedium
)
// Validation jika pilih 'Lainnya' tapi kosong
if (viewModel.isChecked("Lainnya") && viewModel.otherNote.isBlank()) {
Row(
modifier = Modifier.padding(top = 6.dp, start = 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Default.Warning,
contentDescription = "Warning",
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(16.dp)
)
Spacer(Modifier.width(4.dp))
Text(
"Catatan wajib diisi jika memilih 'Lainnya'",
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall
)
}
}
}
}
}
}
// =======================================================
// EmergencyOptionItem
// =======================================================
@Composable
fun EmergencyOptionItem(
option: EmergencyOption,
isChecked: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable { onCheckedChange(!isChecked) }
.padding(horizontal = 12.dp, vertical = 8.dp)
) {
// Icon dengan background
Box(
modifier = Modifier
.size(36.dp)
.background(
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f),
shape = RoundedCornerShape(8.dp)
),
contentAlignment = Alignment.Center
) {
Text(
text = option.icon,
style = MaterialTheme.typography.bodyLarge
)
}
Spacer(Modifier.width(12.dp))
// Label
Text(
text = option.label,
style = MaterialTheme.typography.bodyMedium.copy(
fontWeight = if (isChecked) FontWeight.Medium else FontWeight.Normal
),
color = if (isChecked)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.onSurface,
modifier = Modifier.weight(1f)
)
Spacer(Modifier.width(8.dp))
// Custom Checkbox
Box(
modifier = Modifier
.size(20.dp)
.background(
color = if (isChecked)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(4.dp)
)
.border(
width = 1.dp,
color = if (isChecked)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.outline.copy(alpha = 0.6f),
shape = RoundedCornerShape(4.dp)
),
contentAlignment = Alignment.Center
) {
if (isChecked) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = "Selected",
tint = MaterialTheme.colorScheme.onPrimary,
modifier = Modifier.size(14.dp)
)
}
}
}
}
// =======================================================
// SelectedChip
// =======================================================
@Composable
fun SelectedChip(option: EmergencyOption, viewModel: MainViewModel) {
Box(
modifier = Modifier
.wrapContentWidth()
.background(
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.1f),
shape = RoundedCornerShape(8.dp)
)
.padding(horizontal = 8.dp, vertical = 4.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "${option.icon} ${option.label}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.primary,
maxLines = 1
)
Spacer(Modifier.width(4.dp))
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Hapus",
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier
.size(14.dp)
.clickable { viewModel.setChecked(option.label, false) }
)
}
}
}

View File

@ -1,80 +0,0 @@
package id.ac.ubharajaya.panicbutton
import androidx.compose.runtime.*
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
data class EmergencyOption(
val label: String,
val icon: String
)
class MainViewModel : ViewModel() {
val options = listOf(
EmergencyOption("Kebakaran", "🔥"),
EmergencyOption("Banjir", "🌊"),
EmergencyOption("Gempa Bumi", "🌍"),
EmergencyOption("Huru Hara/Demonstrasi", "💥"),
EmergencyOption("Lainnya", "✏️")
)
// observable map so Compose recomposes on changes
private val _checkedState = mutableStateMapOf<String, Boolean>().apply {
options.forEach { put(it.label, false) }
}
var otherNote by mutableStateOf("")
var dialogMessage by mutableStateOf<String?>(null)
fun isChecked(label: String): Boolean = _checkedState[label] == true
fun setChecked(label: String, isChecked: Boolean) {
_checkedState[label] = isChecked
if (label == "Lainnya" && !isChecked) {
otherNote = ""
}
}
fun sendAlert(latitude: Double? = null, longitude: Double? = null) {
val selectedOptions = options.filter { isChecked(it.label) }.map { it.label }
if (selectedOptions.isEmpty()) {
dialogMessage = "Pilih setidaknya satu kondisi darurat."
return
}
if (isChecked("Lainnya") && otherNote.isBlank()) {
dialogMessage = "Catatan wajib diisi jika Anda memilih 'Lainnya'."
return
}
val bodyBuilder = StringBuilder()
bodyBuilder.append("Kondisi: ${selectedOptions.joinToString(", ")}")
if (otherNote.isNotBlank()) {
bodyBuilder.append("\nCatatan: ${otherNote.trim()}")
}
if (latitude != null && longitude != null) {
bodyBuilder.append("\nLokasi: https://maps.google.com/?q=$latitude,$longitude")
}
val payload = bodyBuilder.toString()
viewModelScope.launch {
try {
val resultMessage = NotificationSender.sendNotification(payload)
dialogMessage = resultMessage
} catch (e: Exception) {
e.printStackTrace()
dialogMessage = "Gagal mengirim laporan. Silakan periksa koneksi Anda dan coba lagi."
}
}
}
fun clearDialog() {
dialogMessage = null
}
}

View File

@ -1,75 +0,0 @@
package id.ac.ubharajaya.panicbutton
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.IOException
object NotificationSender {
private val client = OkHttpClient.Builder()
.build()
private const val server = "https://ntfy.ubharajaya.ac.id"
private const val topic = "panic-button"
private const val TAG = "NotificationSender"
private fun sanitizeHeaderValue(value: String): String {
val sb = StringBuilder()
for (ch in value) {
val code = ch.code
if (code in 32..126) sb.append(ch)
}
val out = sb.toString()
return if (out.isBlank()) "Alert" else out
}
suspend fun sendNotification(message: String): String = withContext(Dispatchers.IO) {
try {
val url = "$server/$topic"
Log.d(TAG, "Preparing notification to $url")
val titleSafe = sanitizeHeaderValue("🚨 Alert 🚨")
val cleanMessage = message.trim()
val body = cleanMessage.toRequestBody("text/plain".toMediaType())
val request = Request.Builder()
.url(url)
.addHeader("Title", titleSafe)
.addHeader("Priority", "urgent")
.addHeader("Tags", "warning")
.post(body)
.build()
// Debug logging
Log.d(TAG, "Body to send: $cleanMessage")
client.newCall(request).execute().use { resp ->
val respCode = resp.code
val respMessage = resp.message
val respBody = try {
resp.body?.string()
} catch (e: Exception) {
null
}
Log.d(TAG, "Response: code=$respCode message=$respMessage body=$respBody")
return@withContext if (resp.isSuccessful) {
"Notifikasi berhasil dikirim!"
} else {
"Gagal mengirim notifikasi: $respCode $respMessage ${respBody ?: ""}".trim()
}
}
} catch (e: IOException) {
Log.e(TAG, "IO error", e)
return@withContext "Gagal mengirim notifikasi: ${e.localizedMessage}"
} catch (e: Exception) {
Log.e(TAG, "Unexpected error", e)
return@withContext "Gagal mengirim notifikasi: ${e.localizedMessage}"
}
}
}

View File

@ -1,58 +0,0 @@
package id.ac.ubharajaya.panicbutton
import androidx.compose.animation.core.tween
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.Dp
@Composable
fun PanicButton(onClick: () -> Unit, buttonSize: Dp = 170.dp, shadowSize: Dp = 210.dp) {
val scaleFactor = buttonSize.value / 170f
val panicColor = Color(0xFFB71C1C)
val darkShade = Color(0xFF7F0F0F)
val lightAccent = Color(0xFFFF8A80)
val interactionSource = remember { MutableInteractionSource() }
val isPressedState = interactionSource.collectIsPressedAsState()
val isPressed = isPressedState.value
val scaleAnim = animateFloatAsState(targetValue = if (isPressed) 0.96f else 1f, animationSpec = tween(120)).value
val elevationAnim = animateDpAsState(targetValue = if (isPressed) 6.dp else 18.dp, animationSpec = tween(120)).value
val gradient = Brush.verticalGradient(listOf(lightAccent, panicColor, darkShade))
Box(contentAlignment = Alignment.Center) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(buttonSize)
.scale(scaleAnim)
.shadow(elevation = elevationAnim * scaleFactor, shape = CircleShape)
.background(brush = gradient, shape = CircleShape)
.clickable(indication = null, interactionSource = interactionSource) { onClick() }
) {
// highlight bubble (scaled)
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)
}
}
}

View File

@ -1,3 +0,0 @@
package id.ac.ubharajaya.panicbutton
data class ReportOption(val label: String, val icon: String)

View File

@ -2,11 +2,10 @@ package id.ac.ubharajaya.panicbutton.ui.theme
import androidx.compose.ui.graphics.Color
val Blue_primary = Color(0xFF007BFF)
val Blue_secondary = Color(0xFF00A2E8)
val Dark_blue = Color(0xFF0056b3)
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Grey_light = Color(0xFFF5F5F5)
val Grey_dark = Color(0xFF333333)
val Red_cancel = Color(0xFFDC3545)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View File

@ -1,53 +1,53 @@
package id.ac.ubharajaya.panicbutton.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Blue_secondary,
secondary = Blue_primary,
background = Grey_dark,
surface = Color(0xFF1E1E1E),
onPrimary = Grey_dark,
onSecondary = Grey_dark,
onBackground = Grey_light,
onSurface = Grey_light,
error = Red_cancel
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Blue_primary,
secondary = Blue_secondary,
tertiary = Dark_blue,
background = Grey_light,
surface = Color.White,
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onBackground = Grey_dark,
onSurface = Grey_dark,
error = Red_cancel
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun PanicButtonTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = false, // Disable dynamic color
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
val view = LocalView.current
if (!view.isInEditMode) {
val window = (view.context as Activity).window
window.statusBarColor = colorScheme.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
@ -55,4 +55,4 @@ fun PanicButtonTheme(
typography = Typography,
content = content
)
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 580 KiB

View File

@ -1,21 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="800dp"
android:height="800dp"
android:viewportWidth="512"
android:viewportHeight="512">
<path
android:pathData="M507.49,426.07L282.86,53.54c-5.68,-9.41 -15.87,-15.17 -26.86,-15.17c-10.99,0 -21.19,5.76 -26.86,15.17L4.51,426.07c-5.84,9.69 -6.01,21.77 -0.45,31.63c5.56,9.85 16,15.94 27.32,15.94h449.26c11.31,0 21.75,-6.09 27.32,-15.94C513.51,447.84 513.34,435.76 507.49,426.07z"
android:fillColor="#FF9900"/>
<path
android:pathData="M256,38.37c-10.99,0 -21.19,5.76 -26.86,15.17L4.51,426.07c-5.84,9.69 -6.01,21.77 -0.45,31.63c5.56,9.85 16,15.94 27.32,15.94h224.63L256,38.37L256,38.37z"
android:fillColor="#FFDC35"/>
<path
android:pathData="M445.33,432.79H67.11c-3.59,0 -6.91,-1.91 -8.72,-5.01c-1.81,-3.1 -1.83,-6.93 -0.05,-10.06L247.23,85.03c1.79,-3.15 5.14,-5.11 8.77,-5.11c0,0 0,0 0,0c3.63,0 6.97,1.95 8.77,5.1l189.32,332.69c1.78,3.12 1.76,6.95 -0.05,10.06S448.92,432.79 445.33,432.79zM84.44,412.62h343.54L256.01,110.42L84.44,412.62z"
android:fillColor="#F20013"/>
<path
android:pathData="M256.33,412.62H84.44l171.58,-302.19l-0.01,-30.5h-0c-3.63,0 -6.98,1.95 -8.77,5.11L58.34,417.72c-1.77,3.12 -1.75,6.95 0.05,10.06c1.81,3.1 5.13,5.01 8.72,5.01h189.22v-20.17H256.33z"
android:fillColor="#FF4B00"/>
<path
android:pathData="M279.36,376.88c0,12.34 -10.54,23.18 -22.88,23.18c-13.25,0 -23.18,-10.84 -23.18,-23.18c0,-12.64 9.94,-23.18 23.18,-23.18C268.83,353.7 279.36,364.24 279.36,376.88zM273.64,319.68c0,9.33 -10.24,13.25 -17.46,13.25c-9.63,0 -17.76,-3.91 -17.76,-13.25c0,-35.83 -4.21,-87.31 -4.21,-123.13c0,-11.74 9.63,-18.36 21.98,-18.36c11.74,0 21.68,6.62 21.68,18.36C277.86,232.37 273.64,283.86 273.64,319.68z"
android:fillColor="#533F29"/>
</vector>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@drawable/ubhara"
android:top="50dp"
android:bottom="50dp"
android:left="50dp"
android:right="50dp"/>
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 KiB

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@android:color/transparent" />
<foreground android:drawable="@drawable/logo" />
<monochrome android:drawable="@drawable/logo" />
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@android:color/transparent" />
<foreground android:drawable="@drawable/logo" />
<monochrome android:drawable="@drawable/logo" />
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.App.Starting" parent="Theme.SplashScreen.IconBackground">
<item name="postSplashScreenTheme">@style/Theme.PanicButton</item>
<item name="windowSplashScreenAnimatedIcon">@drawable/splash_icon_padded</item>
<item name="windowSplashScreenAnimationDuration">500</item>
</style>
</resources>