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