18 lines
31 KiB
XML
18 lines
31 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<project version="4">
|
|
<component name="CopilotDiffPersistence">
|
|
<option name="pendingDiffs">
|
|
<map>
|
|
<entry key="$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt">
|
|
<value>
|
|
<PendingDiffInfo>
|
|
<option name="filePath" value="$PROJECT_DIR$/app/src/main/java/id/ac/ubharajaya/panicbutton/MainActivity.kt" />
|
|
<option name="originalContent" value="package id.ac.ubharajaya.panicbutton import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent 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.selection.toggleable import androidx.compose.foundation.shape.CircleShape 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.draw.scale import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyApp() } } } @Composable fun MyApp() { // dialogMessage akan menampung hasil callback dari sendNotification; null = tidak tampil var dialogMessage by remember { mutableStateOf<String?>(null) } // Report options (checkbox-style) val reportOptions = listOf("Kebakaran", "Banjir", "Gempa Bumi", "Huru Hara/Demostrasi", "Lainnya") val checkedMap = remember { mutableStateMapOf<String, Boolean>().apply { reportOptions.forEach { put(it, false) } } } var otherNote by remember { mutableStateOf("") } // UI Column( modifier = Modifier .fillMaxSize() .padding(16.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { // Checklist area similar to the wireframe Surface( tonalElevation = 2.dp, shape = RoundedCornerShape(8.dp), modifier = Modifier .fillMaxWidth() .padding(horizontal = 24.dp) ) { Column(modifier = Modifier.padding(12.dp)) { Text(text = "Terjadi Kondisi Darurat", fontWeight = FontWeight.SemiBold) Spacer(modifier = Modifier.height(8.dp)) // Each option as a row with checkbox reportOptions.forEach { option -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .padding(vertical = 4.dp) .toggleable( value = checkedMap[option] ?: false, onValueChange = { checked -> checkedMap[option] = checked } ) ) { Checkbox( checked = checkedMap[option] ?: false, onCheckedChange = { checked -> checkedMap[option] = checked } ) Spacer(modifier = Modifier.width(8.dp)) Text(text = option) } } // Additional notes field — show always (wireframe shows a note field) Spacer(modifier = Modifier.height(8.dp)) OutlinedTextField( value = otherNote, onValueChange = { otherNote = it }, label = { Text("Catatan tambahan (opsional)") }, placeholder = { Text("Catatan tambahan ...") }, modifier = Modifier .fillMaxWidth() .padding(top = 8.dp) ) Spacer(modifier = Modifier.height(8.dp)) // small hint when Lainnya is checked but note empty if ((checkedMap["Lainnya"] == true) && otherNote.isBlank()) { Text( text = "Catatan wajib jika Anda memilih 'Lainnya'", color = Color(0xFFB71C1C), style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(top = 6.dp) ) } } } Spacer(modifier = Modifier.height(18.dp)) // Keep the existing nice PanicButton unchanged PanicButton(onClick = { // Validation: at least one option selected val selected = reportOptions.filter { checkedMap[it] == true } if (selected.isEmpty()) { dialogMessage = "Pilih minimal satu jenis laporan sebelum mengirim." return@PanicButton } if (selected.contains("Lainnya") && otherNote.isBlank()) { dialogMessage = "Untuk opsi 'Lainnya', harap tambahkan catatan yang menjelaskan keadaan." return@PanicButton } // build message: list selected options and the note val message = buildString { append("Jenis Laporan: ") append(selected.joinToString(", ")) append("\n") append("Keterangan: ") // Include the note regardless of the selected options; if blank write '-' if (otherNote.isNotBlank()) append(otherNote.trim()) else append("-") append("\n") append("Pengirim: Rakha adi saputro 202310715083") } sendNotification(message) { response -> dialogMessage = response } }) Spacer(modifier = Modifier.height(16.dp)) // dialog hasil if (dialogMessage != null) { Dialog(onDismissRequest = { dialogMessage = null }) { Box( modifier = Modifier .fillMaxSize() .padding(24.dp), contentAlignment = Alignment.Center ) { Surface( shape = RoundedCornerShape(12.dp), color = Color.White, tonalElevation = 8.dp, modifier = Modifier.wrapContentWidth() ) { Column( modifier = Modifier .padding(0.dp) .widthIn(min = 280.dp), horizontalAlignment = Alignment.CenterHorizontally ) { // header Box( modifier = Modifier .fillMaxWidth() .background(color = Color(0xFFB71C1C)) .padding(vertical = 12.dp), contentAlignment = Alignment.Center ) { Text( text = "Notifikasi", color = Color.White, fontWeight = FontWeight.Bold, fontSize = 18.sp ) } Spacer(modifier = Modifier.height(12.dp)) // message Text( text = dialogMessage ?: "", color = Color.Black, modifier = Modifier.padding(horizontal = 16.dp) ) Spacer(modifier = Modifier.height(20.dp)) // action Box( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 12.dp), contentAlignment = Alignment.Center ) { Button( onClick = { dialogMessage = null }, colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFB71C1C)) ) { Text(text = "OK", color = Color.White) } } } } } } } } } // New: 3D-styled PanicButton with press animation and glossy highlight @Composable fun PanicButton( onClick: () -> Unit, modifier: Modifier = Modifier ) { // theme colors val panicColor = Color(0xFFB71C1C) val darkShade = Color(0xFF7F0F0F) val lightAccent = Color(0xFFFF8A80) // interaction for press-state val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() // animations: scale down and lower elevation when pressed val scaleAnim by animateFloatAsState(targetValue = if (isPressed) 0.96f else 1f, animationSpec = tween(120)) val elevationAnim by animateDpAsState(targetValue = if (isPressed) 6.dp else 18.dp, animationSpec = tween(120)) // vertical gradient to simulate 3D lighting val gradient = Brush.verticalGradient(listOf(lightAccent, panicColor, darkShade)) Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = modifier ) { Box(contentAlignment = Alignment.Center) { // soft outer shadow (ground shadow) Box( modifier = Modifier .size(210.dp) .shadow(elevation = elevationAnim, shape = CircleShape) .background(color = Color.Black.copy(alpha = 0.12f), shape = CircleShape) ) // main 3D button Box( contentAlignment = Alignment.Center, modifier = Modifier .size(170.dp) .scale(scaleAnim) .shadow(elevation = elevationAnim, shape = CircleShape) .background(brush = gradient, shape = CircleShape) .clickable(indication = null, interactionSource = interactionSource) { onClick() } ) { // glossy highlight (small white circle offset to top-left) Box( modifier = Modifier .size(70.dp) .offset(x = (-24).dp, y = (-28).dp) .background(color = Color.White.copy(alpha = 0.16f), shape = CircleShape) ) // central exclamation icon Text( text = "!", color = Color.White, fontSize = 72.sp, fontWeight = FontWeight.ExtraBold ) // subtle inner rim to enhance 3D edge Box( modifier = Modifier .matchParentSize() .padding(6.dp) .background(color = Color.Transparent, shape = CircleShape) ) } } } } // Fungsi untuk mengirimkan HTTP request (mengikuti format pesan yang lebih rapi) fun sendNotification(message: String, onResult: (String) -> Unit) { val client = OkHttpClient() val url = "https://ntfy.ubharajaya.ac.id/panic-button" // Ganti <your-topic> dengan topik Anda val requestBody = message.toRequestBody( "text/plain".toMediaType() ) val request = Request.Builder() .url(url) .addHeader("Title", "Alert") .addHeader("Priority", "urgent") .addHeader("Tags", "alert warning,rotating_light") .post(requestBody) .build() // Eksekusi request di thread terpisah Thread { try { val response = client.newCall(request).execute() if (response.isSuccessful) { onResult("Notifikasi berhasil dikirim!") } else { onResult("Gagal mengirim notifikasi: ${response.code}") } } catch (e: Exception) { onResult("Error: ${e.message}") } }.start() } " />
|
|
<option name="updatedContent" value="package id.ac.ubharajaya.panicbutton import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent 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.selection.toggleable import androidx.compose.foundation.shape.CircleShape 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.draw.scale import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MyApp() } } } @Composable fun MyApp() { // dialogMessage akan menampung hasil callback dari sendNotification; null = tidak tampil var dialogMessage by remember { mutableStateOf<String?>(null) } // theme color for panic val panicColor = Color(0xFFB71C1C) // Report options (checkbox-style) with icons (emoji from ntfy emoji list) val reportOptions = listOf( "Kebakaran" to "", "Banjir" to "", "Gempa Bumi" to "", "Huru Hara/Demostrasi" to "", "Lainnya" to "" ) val checkedMap = remember { mutableStateMapOf<String, Boolean>().apply { reportOptions.forEach { put(it.first, false) } } } var otherNote by remember { mutableStateOf("") } // UI Column( modifier = Modifier .fillMaxSize() .padding(18.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { // Checklist area styled as card Surface( color = Color(0xFFFFEBEE), // soft red/pink background to match panic theme tonalElevation = 4.dp, shape = RoundedCornerShape(10.dp), modifier = Modifier .fillMaxWidth(0.92f) .padding(horizontal = 12.dp) ) { Column(modifier = Modifier.padding(14.dp)) { Text( text = "Terjadi Kondisi Darurat", fontWeight = FontWeight.SemiBold, fontSize = 18.sp, color = panicColor ) Spacer(modifier = Modifier.height(10.dp)) // Each option as a row with icon, label and checkbox aligned nicely reportOptions.forEach { (label, icon) -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .padding(vertical = 6.dp) .toggleable( value = checkedMap[label] ?: false, onValueChange = { checked -> checkedMap[label] = checked } ) ) { // icon circle Box( contentAlignment = Alignment.Center, modifier = Modifier .size(34.dp) .background(color = Color.White.copy(alpha = 0.9f), shape = CircleShape) .shadow(1.dp, shape = CircleShape) ) { Text(text = icon, fontSize = 18.sp) } Spacer(modifier = Modifier.width(12.dp)) Column(modifier = Modifier.weight(1f)) { Text(text = label, fontSize = 16.sp) } Checkbox( checked = checkedMap[label] ?: false, onCheckedChange = { checked -> checkedMap[label] = checked }, colors = CheckboxDefaults.colors( checkedColor = panicColor, uncheckedColor = Color.DarkGray ) ) } } Spacer(modifier = Modifier.height(8.dp)) OutlinedTextField( value = otherNote, onValueChange = { otherNote = it }, label = { Text("Catatan tambahan (opsional)") }, placeholder = { Text("Catatan tambahan ...") }, modifier = Modifier .fillMaxWidth() .heightIn(min = 56.dp, max = 140.dp), maxLines = 4 ) Spacer(modifier = Modifier.height(8.dp)) // small hint when Lainnya is checked but note empty if ((checkedMap["Lainnya"] == true) && otherNote.isBlank()) { Text( text = "Catatan wajib jika Anda memilih 'Lainnya'", color = panicColor, style = MaterialTheme.typography.bodySmall, modifier = Modifier.padding(top = 6.dp) ) } } } Spacer(modifier = Modifier.height(20.dp)) // Keep the existing nice PanicButton unchanged (slightly smaller spacing) PanicButton(onClick = { // Validation: at least one option selected val selected = reportOptions.map { it.first }.filter { checkedMap[it] == true } if (selected.isEmpty()) { dialogMessage = "Pilih minimal satu jenis laporan sebelum mengirim." return@PanicButton } if (selected.contains("Lainnya") && otherNote.isBlank()) { dialogMessage = "Untuk opsi 'Lainnya', harap tambahkan catatan yang menjelaskan keadaan." return@PanicButton } // build message: list selected options and the note val message = buildString { append("Jenis Laporan: ") append(selected.joinToString(", ")) append("\n") append("Keterangan: ") if (otherNote.isNotBlank()) append(otherNote.trim()) else append("-") append("\n") append("Pengirim: Rakha adi saputro 202310715083") } sendNotification(message) { response -> dialogMessage = response } }) Spacer(modifier = Modifier.height(14.dp)) // dialog hasil if (dialogMessage != null) { Dialog(onDismissRequest = { dialogMessage = null }) { Box( modifier = Modifier .fillMaxSize() .padding(24.dp), contentAlignment = Alignment.Center ) { Surface( shape = RoundedCornerShape(12.dp), color = Color.White, tonalElevation = 8.dp, modifier = Modifier.wrapContentWidth() ) { Column( modifier = Modifier .padding(0.dp) .widthIn(min = 280.dp), horizontalAlignment = Alignment.CenterHorizontally ) { // header Box( modifier = Modifier .fillMaxWidth() .background(color = panicColor) .padding(vertical = 12.dp), contentAlignment = Alignment.Center ) { Text( text = "Notifikasi", color = Color.White, fontWeight = FontWeight.Bold, fontSize = 18.sp ) } Spacer(modifier = Modifier.height(12.dp)) // message Text( text = dialogMessage ?: "", color = Color.Black, modifier = Modifier.padding(horizontal = 16.dp) ) Spacer(modifier = Modifier.height(20.dp)) // action Box( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp, vertical = 12.dp), contentAlignment = Alignment.Center ) { Button( onClick = { dialogMessage = null }, colors = ButtonDefaults.buttonColors(containerColor = panicColor) ) { Text(text = "OK", color = Color.White) } } } } } } } } } // New: 3D-styled PanicButton with press animation and glossy highlight @Composable fun PanicButton( onClick: () -> Unit, modifier: Modifier = Modifier ) { // theme colors val panicColor = Color(0xFFB71C1C) val darkShade = Color(0xFF7F0F0F) val lightAccent = Color(0xFFFF8A80) // interaction for press-state val interactionSource = remember { MutableInteractionSource() } val isPressed by interactionSource.collectIsPressedAsState() // animations: scale down and lower elevation when pressed val scaleAnim by animateFloatAsState(targetValue = if (isPressed) 0.96f else 1f, animationSpec = tween(120)) val elevationAnim by animateDpAsState(targetValue = if (isPressed) 6.dp else 18.dp, animationSpec = tween(120)) // vertical gradient to simulate 3D lighting val gradient = Brush.verticalGradient(listOf(lightAccent, panicColor, darkShade)) Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, modifier = modifier ) { Box(contentAlignment = Alignment.Center) { // soft outer shadow (ground shadow) Box( modifier = Modifier .size(210.dp) .shadow(elevation = elevationAnim, shape = CircleShape) .background(color = Color.Black.copy(alpha = 0.12f), shape = CircleShape) ) // main 3D button Box( contentAlignment = Alignment.Center, modifier = Modifier .size(170.dp) .scale(scaleAnim) .shadow(elevation = elevationAnim, shape = CircleShape) .background(brush = gradient, shape = CircleShape) .clickable(indication = null, interactionSource = interactionSource) { onClick() } ) { // glossy highlight (small white circle offset to top-left) Box( modifier = Modifier .size(70.dp) .offset(x = (-24).dp, y = (-28).dp) .background(color = Color.White.copy(alpha = 0.16f), shape = CircleShape) ) // central exclamation icon Text( text = "!", color = Color.White, fontSize = 72.sp, fontWeight = FontWeight.ExtraBold ) // subtle inner rim to enhance 3D edge Box( modifier = Modifier .matchParentSize() .padding(6.dp) .background(color = Color.Transparent, shape = CircleShape) ) } } } } // Fungsi untuk mengirimkan HTTP request (mengikuti format pesan yang lebih rapi) fun sendNotification(message: String, onResult: (String) -> Unit) { val client = OkHttpClient() val url = "https://ntfy.ubharajaya.ac.id/panic-button" // Ganti <your-topic> dengan topik Anda val requestBody = message.toRequestBody( "text/plain".toMediaType() ) val request = Request.Builder() .url(url) .addHeader("Title", "Alert") .addHeader("Priority", "urgent") .addHeader("Tags", "alert warning,rotating_light") .post(requestBody) .build() // Eksekusi request di thread terpisah Thread { try { val response = client.newCall(request).execute() if (response.isSuccessful) { onResult("Notifikasi berhasil dikirim!") } else { onResult("Gagal mengirim notifikasi: ${response.code}") } } catch (e: Exception) { onResult("Error: ${e.message}") } }.start() }" />
|
|
</PendingDiffInfo>
|
|
</value>
|
|
</entry>
|
|
</map>
|
|
</option>
|
|
</component>
|
|
</project> |